모듈은 @Module()
데코레이터로 주석이 달린 클래스로 모든 모듈은 각각 선언되지만, 최종적으로 하나로 취합된다. 아래의 그림 또한 그렇다.
Feature Module1과 Feature Module2는 Orders Module로 병합되고,
Feature Module3은 Chat Module로,
그렇게 모인 Users Module, Orders Module, Chat Module은 마지막으로 Application Module로 병합이 되는 형태를 나타냈다.
루트 모듈(Root Modules)
모듈(Module) 특징
@Module()
데코레이터는 속성이 모듈을 설명하는 단일 객체를 사용한다.
속성 | |
---|---|
providers | Nest 인젝터에 의해 인스턴스화 되고 적어도 이 모듈에서 공유될 수 있는 프로바이더 |
controllers | 인스턴스화 되어야 하는 이 모듈에 정의된 컨트롤러 세트 |
imports | 이 모듈에 필요한 프로바이더를 내보내는 가져온 모듈 목록 |
exports | 이 모듈에서 제공하고 이 모듈을 임포트하는 다른 모듈에서 사용할 수 있어야 하는 프로바이더의 하위집합 |
CatsController
와 CatsService
는 동일한 애플리케이션 도메인에 속하며 밀접하게 관련되어 있으므로 기능 모듈(cats.module.ts
)로 이동하는 것이 좋다.
기능 모듈은 단순히 특정 기능과 관련된 코드를 구성하여 코드를 체계적으로 유지하고 명확한 경계를 설정한다.
/* cats/cats.module.ts */
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
/* 각각의 기능을 담당하는 Controller와 Providers는
* 기능 모듈 별로 Module Decorator에 등록됨
* App Module에 import하여 최종적으로 NestFactory 로 create되는 플로우를 가지고 있음
*/
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
위의 모듈을 루트 모듈(app.module.ts 파일에 정의된 AppModule)로 가져오는 것이다.
/* app.module.ts */
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
기본적으로 모듈은 싱글톤으로 여러 모듈간에 쉽게 프로바이더의 동일한 인스턴스를 공유할 수 있다.
즉, 모든 모듈은 자동으로 공유 모듈이며, 생성되면 모든 모듈에서 재사용 가능하다.
여러 다른 모듈간에 CatsService의 인스턴스를 공유하고 싶다고 가정해보자.
/* cats.module.ts */
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
/* CatsModule을 사용하는 모든 모듈은 CatsService에 액세서 가능하고,
* 이를 가져오는 다른 모든 모듈과 동일한 인스턴스를 공유한다.
*/
export class CatsModule {}
모듈은 내부 프로바이더를 내보낼 수 있으며, 가져온 모듈을 다시 내보낼 수도 있다.
/* CommonModule은 CoreModule로 가져오고 내보내므로
* 이 모듈을 가져오는 다른 모듈에서 사용 가능
*/
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
모듈 클래스는 프로바이더도 주입가능하다. (ex. 구성 목적)
하지만, 모듈 클래스 자체는 순환 종속성으로 인해 프로바이더로 삽입될 수 없다.
순환 종속성(Circular dependency)란?
두 클래스가 서로 의존할 때 발생하는 것으로 간단히 말해서클래스 A에는 클래스 B가 필요하고, 클래스 B에는 클래스 A가 필요한 경우를 말한다.
/* cats.module.ts */
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private catsService: CatsService) {}
}
즉시 사용할 수 있어야하는 프로바이더 집합(예: 헬퍼, 데이터베이스 연결 등)을 제공하려면 @Global()
데코레이터를 사용하여 전역 모듈을 만든다.
전역 모듈은 일반적으로 루트 또는 코어 모듈에서 한번만 등록해야 한다.
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
Nest 모듈 시스템에는 동적 모듈(Dynamic module)이라는 강력한 기능이 포함되어 있다.
동적 모듈을 사용하면 프로바이더를 동적으로 등록하고 구성할 수 있는 커스텀 가능한 모듈을 쉽게 만들 수 있다.
/* DatabaseModule 에 대한 동적 모듈 정의의 예 */
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
// 기본적으로 Connection 프로바이더를 정의함
providers: [Connection],
})
export class DatabaseModule {
// forRoot(): 메소드는 동기식 또는 비동기식으로 동적 모듈 반환 가능
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
전역범위에 동적 모듈을 등록하려면 global 속성을 true로 설정한다.
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}