typeorm의 버전이 0.3.x 로 올라가면서 0.2와는 너무나도 다른 많은 변경들이 생겼다. 이것은 혼란 그 잡채였다.
기존 0.2 버전에서는 @EntityRepository를 이용해서 커스텀 레포지토리를 만들어서 쓸 수 있었다면 0.3버전에서는 @EntityRepository가 deprecated되어서 사용할 수 없었다.
커스텀 레포지토리는 필수적 요소라고 생각하기 때문에 typeorm 버전 up이 되더라도 무조건 쓸 수 있게 설정 해놔야 한다고 생각했다. 그래서 여러가지 방법들을 찾아보려고 노력하였다. 두가지 방법에 대하여 찾아봤는데 그 전에 레포지토리가 무엇인지 먼저 살펴보도록 하겠다.
리포지토리는 데이터 원본에 액세스하는 데 필요한 논리를 캡슐화하는 클래스 또는 구성 요소입니다. 리포지토리는 공통 데이터 액세스 기능에 집중해 더 나은 유지관리를 제공하고 도메인 모델 계층에서 데이터베이스에 액세스하는 데 사용되는 기술이나 인프라를 분리합니다. Entity Framework와 같은 ORM(개체 관계 매핑)을 사용하는 경우 LINQ 및 강력한 형식화 덕분에 구현해야 할 코드가 간소화됩니다. 이렇게 하면 데이터 액세스 내부 작업보다 데이터 지 속성 논리에 더 집중하게 합니다.
간략하게 db와 관련된 일을 서비스단에서 하는것이 아닌 repository에서 다루어서 서비스단에서는 db의 쿼리들이 노출되지 않는다라는 것이다.
간단한 방법으로 다른 provider들과 똑같이 쓰는 것이다.
user.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService, UserRepository],
})
export class UserModule {}
user.repository.ts
@Injectable()
export class UserRepository extends Repository<User> {
constructor(private dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
async findByUsername(username: string): Promise<User> {
const result = await this.createQueryBuilder('user')
.where('user.username = :username', { username: username })
.getOne();
return result;
}
}
user.service.ts
@Injectable()
export class UserService {
constructor(
private userRepository: UserRepository,
) {}
async findByUsername(username: string): Promise<User> {
return this.userRepository.findByUsername(username);
}
}
Repository에서 dataSource를 injection 받는 방법인 것이다.
constructor의 기본 짜임새는 아래와 같다.
constructor(target: EntityTarget<Entity>, manager: EntityManager, queryRunner?: QueryRunner);
두번째 방법은 현재 나는 에러가 나는데 다른 분들은 잘 되신건지 공유를 하신 방법이다. 나는 해결을 못해서 일단 공유는 하는데 혹시나 빠진 부분이 있거나 실수한 부분이 있다면 댓글 부탁드립니다.
두번째 방법으로는 @EntityRepository와 유사하게 만든 @CustomRepository를 사용하는 방법이다.
우선 소스에서 @CustomRepository 데코레이터와 module을 만들어준다.
src/db/typeorm.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const TYPEORM_CUSTOM_REPOSITORY = 'TYPEORM_CUSTOM_REPOSITORY';
// eslint-disable-next-line @typescript-eslint/ban-types
export function CustomRepository(entity: Function): ClassDecorator {
return SetMetadata(TYPEORM_CUSTOM_REPOSITORY, entity);
}
src/db/typeorm-custom.module.ts
import { DynamicModule, Provider } from '@nestjs/common';
import { getDataSourceToken } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { TYPEORM_CUSTOM_REPOSITORY } from './typeorm.decorator';
export class TypeOrmCustomModule {
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_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: TypeOrmCustomModule,
providers,
};
}
}
user.repository.ts
@CustomRepository(User)
export class UserRepository extends Repository<User> {}
user.module.ts
@Module({
imports: [TypeOrmCustomModule.forCustomRepository([UserRepository])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
이렇게 해주면 0.2.x 버전의 @EntityRepository와 유사하게 사용할 수 있다.
하지만 난 되지 않아서 첫번째 방법으로 해줬다.나의 두번째 방법 에러는 아래와 같다.
[Nest] 160316 - 2022. 10. 19. 오후 12:21:50 ERROR [ExceptionHandler] Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index [0] is available in the UserModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current UserModule?
- If UserRepository is exported from a separate @Module, is that module imported within UserModule?
@Module({
imports: [ /* the Module containing UserRepository */ ]
})
Error: Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index [0] is available in the UserModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current UserModule?
- If UserRepository is exported from a separate @Module, is that module imported within UserModule?
@Module({
imports: [ /* the Module containing UserRepository */ ]
})
at Injector.lookupComponentInParentModules (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:241:19)
at Injector.resolveComponentInstance (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:194:33)
at resolveParam (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:116:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:131:27)
at Injector.loadInstance (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:57:13) at Injector.loadProvider (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:84:9)
at async Promise.all (index 3)
at InstanceLoader.createInstancesOfProviders (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\instance-loader.js:47:9)
at D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\instance-loader.js:32:13
이렇게 NestJS@9.x.x & TypeORM@0.3.x에서 customRepository를 사용하는 방법에 대하여 알아보았다.