[NestJS 공식문서 정독하기] Fundamentals - Custom providers
2022. 8. 7. 17:56ㆍWeb/NestJS
반응형
🔗 https://docs.nestjs.com/fundamentals/custom-providers
DI fundamentals
- DI는 의존성들의 인스턴스화를 직접하지 않고 IoC 컨테이너(NestJS 런타임 시스템)에게 위임하는 테크닉임
- NestJS가 DI를 하는 3가지 핵심 과정
@Injectable()
로 NestJS IoC 컨테이너로 관리할 수 있는 클래스를 명시- 명시된 클래스를 다른 클래스의 생성자에서 클래스 토큰으로 의존성을 추가함
- 모듈에서 해당 클래스 토큰을 클래스와 연결함 (등록)
- 위 과정은 상당히 단순화된 과정임
- 의존성 분석(의존성 그래프 생성) 과정의 한 가지 핵심 포인트는 이것이 '전이적'이라는 것임 (의존성의 의존성의 의존성...)
- 의존성이 올바른 순서로 연결되도록 'bottom-up' 방식으로 처리됨
Standard providers
@Module({
providers: [CatsService],
})
@Module({
providers: [
{
provide: CatsService,
useClass: CatsService,
},
];
})
- 위 두 방식은 동일한 기능을 함
CatsService
라는 클래스 토큰을CatsService
클래스 자체와 연결함
Custom providers
- standard providers 방식으로 처리할 수 없는 경우
- NestJS가 생성하는 인스턴스 대신 자신이 직접 생성한 인스턴스를 주입하고 싶음
- 한 클래스를 다른 의존성으로 재사용하고 싶음 (같은 클래스, 다른 인스턴스)
- 클래스를 테스트용으로 Mock해서 덮어씌우고 싶음
- NestJS는 위와 같은 케이스들을 위해 custom providers를 정의할 수 있으며 정의하는 여러가지 방법을 제공함
Value providers: useValue
import { CatsService } from './cats.service';
const mockCatsService = {
/* mock implementation
...
*/
};
@Module({
imports: [CatsModule],
providers: [
{
provide: CatsService,
useValue: mockCatsService,
},
],
})
export class AppModule {}
- 상수값, 외부 라이브러리, 실제 구현체를 Mock 객체로 대체하기 위해 사용할 수 있음
useValue
에는 클래스와 동일한 인터페이스를 갖는 구현체를 넣어야 함- typescript의 structural typing 덕분에 동일한 인터페이스의 객체 혹은 인스턴스를 넣을 수 있음
Non-class-based provider tokens
import { connection } from './connection';
@Module({
providers: [
{
provide: 'CONNECTION',
useValue: connection,
},
],
})
export class AppModule {}
@Injectable()
export class CatsRepository {
constructor(@Inject('CONNECTION') connection: Connection) {}
}
- 클래스명으로 토큰을 설정하는 방식 대신 문자열이나 symbol, 또는 enum으로 토큰을 설정할 수 있음
- 단, 이런 방식은
@Inject()
decorator에 토큰을 넣고 사용하여 주입할 수 있음 - 깔끔한 코드 구성을 위해 토큰을
constants.ts
와 같은 별도의 파일에 명시하는 것이 좋음
Class providers: useClass
const configServiceProvider = {
provide: ConfigService,
useClass:
process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
@Module({
providers: [configServiceProvider],
})
export class AppModule {}
useClass
는 토큰이 연결될 클래스를 동적으로 처리할 수 있음
Factory providers: useFactory
const connectionProvider = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider, optionalProvider?: string) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider, { token: 'SomeOptionalProvider', optional: true }],
// \_____________/ \__________________/
// This provider The provider with this
// is mandatory. token can resolve to `undefined`.
};
@Module({
providers: [
connectionProvider,
OptionsProvider,
// { provide: 'SomeOptionalProvider', useValue: 'anything' },
],
})
export class AppModule {}
useFactory
는 provider를 동적으로 생성할 수 있도록 함 (실제 provider는 factory 함수의 반환값으로 공급됨)- factory 함수는 다른 provider에 의존성을 가질 수도, 가지지 않을 수도 있음
- inject에 명시된 의존성을 순서대로 factory 함수의 파라미터로 전달함
- factory 함수의 의존성은 optional할 수 있음
Alias providers: useExisting
@Injectable()
class LoggerService {
/* implementation details */
}
const loggerAliasProvider = {
provide: 'AliasedLoggerService',
useExisting: LoggerService,
};
@Module({
providers: [LoggerService, loggerAliasProvider],
})
export class AppModule {}
useExisting
은 존재하는 provider에 대한 alias를 생성할 수 있음 (동일한 provider에 대해 접근하는 여러가지 참조 생성 가능)- 위 코드에서
LoggerService
가SINGLETON
스코프 provider라면LoggerService
와loggerAliasProvider
는 동일한 인스턴스를 참조함
Non-service based providers
const configFactory = {
provide: 'CONFIG',
useFactory: () => {
return process.env.NODE_ENV === 'development' ? devConfig : prodConfig;
},
};
@Module({
providers: [configFactory],
})
export class AppModule {}
- provider는 service가 아닌 어떤 값이라도 제공할 수도 있음
Export custom provider
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
exports: ['CONNECTION'],
})
export class AppModule {}
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
exports: [connectionFactory],
})
export class AppModule {}
- custom provider를 export하기 위해서는 토큰을 사용하거나 provider 객체를 사용할 수 있음
반응형