TIL 05 - 인스타그램 클론코딩 (5) User 마무리

MOON·2021년 6월 3일
0
post-thumbnail

노마드코더 인스타그램 클론코딩 바로가기 https://nomadcoders.co/instaclone

포스팅 목차

  1. Following,Follower 구현
  2. Pagination
  3. Computed Fields
  4. Searching User

Following,Follower

유저의 팔로잉과 팔로워를 구현합니다.

Self Relation

  • 한 모델 내의 필드를 Relation 합니다.

// 다 대 다 Self Relation
model User {
   id        Int @default(autoincrement()) @id
   followers User[] @relation("FollowRelation",references:[id])
   following User[] @relation("FollowRelation",references:[id])
}

1:1 Self Relation

  • 1:1은 양쪽 모두 필수가 될 수 없습니다. 한쪽,양쪽은 선택 사항이어야합니다.
  • 관계의 양쪽은 같은 이름을 가져야합니다. (FollowRelation)
  • 하나의 관계 필드는 모든 속성이 정의되어야 합니다.(field 및 references)
  • 하나의 관계 필드는 외래키를 가져야합니다.
    일대일 자기관계는 양쪽이 동일하더라도 두개의 필드가 필요합니다.
    예를 들면 가장친한친구 관계를 모델링한다면 두개의 필드를 만들어야 합니다. bestfriend1 , bestfriend2

1:N Self Relation

model User {
   id        Int @id @default(autoincrement())
   name      String?
   teacherId Int?
   teacher   User? @relation("TeacherStudents",fields:[teacherId],references:[id])
   students  User[] @relation("TeacherStudents")
} 

N:N Self Relation

model User {
   id        Int @id @default(autoincrement())
   name      String?
   followers User[] @relation("UserFollow",references:[id])
   following User[] @relation("UserFollow",references:[id])
}

Follow & UnFollow 기능 구현

이를 위해서 prisma에서 followers,following을 구현
두 필드는 Self Relation으로 N:N 즉 다대다 관계를 가집니다.

FollowUser Resolvers 구현

export default{
   Mutation:{
      followUser:protectedResolver(async (_,{username},{loggedInUser})=>{
         const ok = await client.user.findUnique({
            where:{
               username,
            },
         });
        
         if(!ok){
            return{
               ok:false,
               error:"해당하는 유저가 존재하지 않습니다.",
            }
         }
         
         await client.user.update({
            where:{
               id:loggedInUser.id,
            },
            data:{
               following:{
                  connect:{
                     username,
                  },
               },
            },
         });
        return{
           ok:true,
        }
      });
   }

}

UnFollowUser Resolvers 구현

unFollowUser은 FollowUser와 방밥은 같고 disconnect해주는 것만 다르므로 코드는 생략합니다.
여기서 Relation관계를 가진 필드를 서로 연결해주는 것을 connect 그 연결을 끝내는 것을 disconnect라고 합니다.

SeeFollowers & SeeFollowing

User의 Followers,Following을 보기 위해서는 seeProfile을 이용해 볼 수 있는데 분명 제대로 User에 followers와 following이 있으나 request시 제대로 출력이 되지 않는 이유는 followers와 같은 경우는 수억명이 될 수도있는데 그렇다면 우리의 DB는 수억명의 사람들의 ID랑 사용자 이름을 가져와야 합니다.
이는 DB의 과부화를 가져올 것이기 때문에 좋지 않습니다.
이를 위해서는 Pagination을 사용해 data를 가져오는 것이 좋지만 1차원적인 방법을 그냥 모든 데이터를 다 가져오는 방법도 있습니다.

include

seeProfile resolvers에서 기본적으로 false였기 때문에 제대로 출력되지 않았지만 이를 true로 변경해줬기 때문에 정상적으로 출력되는 것을 볼 수 있습니다.


seeProfile:(_,{username})=>client.user.findUnique({
   where:{
      username,
   },
   include:{
      followers:true,
      following:true,
   }
})

그러나 ❗️❗️

만약 followers가 100명이라면 상관없겠지만 더 커지게 된다면 이는 분명 DB에 과부하를 가져다 줄 것이고 때문에 Pagination을 사용합니다.

Pagination

  1. offset pagination
  2. cursor based pagination

Pagination에는 총 2가지의 Pagination이 존재합니다.

offset Pagination

