NestJS 帶你飛! 시리즈 번역 15# Dynamic Module

2023. 6. 9. 14:42개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.

 

https://docs.nestjs.com/fundamentals/dynamic-modules

 

이전의 포스팅에서 Module의 기본 사용 방법에 대해 소개한 적이 있습니다.

그런데 미처 소개하지 못했던 강력한 기능이 있습니다. 바로 동적 모듈(Dynamic Module, 動態模組)인데요.

오늘은 이 동적 모듈에 대해 이야기 하고자 합니다.

다이나믹 모듈은 아주 쉽게 Provider의 내용을 원하는대로 커스터마이징 할 수 있습니다.

Module의 Provider를 동적화 한다.
무슨 말일까요? 간단하게 말하면 Module을 외부에서 입력된 파라미터로 Provider의 내용을 설정하는 것입니다.

일반 정적 모듈(Static Module, 靜態模組)과의 다른점은 정적 모듈은 Provider가 생성된 후 만들어집니다.

Provider 관련 설정을 변경 하려면 해당 Module 내부의 코드를 수정해야 하는데요.

동적 모듈은 변경하고자 하는 부분을 파라미터화 하여 사용자가 해당 Module을 사용하고자 할 경우
정적 메서드를 통하여 해당 파라미터를 전달하여 Provider가 해당 파라미터로 Module이 생성되도록 합니다.

생활에 빗대어 설명해보자면 정적 모듈은 전용 리모콘과 비슷하다고 볼 수 있습니다.

내부의 규격/부품들을 개조하지 않는 한 이 리모콘은 정해진 장비에서만 사용이 가능합니다.

동적 모듈은 만능 리모콘과 비슷하다고 볼 수 있습니다.

조종할 수 있는 리모콘이라는 점에선 같지만 이는 다른 장치를 제어할 수 있다는 점이 다릅니다.

 

Dynamic Module 설계

동적 모듈은 매우 자주 쓰이는 기능중에 하나입니다. 가장 많이 만나게 될 상황은 바로 환경변수 관리인데요.

환경변수를 관리를 담당하는 Module을 하나 생성 해보겠습니다.

환경변수를 관리해야 하는 상황은 동적 모듈을 사용하기에 가장 적합한 예라고 할수 있습니다.

환경 변수를 관리하는 로직은 일반적으로 만든 후에는 불변에 가깝습니다.

변화를 주게 될 부분이라곤 환경변수의 파일 경로 등.. 밖에 없을것입니다.

동적 모듈을 사용하여 공용 컴포넌트로 분리하여 결합도를 낮출 수 있습니다.

주의: 환경 변수에 대한 소개는 다음 포스팅에서 자세히 설명하겠습니다.

이번 포스팅에서는 동적 모듈과 dotenv를 통해 간단히 환경변수를 관리하는 모듈을 만들어 보겠습니다.

이름은 ConfigurationModule로 정하겠습니다.

주의: dotenv란 환경 변수를 관리하는 패키지중 하나입니다. 자세한 내용은 공식 사이트를 참고해주세요.

목표는 이 ConfigurationModule로 정적 메서드 forRoot을 제공하여 key값은 path의 객체 파라미터를 받을 수 있도록 하는것입니다.

path는 .env파일의 상대 경로입니다. 이 forRoot을 통해 파라미터를 ConfigurationModule에 전달하여 .env 파일을 처리하고 파싱된 변수들을 관리합니다. 먼저 npm을 통해 dotenv를 설치해봅시다.

$ npm install dotenv --save

CLI를 통해 ConfigurationModuleConfigurationService를 생성해보겠습니다.

$ nest generate module common/configuration
$ nest generate service common/configuration

configuration.module.ts를 열어보면 ConfigurationModuleforRoot이라는 정적 메서드가 하나 붙어있습니다.

반환값은 DynamicModule인데 이 DynamicModule이 하나의 객체입니다. @Module 데코레이터의 내용과 대개 일치하는데

다른점은 반드시 module 파라미터에 붙여야 한다는것이며 그 값은 ConfigurationModule 자체입니다. 

그밖에 global 파라미터는 생성된 Module을 전역으로 변경합니다.

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigurationService } from './configuration.service';

@Module({})
export class ConfigurationModule {

  static forRoot(): DynamicModule {
    return {
      providers: [
        ConfigurationService
      ],
      module: ConfigurationModule,
      global: true
    };
  }

}
주의: 정적 메서드는 개발자가 스스로 설계하여도 됩니다.
하지만 반환값은 반드시 동기 혹은 비동기 DynamicModule이여야 합니다.
이름은 일반적으로 forRootregister를 사용합니다.

 

