nestjs with graphql - ch5 (dataloader with orm)

Jae Min·2023년 12월 11일
0

nestjs with graphql

목록 보기
5/6

저번에는 실제 디비를 연결하지 않고, 고정 값으로 dataloader 를 구현해봤다면, 이번에는 실제로 디비에 연결해서 dataloader 가 어떻게 돌아가는지 확인해보자.

전체적인 플로우를 먼저 살펴보자
시나리오는 다음과 같다.

" 전체 채팅방에 대해서 각 채팅방에 속해있는 유저들의 정보를 긁어온다. "
chat resolver → chat service → chat repository → user resolver

chat resolver

@Resolver(() => RoomModel)
// 이 resolver 는 chatModel 을 뽑아내기 위한 리졸버이다
export class ChatResolver {
  constructor(
    private readonly chatService: ChatService,
    private readonly chatLoader: ChatLoader,
  ) {}

  @Query(() => [UserModel], { name: 'user' })
  init() {}

  /**
   *
   * @description 전체 채팅방과 그 안에 포함된 유저들의 정보를 불러오자
   */

  @Query(() => [RoomModel])
  async getAllRoomInfo(): Promise<RoomModel[]> {
    return await this.chatService.getAllRoomIds();
  }

  /**
   * @description user resolver 로 필요한 정보를 얻기 위한 쿼리 전송
   * roomId 로 그 안에 속해있는 유저들의 정보를 긁어와야 하는데,
   * resolveField 호출은 여러번 해도 실제 디비에서 조회하는 로직은 한번만 할 수 있도록
   */
  @ResolveField('users', () => [UserModel])
  async getUsers(@Parent() room: RoomModel): Promise<UserModel[]> {
    try {
      const result = await this.chatLoader.findByUserId.load(room.roomId);
      return result;
    } catch (error) {
      this.chatLoader.findByUserId.clear(room.roomId);
    }
  }
}

chat service

@Injectable()
export class ChatService {
  constructor(private readonly roomRepo: RoomRepository) {}
  findAll() {
    return `This action returns all chat`;
  }

  async getAllRoomIds(): Promise<RoomModel[]> {
    const rooms = await this.roomRepo.getAllRoomIds();
    const result: RoomModel[] = [];
    for (const r of rooms) {
      result.push({
        roomId: r.id.toString(),
      });
    }
    return result;
  }
}

chat repository

@Injectable()
export class RoomRepository extends Repository<RoomEntity> {
  constructor(private dataSource: DataSource) {
    super(RoomEntity, dataSource.createEntityManager());
  }

  async getAllRoomIds(): Promise<RoomEntity[]> {
    return await this.createQueryBuilder().getMany();
  }
}

user resolver

@Resolver(() => UserModel)
export class UserResolver {
  constructor(private readonly userService: UserService) {}

  @Query(() => [UserModel], { name: 'user' })
  init() {}

  /**
   *
   * @description chat resolver 에서 처리하지 못하는 쿼리를 여기서 처리함 (find user)
   * resolverField 를 여기서 수신해서 처리한다.
   */
  @ResolveReference()
  async resolveReference(reference: {
    __typename: string;
    id: string;
  }): Promise<UserModel> {
    return await this.userService.getUser(reference.id);
  }
}

user service

@Injectable()
export class UserService {
  constructor(private readonly userRepo: UserRepository) {}
  async getUser(userId: string): Promise<UserModel> {
    const user = await this.userRepo.getUser(userId);
    const result: UserModel = {
      id: user.id,
      name: user.name,
    };
    return result;
  }
}

user repository

@Injectable()
export class UserRepository extends Repository<UserEntity> {
  constructor(private dataSource: DataSource) {
    super(UserEntity, dataSource.createEntityManager());
  }

  async getUser(userId: string): Promise<UserEntity> {
    return await this.createQueryBuilder().where({ id: userId }).getOne();
  }
}

db schema

play ground result

{
  "data": {
    "getAllRoomInfo": [
      {
        "roomId": "23bfddea-ef11-413f-940d-0101fc8ab23c",
        "users": [
          {
            "id": "ab5e6db1-2054-4fd2-9066-4ad1e407e903",
            "name": "tom"
          },
          {
            "id": "268311cb-66ce-486c-85c4-1bc6e0adbd6a",
            "name": "ciara"
          },
          {
            "id": "323d22e7-0edd-4d4f-ae3b-cb8b7ce42a97",
            "name": "json"
          },
          {
            "id": "ba176fc3-b860-4cae-9703-c9030212f5e7",
            "name": "daniel"
          }
        ]
      },
      {
        "roomId": "68e75332-c884-4f20-b05a-2795bf03945a",
        "users": [
          {
            "id": "9220a7f9-c958-48b7-80ff-15d1b462ae5a",
            "name": "jonny"
          },
          {
            "id": "7c7c2b9d-337c-4da7-b0b1-3f88b1adc486",
            "name": "hardy"
          }
        ]
      },
      {
        "roomId": "1ec747ab-b761-4889-89bb-6c6516f4874d",
        "users": []
      }
    ]
  }
}

repository ???

여기서 주의 깊게 봐야할 부분은 repository 이다
현재 나는 typeorm 버전이 3.0.0 이상이다
대부분의 사람들이 알겠지만, typeOrm 버전이 올라가면서 @EntityRepository() 가 없어졌다.
구글링을 해보면 다들 custom repository 를 만들어서, @InjectRepository() 를 이용해서 각 모듈마다 주입을 해주는 방법을 구현하고 있는데, 생각보다 너무 복잡해서 굳이 이렇게까지 어렵게 구현하라고 nest 가 @EntityRepository() 를 없애진 않았을 것이다.

찾아보다가 좋은 방법을 찾았다.
https://stackoverflow.com/questions/72549668/how-to-do-custom-repository-using-typeorm-mongodb-in-nestjs

위에 나와있는 repository 코드가 다음 레퍼런스를 참고해서 만들었다.
생성자 부분을 잘 보면, 필요한 엔티티를 부모 클래스인 Repository 로 넘겨주면 provider 로 사용할 수 있게 된다.

여기서 transaction 은 어떻게 처리하는지는 다음에 알아보겠다.


REF

사진: UnsplashBarn Images

profile
자유로워지고 싶다면 기록하라.

0개의 댓글