이 방법은 args로 page를 받아서 해당하는 페이지의 data만 로딩해주는 방법입니다.
findMany와 같이 여러개의 데이터를 가져오는 경우에 take,skip이라는 옵션을 추가해 주기만 하면 됩니다.
take는 몇개의 data를 한 페이지에 담겠는지를 설정하는 옵션이고
skip은 만약 n으로 설정했다면 n+1번째 data부터 로딩하겠다는 의미입니다.

// 첫번째 코드
await client.user.findUnique({
   where:{
      id:loggedInUser.id
   }
}).following({
   take:10,
   skip:page ? (page-1)*10 : 0
});

위 코드는 현재 로그인한 유저의 following한 모든 유저를 가져오는데 offset Pagination을 사용해 페이지당 10개씩의 data를 loading하도록 구현한 코드입니다.

🙀 하나의 유저를 찾고 연결된 모든 following user를 가져오는 방법 X 또다른 방법 !

이 방법은 위 코드와 같은 방법이 아니고 두번째 방법입니다.
첫번째 방법인 위 코드는 한명의 User를 찾고 그 User의 모든 following을 가져왔습니다.
두번째 방법은 모든 User에 방문을 해서 followers에 해당 User가 있는 필터된 모든 User를 가져오는 방법입니다.
쉽게 설명하자면 A와 B가 있을 때 A가 B에게 Following을 했다면
B의 Followers에는 A가 있을 것입니다.

// 두번째 코드
await client.user.findMany({
   where:{
      followers:{
         some:{
            id:loggedInUser.id,
         },
      },
   },
});

-> 첫번째 코드와 두번째 코드는 같은 기능을 하는 코드 입니다.

cursor based Pagination

offset pagination의 단점은 만약 200000만개의 data를 skip한 후 10개의 data를 가져온다고 하면 DB는 200000만개 data를 가져와야 하기 때문에 이상적인 방법은 아닙니다.
따라서 offset pagination으로 pagination을 시작하고 느려졌을때 cursor based pagination을 사용합니다.

예를 들어 take :4를 해서 1,2,3,4를 가져왔다고 하면 cursor는 4가 됩니다. 4는 이미 가져왔기 때문에 DB에 skip:1,take:4를 요청하면 1,2,3,4 이후의 4개의 아이템을 가져옵니다. 그리고는 cursor는 만약 5,6,7,8을 가져왔다고 하면 8이 되는 것입니다.

단 cursor의 값은 Unique해야합니다.

Unique한 어떠한 값이든 가능합니다.

// seeFollowing.resolvers.js

seeFollowing:async (_,{username,lastId})=>{
   const ok = await client.user.findUnique({
      where:{
         username,
      },
      select:{
         id:true,
      },
   });
   if(!ok){
      return{
         ok:false,
         error:"유저가 존재하지 않습니다."
      };
   }
  
   const following = await client.user.findUnique({
      where:{
         username,
      },
   }).following({
      take:5,
      skip:lastId ? 1:0,
      ...(lastId && {cursor:{id:lastId}}),
   })
}

단점

  • 특정페이지로 바로 갈 수 없습니다.
  • 1페이지에서 바로 35페이지로 갈 수 없습니다.

cursor based pagination이 가장 유용하게 사용되는 곳은 무제한 스크롤 페이지가 필요할 경우입니다.

Computed Field

  • graphql schema에는 존재하지만 DB에는 없는 필드를 뜻합니다.
  • 누가 request하는지랑 언제 request하는지에 따라 변동됩니다.

totalFollowing

request

kamoo2라는 username을 가진 user의 총 Following 수를 요청합니다.

{
  seeProfile(username:"kamoo2"){
    totalFollowing
  }
}

user resolvers

위와 같은 요청에 먼저 DB로가 user를 리턴받을 것이고 그 user에서 totalFollowing을 찾을 것입니다. 그러나 computed field이므로 찾지 못하고 type user resolvers로 가서 totalFollowing을 확인하고 있다면 해당 resolvers가 실행됩니다.
그 모든 user들의 followers에 방문해 id:root.id 인 user가 있는 user의 개수를 리턴합니다.

// users.resolvers.js
export default {
   User :{
      totalFollowing:(root)=>{
         return client.user.count({
            where:{
               followers:{
                  some:{
                     id:root.id,
                  },
               },
            },
         });
      }
   }
}

root

root는 seeProfile의 username에 해당하는 user가 리턴됩니다.

0개의 댓글