- Published on
nestjs dynamic module 에 대해서
- Authors
- Name
- 길재훈
Dynamic Module
에 대해서 알아보자
Module
은 말그대로 그룹을 형성해주는 역할을 한다 내부에는 Controller
와 Providers
를 받아서 자신만의 실행 컨텍스트를 만든다고 보면 된다
만약 Module
내부에서 Service logic
을 exports
하지 않는다면 encapsulate
되기에, 해당 Module
범위에서만 사용되도록 한정시킨다.
이러한 방식을 nestjs
에서는 static module binding
이라고 한다 만약 exports
에 Service
가 포함되어 있으며, Module
을 imports
한다면 다음과 같이 작동한다.
가져오는 Module
을 사용하는 Module
에서 가져와 모든 종속성을 해결하고 Module
을 Instace
화 시킨다
그리고 사용하는 Module
에서 가져온 Module
이 내보낸 Providers
를 구성요소에서 사용할 수 있도록 하며, 사용하는 Module
을 Instance
화 한다.
그런다음, 사용해야할 Service
를 주입시키는 방식으로 이루어진다
Docs
에서는 사용하는 Module
을 Host Module
이라 칭하고, 사용되는 모듈을 Consuming Module
이라 명한다
Dynamic Module 을 사용하는 이유
Module
을 사용자 입맛대로 동적으로 생성이 가능하다
이전의 Module
export
및 import
방식은 consuming module
에 의해 항상 정의되고 만들어져야 한다는 점이다
이렇게 정적으로 제공되어지기 보다는, 사용자가 해당 모듈을 가져올때 원하는 방식의 API
를 만들어, Module
로 받아올 수 있다면 정말 편할것이다.
Dynamic Module
은 consuming module
에서 API
를 구성하여 Host Module
로 가져올때, 사용자가 정의한 방법에 따라 동적으로 모듈을 구성하는 방법이다
Docs
에서는 ConfigModule
을 사용할때, 사용자가 정의한 옵션에 맞추어 Module
을 가져오는것을 보여준다
// 정적으로 module 을 가져오는 로직
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 {}
// 동적으로 module 을 가져오는 로직
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 {}
위의 로직을 살펴보면 Dynamic module
은 register static method
를 사용하여 객체로 인자를 전달하고 있다
보통 이렇게 Dynamic module
로 만들어지면, static method
를 통해 forRoot
혹은 register
를 만들어 사용하는것이 Convention
이라고 한다
위의 로직은 흥미롭게도, Module
의 static method
에 인자로 객체를 보낸다. 그러면 이 Module
는 제공하는 Service
에 해당 인자의 객체를 제공하여, 설정한 객체에 맞는 Service
를 주입해준다
일단 register()
가 반환하는 DynamicModule
은 runtime
에 생성된 모듈에 불과하다.
모듈을 간단하게 아무렇게나 빠르게 선언하면 다음과 같이 선언도 가능하다
@Module({
imports: [DogsModule],
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
실제로, 우리가 여태껏 작성한 Module
역시 동일하게 작성해서 처리한다 그냥 객체에 Module
에 사용할 API
에 맞춰서 값을 넣은것 뿐이다.
Dynamic Module
역시 동일한 인터페이스 와 함께 module
이라고 불리는 property
를 추가한 형태를 가진다
이 module property
는 Dynamic module
의 이름을 정의해주는 property
로 생각하면 된다
이제, register
가 어떻게 작동하는지 확인하는것이 좋을 듯 하다
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],
};
}
위는 Docs
에서 제시해주는 예시이다. 이 예를 보면 ConfigModule
의 register
가 어떻게 생겼는지 볼 수 있다 정말 간단하게도, 일반 모듈과 같지만, module
프로퍼티가 존재하는것이 보인다.
이제 이 register
에 인자를 넣으면 다음처럼 만들 수 있다
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],
}
}
}
굉장히 흥미로운 상황이다. 여기서 인자값으로 받은 값을 받는데, 위의 Record Utility
는 다음의 type
으로 만든다.
static register(options: { [key: string]: any }): DynamicModule {
즉, 어떠한 값이라도 받을수 있는 options
객체를 뜻하지만, 실제로 Interface
로 만들어 options
를 지정하는것이 더 현명한 방법이다.
위는 Docs
에서 보여주기식으로 만든 코드라는점 알아두자 즉, 어떠한 값이라도 받을수 있는 options
객체를 뜻하지만, 실제로 Interface
로 만들어 options
를 지정하는것이 더 현명한 방법이다.
위는 Docs
에서 보여주기식으로 만든 코드라는점 알아두자
일단, 위의 로직은 custom Provider
를 사용하여 새로운 Servide Logic
을 만든다.
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
ConfigService,
],
위 로직은 간단하게, value
값이 options
이며, Provider token
은 CONFIG_OPTIONS
라는것을 뜻한다
이렇게, 주입된 Provider
를 어떻게 커스텀하게 사용할까? 만약 DI
에 대한 이해가 있다면 쉽게 유추가능하다.
바로, Service
로직인 configService
에서 주입받아서 처리하면, 동적으로 설정에 따라 Logic
변경이 가능하다.
ConfigService
를 보도록 하자
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]
}
}
위 로직을 보면, Custom Provider
를 제공하기 위해, @inject
를 사용하여 Constructor
에서 주입받는것을 볼 수 있다.
이제 이 Service
로직은 Host Module
에서 Dynamic module
에 넣은 options
주입받아서 동적으로 처리된다.
굉장히 멋진 방법으로 Module
을 조작한다. Dynamic Module
에 대해서 알아보았다.
Docs
에는 Dynamic Module
을 build
할수 있는 새로운 API
를 제시하지만 이부분은 조금씩 차근차근 공부해 나가볼 예정이다.