위의 코드를 보면 @Module의 파라미터가 비어있는걸 확인할 수 있습니다. 왜 비어있을까요?

바로 우리가 동적 모듈만을 사용하기 때문에 특별히 정적 모듈의 부분을 작성하지 않아도 되는것입니다.

하지만 작성하고자 한다면 작성은 가능하긴 합니다.

 

이어서 forRoot에서 path라는 key 값을 포함하는 객체 파라미터를 만들어야 하고 해당 path를 추출하여 Value Provider를 사용하여 해당 값을 기록해 보겠습니다.

먼저 configuration의 폴더 아래 constants 폴더를 만들고 안에 token.const.ts을 생성해 token을 관리하게 하도록 하겠습니다.

export const ENV_PATH = 'ENV_PATH';

configuration.module.ts를 수정해봅시다.

import { DynamicModule, Module } from '@nestjs/common';
import { ConfigurationService } from './configuration.service';
import { ENV_PATH } from './constants/token.const';

@Module({})
export class ConfigurationModule {

  static forRoot(options: { path: string }): DynamicModule {
    return {
      providers: [
        {
          provide: ENV_PATH,
          useValue: options.path
        },
        ConfigurationService
      ],
      exports: [
        ConfigurationService
      ],
      module: ConfigurationModule,
      global: true
    };
  }

}

마지막으로 ConfigurationService를 작성해주면 됩니다.

constructor를 통해 방금 작성했던 환경변수 경로 ENV_PATH를 가져오고 setEnvironment.env 파일을 읽고 해석할 수 있도록 해준 후 config 속성 안에 집어넣습니다.

마지막으로 get(key: string) 메서드를 통해 사용하고자 하는 환경 변수를 불러오면 됩니다.

import { Inject, Injectable } from '@nestjs/common';

import * as fs from 'fs';
import * as path from 'path';
import * as dotenv from 'dotenv';

import { ENV_PATH } from './constants/token.const';

@Injectable()
export class ConfigurationService {

  private config: any;

  constructor(
    @Inject(ENV_PATH) private readonly path: string
  ) {
    this.setEnvironment();
  }

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

  private setEnvironment(): void {
    const filePath = path.resolve(__dirname, '../../', this.path);
    this.config = dotenv.parse(fs.readFileSync(filePath));
  }

}

 

Dynamic Module 사용

ConfigurationModule의 작성이 끝났으면 먼저 프로젝트 경로 아래 development.env 파일을 생성하여 아래의 내용을 자유롭게 작성해봅시다.

USERNAME=WOO
주의: 프로젝트 바로 아래 경로에 생성합니다. src가 아닌 package.json과 같은 폴더 경로입니다. 

다음으로 app.module.ts의 내용을 수정합니다.

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

@Module({
  imports: [
    ConfigurationModule.forRoot({ 
      path: `../${process.env.NODE_ENV || 'development'}.env`
    })
  ],
  controllers: [
    AppController
  ],
  providers: [
    AppService
  ]
})
export class AppModule {
}

app.controller.ts의 내용을 수정하여 constructorConfigurationServce를 주입하고 getHello의 반환값을 수정합니다.

import { Controller, Get } from '@nestjs/common';
import { ConfigurationService } from './common/configuration/configuration.service';

@Controller()
export class AppController {
  constructor(
    private readonly configService: ConfigurationService
  ) {
  }

  @Get()
  getHello() {
    return { username: this.configService.get('USERNAME') };
  }
}

http://127.0.0.1:3000에 접속해보면 USERNAME의 값을 불러올 수 있는걸 확인할 수 있습니다.

 

마치며

Dynamic Module은 엄청 실용적인 기능중 하나입니다.
데이터베이스, 환경변수 관리 등의 기능을 사용하기 위해 자주 사용됩니다.

그러나 이 Dynamic Module을 사용하기 위해선 Nest의 의존성 주입 원리에 대해 어느정도 이해가 있어야 하는데요.

어느정도 이해가 되어야만 이를 사용하는데 문제가 없을것입니다.

아래는 오늘의 학습 요약본입니다.

1. Dynamic Module은 정적 메소드를 사용하여 DynamicModule 유형의 객체를 반환하는 방식입니다.

2. Dynamic Module을 적절히 활용하여 공유 컴포넌트를 분리합니다.

3. DynamicModulemodule 파라미터를 반드시 포함해야 합니다.

4. 정적 메서드의 이름은 일반적으로 forRoot, register 라고 짓습니다.