Project

[Project] Nest 넘어져보기 (Service, Repository 분리)

붓 필 2023. 4. 18. 09:19


시작하며

nest 프로젝트를 하면서 Service와 Repositoty를 분리하는 것이 좋다는 조언을 얻었습니다. 저는 Service계층에서 직접 DB에 접근하는 방법을 쓰고 있었고, 구글링을 통해 @EntityRepository()를 사용해서 Service와 Repository를 분리하는 방법을 알아냈습니다. (현재 작업 중인 프로젝트에서 typeORM을 쓰고 있습니다)

 

 


 

마주친 문제

typeorm 0.3.x 버전 이후 해당 @EntityRepository()를 사용하게 되면 deprecated라는 문구를 보게 되며, 사용이 불가하다는 사실을 알게 됩니다. 이에 대한 문제 해결을 다루어 보려 합니다.

 

문제 개선 전)

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(UserRepository)
    private userRepository: Repository<User>   <- 이 부분
  ){}
}

이렇게 Service에서 typeORM이 제공하는 Repository<Entity>를 통해 Entity에 접근할 수 있지만, 이 부분을 분리하여 문제를 해결해 보려 합니다. 이 문제를 해결하기 위해 Custom Repository를 사용해야 합니다.

Custom Repository 생성 

커스텀 데커레이터 생성

// typeorm-ex-decotator.ts

import { SetMetadata } from "@nestjs/common";

export const TYPEORM_EX_CUSTOM_REPOSITORY = "TYPEORM_EX_CUSTOM_REPOSITORY";

export function CustomRepository(entity: Function): ClassDecorator {
  return SetMetadata(TYPEORM_EX_CUSTOM_REPOSITORY, entity);
}

이 코드는 @EntityRepository()를 대체할 코드입니다. 벌써부터 어질어질합니다......

vsCode 정의 기능으로 SetMetadata를 읽어 봅시다.

/**
 * Decorator that assigns metadata to the class/function using the
 * specified `key`.
 *
 * Requires two parameters:
 * - `key` - a value defining the key under which the metadata is stored
 * - `value` - metadata to be associated with `key`
 *
 * This metadata can be reflected using the `Reflector` class.
 *
 * Example: `@SetMetadata('roles', ['admin'])`
 *
 * @see [Reflection](https://docs.nestjs.com/guards#reflection)
 *
 * @publicApi
 */
export declare const SetMetadata: <K = string, V = any>(metadataKey: K, metadataValue: V) => CustomDecorator<K>;

이 외계어를 최대한 해석해 보겠습니다. 위 코드는 key : value형태를 인자로 받고 있습니다. 여기서 key는 TYPEORM_EX_CUSTOM_REPOSITORY 되고, entityvalue값이 되는 것입니다.

다이나믹 모듈 생성

아래 동적 모듈은 위에서 작성한 @CustomRepository가 적용된 레파지토리를 받아줄 모듈입니다.

// typeorm-ex.module.ts

import { DynamicModule, Provider } from '@nestjs/common'
import { getDataSourceToken } from '@nestjs/typeorm'
import { DataSource } from 'typeorm'
import { TYPEORM_EX_CUSTOM_REPOSITORY } from './typeorm-ex.decorator'

export class TypeOrmExModule {
  public static forCustomRepository<T extends new (...args: any[]) => any>(
    repositories: T[],
  ): DynamicModule {
    const providers: Provider[] = []

    for (const repository of repositories) {
      const entity = Reflect.getMetadata(
        TYPEORM_EX_CUSTOM_REPOSITORY,
        repository,
      )

      if (!entity) {
        continue
      }

      providers.push({
        inject: [getDataSourceToken()],
        provide: repository,
        useFactory: (dataSource: DataSource): typeof repository => {
          const baseRepository = dataSource.getRepository<any>(entity)
          return new repository(
            baseRepository.target,
            baseRepository.manager,
            baseRepository.queryRunner,
          )
        },
      })
    }

    return {
      exports: providers,
      module: TypeOrmExModule,
      providers,
    }
  }
}

 

아직까지 위 코드를 정확히 이해하고 내 코드로 습득하지는 못했다.......

 

모듈에 적용시키기

-> user.repository.ts

-> user.service.ts

보다시피 서비스에서 @injectRepository를 사용하지 않았다.

 

-> auth.module에 imports 부분을 보면 forCustomRepository로 불러온다.

-> app.module에 entities에 User를 추가해 주면 된다

 


출처: