13. Dynamic modules

Module에 관한 장은 Nest 모듈의 기본 사항을 다루고 동적 모듈 에 대한 간략한 소개를 포함하고 있다. 이 장에서는 동적 모듈 주제를 심화 학습한다. 완료되면 해당 기능이 무엇인지, 언제 어떻게 사용해야 하는지 이해할 수 있을 것이다.

소개 

1~10 장 섹션 에 있는 대부분의 애플리케이션 코드 예제는 일반 또는 정적 모듈을 사용하고 있다. 모듈은 전체 애플리케이션의 모듈화된 부분으로 함께 어울리는 공급자  컨트롤러 와 같은 구성 요소 그룹을 정의한다. 또한, 이러한 구성 요소에 대한 실행 컨텍스트 또는 범위를 제공한다. 예를 들어, 모듈에 정의된 공급자는 익스포트 할필요 없이 모듈의 다른 구성원에게서 사용 될 수 있다. 공급자가 모듈 외부에 사요되어야 하는 경우 먼저 호스트 모듈에서 내보낸 다음 소비 모듈로 가져오면 된다.

 

친숙한 예를 살펴보자.

먼저 UsersModule 를 제공하고 UsersService 를 내보내도록 정의한다. UsersService 의 호스트 모듈은 UsersModule이다.

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

다음으로, UsersModule 를  임포트하고, UserModule을 포함하는 AuthModule 을 정의한다. UserModule 에서 익스포트한  공급자를 AuthModule 내부에서 사용할 수 있게 만든다.

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [UsersModule],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {}

이러한 구성을 사용하면, 예를 들어   AuthModule 에서 호스팅되는 AuthService 에  UserService 를 삽입할 수 있다.

import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}
  /*
    Implementation that makes use of this.usersService
  */
}

우리는 이것을 정적 모듈 바인딩 이라고 부른다. Nest가 모듈을 연결하는 데 필요한 모든 정보는 이미 호스트 및 소비 모듈에서 선언되었다. 이 과정에서 무슨 일이 일어나고 있는지 살펴보자. Nest는 다음과 같은 방법으로 AuthModule 내부에서 UsersService 를 사용할 수 있다 .

  1. 자체적으로 사용하는 다른 모듈 UsersModule 을 전이적으로 가져오고 모든 종속성을 전이적으로 해결하는 것을 포함하여 UsersModule 을 인스턴스화한다 ( 사용자 정의 공급자 참조 ).
  2. AuthModule 을 인스턴스화하고 UsersModule 에서 익스포트한 공급자를 AuthModule 의 구성 요소에서 사용할 수 있도록 만든다. (에서 선언된 것처럼 AuthModule).
  3. AuthService 안에서 UsersServicein 의 인스턴스를 주입한다.

 

동적 모듈 사용 사례

정적 모듈 바인딩을 사용하면 소비(Consumer) 모듈이 호스트 모듈의 공급자 구성 방식에 영향을 미칠 기회가 없다 . 이것이 왜 중요 하나면,  다양한 사용 사례에서 다르게 동작해야 하는 범용 모듈이 있는 경우를 생각해 보자. 이는 소비자(Consumer)가 일반 기능을 사용하기 전에 구성이 필요한 시스템의 "플러그인" 개념과 유사하다.

 

Nest의 좋은 예는 구성 모듈이다 . 많은 애플리케이션에서는 구성 모듈을 사용하여 구성 세부 정보를 외부화하는 것이 유용하다. 이를 통해 개발자를 위한 개발 데이터베이스, 스테이징/테스트 ​​환경을 위한 스테이징 데이터베이스 등 다양한 배포에서 애플리케이션 설정을 동적으로 쉽게 변경할 수 있다. 구성 매개변수 관리를 구성 모듈에 위임함으로써 애플리케이션 소스 코드 구성 매개변수와 독립적으로 유지된다.

 

문제는 구성 모듈 자체가 일반적이기 때문에("플러그인"과 유사) 소비 모듈에 의해 사용자 정의되어야 한다는 것이다. 여기가 동적 모듈이 작동하는 곳이다. 동적 모듈 기능을 사용하면 소비 모듈이 API를 사용하여 구성 모듈을 가져올 때 구성 모듈을 사용자 정의하는 방법을 제어할 수 있도록 구성 모듈을 동적으로 만들 수 있다 .

즉, 동적 모듈은 지금까지 본 정적 바인딩을 사용하는 것과는 달리 하나의 모듈을 다른 모듈로 가져오고 모듈을 가져올 때 해당 모듈의 속성과 동작을 사용자 정의하기 위한 API를 제공한다.

 

 

구성 모듈 예시 

이 섹션의 구성 장 에 있는 예제 코드의 기본 버전을 사용하겠다 . 이 장의 끝부분에 있는 완성된 버전은 여기에서 예제로 제공된다 .

 

우리의 요구 사항은 개체를 사용자 정의하기 위해 options 객체를 수락 하는 ConfigModule 을 만드는 것이다. 우리가 지원하고 싶은 기능은 다음과 같다. 기본 샘플은 프로젝트 루트 폴더에 .env 파일 위치를 하드 코딩한다. 선택한 폴더에서 .env 파일을 관리할 수 있도록 이를 구성 가능하게 만든다고 가정해 보자. 예를 들어, 프로젝트 루트 아래의 config 폴더 (예: src 의 형제 폴더)에 다양한 .env 파일을 저장한다고 가정할 수 있다. 다른 프로젝트에서 ConfigModule 를 사용할 때 다른 폴더를 선택할 수도 있기를 원할 것이다.

 

동적 모듈은 가져오는 모듈에 매개변수를 전달하여 동작을 변경할 수 있는 기능을 제공한다. 이것이 어떻게 작동하는지 보자. 소비 모듈의 관점에서 이것이 어떻게 보일지에 대한 최종 목표부터 시작한 다음 거꾸로 작업하는 것이 도움이 된다. 먼저, ConfigModule 을 정적으로 가져오는 (즉, 가져온 모듈의 동작에 영향을 줄 수 없는 접근 방식)예를 빠르게 검토해 보겠다. @Module() 데코레이터가 있는 imports 배열을 주의깊게 보자.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

구성 객체를 전달하는 동적 모듈 가져오기가 어떤 모습일지 생각해 보자. 다음 두 예제의 imports 배열 차이를 비교해 보면 된다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule.register({ folder: './config' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

위의 동적 예에서 무슨 일이 일어나고 있는지 살펴봤을 때, 동적인 부분은 무엇일까?

  1. ConfigModule은 일반 클래스이므로 register() 이라는 정적 메서드가 있어야 한다고 추론할 수 있다 . 클래스 인스턴스가 ConfigModule 클래스에서 호출하기 때문에 정적이라는 것을 알 수 있다. 참고: 곧 생성할 이 메소드는 임의의 이름을 가질 수 있지만 관례상 forRoot() 또는 register() 로 호출해야 한다.
  2. 메서드 register()는 우리가 정의하므로 원하는 입력 인수를 받아들일 수 있다. 일반적인 경우, 적절한 속성을 가진 간단한 options 개체를 허용한다 .
  3. 반환 값이 지금까지 봐왔던 익숙한 imports 목록에 나타나기 때문에 register()메서드가 module 같은 것을 반환해야 한다고 추측할 수 있다 .

실제로 register() 메서드가 반환할 내용은 DynamicModule 이다. 동적 모듈은 정적 모듈과 동일한 속성과 module 이라는 속성을 추가하여 런타임에 생성된 모듈에 지나지 않는다 . 데코레이터에 전달된 모듈 옵션에 주의하면서 샘플 정적 모듈 선언을 빠르게 검토해 보자.

@Module({
  imports: [DogsModule],
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})

힌트) 동적 모듈의 경우 module  속성을 제외한 옵션 개체의 모든 속성은 선택 사항 입니다.

 

정적 register()방법과 비교하면 그 작업이 인터페이스가 있는 DynamicModule 객체를 반환하는 것임을 알 수 있다. 이를 호출하면 모듈 클래스 이름을 나열하여 정적 사례에서 수행하는 방식과 유사하게 imports 목록에 모듈을 효과적으로 제공한다. 즉, 동적 모듈 API는 단순히 모듈을 반환하지만 @Module 데코레이터의 속성을 수정하는 대신 프로그래밍 방식으로 지정한다.

그림을 완성하는 데 도움이 되는 몇 가지 세부 사항을 다루어야 한다.

  1. 이제 @Module() 데코레이터의 imports속성이 모듈 클래스 이름(예: imports: [UsersModule] )뿐만 아니라 동적 모듈을 반환하는함수 (예: imports: [ConfigModule.register(...)] ) 도 사용할 수 있다고 말할 수 있다.
  2. 동적 모듈은 자체적으로 다른 모듈을 가져올 수 있다. 이 예에서는 그렇게 하지 않지만 동적 모듈이 다른 모듈의 공급자에 의존하는 경우 선택적 imports속성을 사용하여 해당 모듈을 가져온다. 다시 말하지만, 이는 @Module() 데코레이터를 사용하여 정적 모듈에 대한 메타데이터를 선언하는 방식과 정확히 유사하다.

이러한 이해를 바탕으로 이제 동적 ConfigModule 선언이 어떤 모습인지 알 수 있다 . 한 번 살펴보자.

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({})
export class ConfigModule {
  static register(): DynamicModule {
    return {
      module: ConfigModule,
      providers: [ConfigService],
      exports: [ConfigService],
    };
  }
}

이제 조각들이 어떻게 연결되는지 분명해졌다. ConfigModule.register(...)호출하면 지금까지 @Module() 데코레이터를 통해 메타데이터로 제공한 속성과 본질적으로 동일한 속성을 가진 DynamicModule 객체가 반환된다.

 

힌트) DynamicModule 은 @nestjs/common 패키지에서 가져온다.

 

그러나 동적 모듈은 아직까지 그닥 흥미롭지 않다. 우리가 원하는 대로 구성할 수 있는 기능을 도입하지 않았기 때문이다. 이에 대해서는 다음에 다루겠다.

 

모듈 구성

ConfigModule 의 동작을 사용자 정의하기 위한 확실한 해결책은 위에서 추측한 것처럼 정적 register() 메서드에서 options 객체를 전달하는 것이다. 컨슈머 모듈의 imports 속성을 다시 한 번 살펴보자.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
  imports: [ConfigModule.register({ folder: './config' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

이는 동적 모듈에 options 객체를 전달하는 것을 보여준다. 그러면 ConfigModule 에서 해당 options 객체를 어떻게 사용할까? 잠시 생각해 보자. 우리는 기본적으로 다른 공급자가 사용할 수 있도록 ConfigModule 은 주입 가능한 서비스를 제공하고 내보내는 호스트라는 것을 알고 있다. 다른 공급자에 의해 사용될 ConfigService 의 예에서 실제로 동작을 사용자 정의하기 위해 options 객체를 읽어야 한다. register 메소드에서 options 객체를 어떻게든 가져오는 방법을 알고 있다고 가정해 보자. 이러한 가정을 바탕으로 서비스를 몇 가지 변경하여 options 객체의 속성을 기반으로 동작을 사용자 지정할 수 있다. ( 참고 : 당분간 실제로 전달 방법을 결정 하지 않았 으므로 options 를 하드 코딩만 한다 . 이 문제는 잠시 후에 수정 하겠다.)

import { Injectable } from '@nestjs/common';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
import { EnvConfig } from './interfaces';

@Injectable()
export class ConfigService {
  private readonly envConfig: EnvConfig;

  constructor() {
    const options = { folder: './config' };

    const filePath = `${process.env.NODE_ENV || 'development'}.env`;
    const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  get(key: string): string {
    return this.envConfig[key];
  }
}

이제 ConfigService 는 우리가 특정한 .env 파일을 찾는 방법을 알수 있다.

Now our ConfigService knows how to find the .env file in the folder we've specified in options.

남은 임무는 어떻게든 register() 메소드에서 연유한 option객체를 ConfigService에 주입하는 것이다. 물론 이를 위해 종속성 주입을 사용할 것이다. ConfigModule은 ConfigService 를 제공한다. ConfigService 는 런타임에 제공되는 options 객체에 의존한다. 따라서 Nest IoC 컨테이너에서 options 객체를 바인딩 할 필요가 있다. 그 후 Nest 는 ConfigService 에 options 객체를 주입한다. 우리는 '사용자 정의 공급자 장' 에서 공급자는 서비스 뿐만 아니라 모든 값을 포함 할 수 있으므로 종속성 주입을 통해서 간단한 개체를 처리해도 괜찮다는 점을 기억해야 한다.

먼저 options 객체를 IoC 컨테이너에 바인딩 하는 방법을 살펴보자. 우리는 이것을 정적 register() 메소드로 수행한다. 모듈을 동적으로 그성하고 있으며 모듈의 속성 중 하나가 공급자 목록이라는 점을 기억하자. 따라서 우리가 해야 할 일은 options 객체를 공급자로 정의하는 것이다.  이렇게 하면 다음 단계에서 활용하게 될 ConfigService 에 주입할 수 있게 된다.. 아래 코드에서 providers배열에 주의하자.

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({})
export class ConfigModule {
  static register(options: Record<string, any>): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

이제 공급자 'CONFIG_OPTIONS'를 ConfigService 에 주입하는 과정에서 클래스가 아닌 토큰을 사용하여 공급자를 정의할 때 여기에 설명된 대로 @Inject() 데코레이터를 사용해야 한다는 점을 알아두자.

import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
import { Injectable, Inject } from '@nestjs/common';
import { EnvConfig } from './interfaces';

@Injectable()
export class ConfigService {
  private readonly envConfig: EnvConfig;

  constructor(@Inject('CONFIG_OPTIONS') private options: Record<string, any>) {
    const filePath = `${process.env.NODE_ENV || 'development'}.env`;
    const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  get(key: string): string {
    return this.envConfig[key];
  }
}

마지막 참고 사항: 단순화를 위해 위에서 문자열 기반 주입 토큰( 'CONFIG_OPTIONS' )을 사용했지만 모범 사례는 이를 별도의 파일에서 'CONFIG_OPTIONS' 상수(또는 Symbol) 로 정의하고 해당 파일을 가져오는 것입니다 .를 들어:

export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';

이 장에 있는 코드의 전체 예는 여기에서 찾을 수 있습니다.

 

커뮤니티 가이드라인

일부 @nestjs/ 패키지 주변에서 forRoot, register및 forFeature 은 메서드의 사용을 본 적이 있을 수 있으며 이러한 모든 메서드의 차이점이 무엇인지 궁금할 수 있습니다. 이에 대한 엄격한 규칙은 없지만 @nestjs/ 패키지는 다음 지침을 따르려고 노력하고 있다.

 

다음을 사용하여 모듈을 생성할 때:

  • register, 호출 모듈에서만 사용할 특정 구성으로 동적 모듈을 구성하려고 할때, 예를 들어 Nest의 @nestjs/axios: HttpModule.register({ baseUrl: 'someUrl' }). 다른 모듈에서 HttpModule.register({ baseUrl: 'somewhere else' }) 을 사용하면 구성이 달라진다. 이처럼, 원하는 만큼 많은 모듈에 대해 이 작업을 수행할 수 있다.
  • forRoot, 동적 모듈을 한 번 구성하고 해당 구성을 여러 위치에서 재사용할 것으로 예상할 수 있다.(비록 추상화되어 있어서 자신도 모르게). 이것이 하나의 GraphQLModule.forRoot(),  하나의 TypeOrmModule.forRoot() 등이 있는 이유이다.
  • forFeature, 동적 모듈의 forRoot 구성을 사용할 것으로 예상 하지만 호출 모듈의 요구 사항에 따라 일부 구성을 수정해야할 때.(예: 이 모듈이 액세스해야 하는 저장소 또는 로거가 사용해야 하는 컨텍스트) .

이들 모두에는 일반적 async으로 대응되는 registerAsync, forRootAsync및 forFeatureAsync 가 있다 . 이는 동일한 의미이지만 구성에도 Nest의 종속성 주입을 사용한다.

 

구성 가능한 모듈 빌더

async메소드 ( registerAsync, forRootAsync등) 를 노출하는 구성 가능한 동적 모듈을 수동으로 생성하는 것은 매우 복잡하기 때문에, 특히 초보자에게는 Nest가 이 프로세스를 용이하게 하는 ConfigurableModuleBuilder 클래스를 노출하고 단 몇 줄의 코드로 모듈을 구성할 수 있다.

 

예를 들어 위에서 사용한 예( ConfigModule)를 ConfigurableModuleBuilder. ConfigModule 을 사용하여 변환해 보겠다. 시작하기 전에 우리가 어떤 옵션을 선택하는지 나타내는 전용 인터페이스를 생성하도록 하자 .

export interface ConfigModuleOptions {
  folder: string;
}

이를 사용하여 기존 config.module.ts 파일과 함께 새 전용 파일을 만들고 config.module-definition.ts 로 이름 짓는다. 이 파일에서는 ConfigModule 정의를 구성하는 데 ConfigurableModuleBuilder를 활용해 본다.

// config.module-definition.ts

import { ConfigurableModuleBuilder } from '@nestjs/common';
import { ConfigModuleOptions } from './interfaces/config-module-options.interface';

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigModuleOptions>().build();

이제 config.module.ts 파일을 열고자동 생성된 ConfigurableModuleClass 항목을 활용하도록 구현을 수정해 보자.

import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass } from './config.module-definition';

@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {}

이제 ConfigurableModuleClass 확장함으로써,  ConfigModule 이 register  메서드(이전의 사용자 지정 구현과 마찬가지로)뿐만 아니라 소비자가 해당 모듈을 비동기식으로 구성할 수 있는 registerAsync 메서드(예: 비동기 팩토리 제공)를 제공할 수 있다.

@Module({
  imports: [
    ConfigModule.register({ folder: './config' }),
    // 위에 register 메서드 대신 registerAsync 메소드를 사용할 수 있다.
    // ConfigModule.registerAsync({
    //   useFactory: () => {
    //     return {
    //       folder: './config',
    //     }
    //   },
    //   inject: [...any extra dependencies...]
    // }),
  ],
})
export class AppModule {}

마지막으로, 지금까지 사용한 'CONFIG_OPTIONS' 대신 생성된 모듈 옵션 제공자를 주입하도록 ConfigService 클래스를 업데이트 하겠다.

@Injectable()
export class ConfigService {
  constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) { ... }
}

사용자 정의 메서드 키

ConfigurableModuleClass 는기본적으로 register및 해당 registerAsync메소드를 제공한다. 다른 메소드 이름을 사용하려면다음과 같이  ConfigurableModuleBuilder#setClassMethodName 메소드를 사용한다.

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigModuleOptions>().setClassMethodName('forRoot').build();

이 구성은 ConfigurableModuleBuilder 로 하여금 forRoot  forRootAsync 노출하는 클래스를 생성하도록 지시한다.

@Module({
  imports: [
    ConfigModule.forRoot({ folder: './config' }), // <-- register 대산 'forRoot'를 사용함
    // 대신 forRootAsync를 써도 된다
    // ConfigModule.forRootAsync({
    //   useFactory: () => {
    //     return {
    //       folder: './config',
    //     }
    //   },
    //   inject: [...any extra dependencies...]
    // }),
  ],
})
export class AppModule {}

사용자 정의 옵션 팩토리 클래스 

메서드 registerAsync(또는 forRootAsync구성에 따라 다른 이름)를 통해 소비자는 모듈 구성을 확인하는 공급자 정의를 전달할 수 있으므로 라이브러리 소비자는 잠재적으로 구성 개체를 구성하는 데 사용할 클래스를 제공할 수 있다.

@Module({
  imports: [
    ConfigModule.registerAsync({
      useClass: ConfigModuleOptionsFactory,
    }),
  ],
})
export class AppModule {}

기본적으로 이 클래스는모듈 구성 개체를 반환하는  create() 메서드를 제공해야 한다. 그러나 라이브러리가 다른 명명 규칙을 따르는 경우 해당 동작을 변경하고 ConfigurableModuleBuilder#setFactoryMethodName 메서드를 사용하여 다른 메서드(예 : createConfigOptions) 를 기대하도록 ConfigurableModuleBuilder 에게 지시할 수 있다

// config.module-definition.ts

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
  new ConfigurableModuleBuilder<ConfigModuleOptions>().setFactoryMethodName('createConfigOptions').build();

이제 ConfigModuleOptionsFactory 클래스는 createConfigOptions 메소드( create대신 )를 노출해야 한다.

@Module({
  imports: [
    ConfigModule.registerAsync({
      useClass: ConfigModuleOptionsFactory, // <-- this class must provide the "createConfigOptions" method
    }),
  ],
})
export class AppModule {}

추가 옵션 

모듈이 어떻게 작동할지 결정하는 추가 옵션이 있을 수 있다, (이러한 옵션의 좋은 예는 플래그 isGlobal또는 global 이다). 이러한 경우 추가 옵션은 MODULE_OPTIONS_TOKEN 공급자에 포함되면 안된다.(해당 모듈 내에 등록된 서비스/공급자와 관련이 없기 때문에, 예를 들어 ConfigService 가 ConfigService의 호스트 모듈이 전역 모듈로 등록되었는지 여부를 알 필요가 없다).

이러한 경우에는 ConfigurableModuleBuilder#setExtras 메소드를 사용하는 방법이 있다. 다음 예를 참조:

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<ConfigModuleOptions>()
  .setExtras(
    {
      isGlobal: true,
    },
    (definition, extras) => ({
      ...definition,
      global: extras.isGlobal,
    }),
  )
  .build();

위의 예에서 setExtras 메서드에 전달된 첫 번째 인수는 "추가" 속성에 대한 기본값을 포함하는 개체이다. 두 번째 인수는 자동 생성된 모듈 정의( provider, exports등 포함)와 추가 속성(소비자 또는 기본값에 의해 지정됨)을 나타내는 extras 객체를 취하는 함수다. 이 함수의 반환 값은 수정된 모듈 정의인데, 이 특정 예에서는 extras.isGlobal 속성을 가져와 모듈 정의의 global 속성에 할당한다. (모듈 정의는 모듈이 전역인지 아닌지를 결정한다. 자세한 내용은 여기를 참조).

 

이제 이 모듈을 사용할 때 다음과 같이 추가 isGlobal플래그를 전달할 수 있다.

@Module({
  imports: [
    ConfigModule.register({
      isGlobal: true,
      folder: './config',
    }),
  ],
})
export class AppModule {}

하지만 isGlobal"추가" 속성으로 선언되었으므로 MODULE_OPTIONS_TOKEN 공급자에서 사용할 수 없다.

@Injectable()
export class ConfigService {
  constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) {
    // "options" 객체는 "isGlobal" 속성을 사용할 수 없다.
    // ...
  }
}

자동 생성된 메서드 확장 

필요한 경우 자동 생성된 정적 메서드( register, registerAsync 등)를 다음과 같이 확장할 수 있다.

import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, OPTIONS_TYPE } from './config.module-definition';

@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {
  static register(options: typeof OPTIONS_TYPE): DynamicModule {
    return {
      // your custom logic here
      ...super.register(options),
    };
  }

  static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
    return {
      // your custom logic here
      ...super.registerAsync(options),
    };
  }
}

모듈 정의 파일에서 내보내야 하는 OPTIONS_TYPE 및 ASYNC_OPTIONS_TYPE 타입의 사용에 유의하자.

 

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE } = new ConfigurableModuleBuilder<ConfigModuleOptions>().build();

'Backend(Framework) > NestJS 개요(공식문서 번역)' 카테고리의 다른 글

15. Circular Dependency  (1) 2023.12.02
14. Injection scopes  (1) 2023.12.02
12. Asynchronous providers  (0) 2023.11.19
11. Custom provider  (1) 2023.11.19
10. Custom decorators  (0) 2023.11.19
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유