[NestJS 공식문서 정독하기] Fundamentals - Custom providers

2022. 8. 7. 17:56Web/NestJS

반응형
🔗  https://docs.nestjs.com/fundamentals/custom-providers
 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

DI fundamentals

  • DI는 의존성들의 인스턴스화를 직접하지 않고 IoC 컨테이너(NestJS 런타임 시스템)에게 위임하는 테크닉임
  • NestJS가 DI를 하는 3가지 핵심 과정
    1. @Injectable() 로 NestJS IoC 컨테이너로 관리할 수 있는 클래스를 명시
    2. 명시된 클래스를 다른 클래스의 생성자에서 클래스 토큰으로 의존성을 추가함
    3. 모듈에서 해당 클래스 토큰을 클래스와 연결함 (등록)
  • 위 과정은 상당히 단순화된 과정임
  • 의존성 분석(의존성 그래프 생성) 과정의 한 가지 핵심 포인트는 이것이 '전이적'이라는 것임 (의존성의 의존성의 의존성...)
  • 의존성이 올바른 순서로 연결되도록 '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에 대해 접근하는 여러가지 참조 생성 가능)
  • 위 코드에서 LoggerServiceSINGLETON 스코프 provider라면 LoggerServiceloggerAliasProvider 는 동일한 인스턴스를 참조함

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 객체를 사용할 수 있음
반응형