[Project] Nest 넘어져보기 (Service, Repository 분리)
시작하며
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가 되고, entity가 value값이 되는 것입니다.
다이나믹 모듈 생성
아래 동적 모듈은 위에서 작성한 @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를 추가해 주면 된다