Nestjs 공식문서를 번역한 내용입니다. 일부 오역, 의미 전달이 모호한 부분이 있을 수 있습니다.
모듈은 @Module()
데코레이터가 달려 있는 클래스입니다. @Module
데코레이터는 Nest
가 애플리케이션의 구조를 구성하는데 사용하는 메타데이터를 제공합니다.
각 애플리케이션은 적어도 한 개의 루트 모듈을 가집니다. 루트 모듈은 Nest
가 애플리케이션 그래프(Nest
가 모듈, 프로바이더 관계, 종속성 등을 정의(resolve)하기 위해 사용하는 내부 데이터 구조)를 빌드하기 위해서 사용하는 시작지점입니다. 매우 작은 애플리케이션은 이론적으로 루트 모듈만 가질수도 있지만, 이것은 일반적인 경우는 아닙니다. 컴포넌트들을 구성하는 효과적인 방법으로 모듈을 사용하길 강력하게 권장합니다. 그래서 대부분의 애플리케이션들처럼, 유사하고 관련된 기능들을 단위로 가지는 여러 모듈을 사용하는 아키텍처가 됩니다.
@Module()
데코레이터는 모듈을 정의하는 속성들로 구성된 객체를 가지고 있습니다.
providers
:Nest
주입기(injector)에 의해서 인스턴스화 될 것이고, 본 모듈에서 공유되어 사용될 프로바이더들입니다.controllers
: 이 모듈에서 인스턴스화 될 컨트롤러의 집합입니다.imports
: 이 모듈에서 필요한 프로바이더를 export하는 모듈의 목록입니다.exports
: 이 모듈에서 제공하는 프로바이더들 중 이 모듈을 import하는 다른 모듈에서 사용할 수 있어야하는 프로바이더들의 목록 입니다. 프로바이더 자체나 그것의 토큰(provide value)를 사용할 수 있습니다.
모듈은 기본적으로 프로바이더들을 캡슐화합니다. 즉, 현재 모듈에 직접적으로 속하지 않거나 import된 모듈에서 export되지 않은 프로바이더들을 주입하여 사용할 수 없습니다. 따라서 다른 모듈에서 export되는 프로바이더들은 그 모듈의 public 인터페이스 혹은 API라고 볼 수 있습니다.
기능 모듈(Feature modules)
CatsController
와 CatsService
는 같은 애플리케이션 도메인에 속해 있습니다. 밀접하게 관련되어 있기 때문에, 기능 모듈에 함께 속하는 것이 적당합니다. 기능 모듈은 특정한 기능과 관련된 코드들을 정리할 수 있게 하고, 명확한 경계를 만들 수 있도록 코드를 구성합니다. 이것은 애플리케이션이나 팀이 커질수록 복잡성을 관리할 수 있도록 도와주고, SOLID 원칙과 함께 개발할 수 있도록 해줍니다.
간략히 살펴보기 위해 CatsModule
을 만들어 봅시다.
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
CLI를 사용하여 모듈을 만들려면,
$ nest g module cats
명령어를 실행하세요.
예시에서 우리는 CatsModule
을 cats.module.ts
파일에 정의하고 모듈과 관련된 모든 것을 cats
디렉토리로 옮겼습니다. 그리고 마지막으로 가장 중요한 것은 루트 모듈(app.module.ts
파일 안에 있는 AppModule
)안에 이 모듈을 import하도록 설정하는 것입니다.
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
현재 디렉토리 구조는 아래와 같습니다:
공유 모듈(Shared module)
Nest
에서 기본적으로 모듈은 싱글톤입니다. 따라서 여러 모듈간에 동일한 프로바이더의 인스턴스를 쉽게 공유할 수 있습니다.
모든 모듈은 자동으로 공유 모듈이 됩니다. 한번 생성하면 다른 모듈에서 재사용될 수 있습니다. CatsService
의 인스턴스를 여러 다른 모듈에서 공유하기를 원한다고 가정해봅시다. 이것을 위해서 우리는 CatsService
프로바이더를 모듈의 exports
배열에 추가해서 export 해야 합니다.
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
이제 CatsModule
을 import하는 모듈은 CatsService
에 접근할 수 있고, 이 모듈을 import하는 다른 모든 모듈에 동일한 인스턴스를 공유할 것입니다.
모듈 다시 내보내기(Module re-exporting)
위에서 본 바와 같이 모듈은 자신의 내부 프로바이더들을 내보낼(export) 수 있습니다. 그리고 모듈은 그 모듈이 import한 모듈을 다시 내보내기(re-exporting) 할 수 있습니다. 아래의 예시와 같이 CommonModule
은 CoreModule
에 의해서 이 모듈 하나를 import하는 다른 모듈이 사용할 수 있도록 import되고, export됩니다.
의존성 주입(Dependency injection)
모듈 클래스 또한 프로바이더를 주입할 수 있습니다.
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 Module)
같은 모듈을 여러 곳에서 import 해야한다면 지루할 수 있습니다. Nest와 달리 Angular 프로바이더는 전역 범위(global scope)로 등록됩니다. 한번 정의되면 어디서나 사용될 수 있습니다. 하지만 Nest는 프로바이더를 모듈 범위(module scope)안에 캡슐화 합니다. 캡슐화된 모듈을 import하지 않고는 모듈의 프로바이더를 사용할 수 없습니다.
아무곳에서나 사용 되어야하는 프로바이더를 제공하려면 @Globla()
데코레이터를 이용해서 모듈을 전역으로 만들면 됩니다.
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 {}
@Global()
데코레이터는 모듈을 전역 범위로 만듭니다. 글로벌 모듈은 일반적으로 루트 모듈이나 코어 모듈에 의해서 단 한번만 등록되어야 합니다. 위의 예에서 CatsService
프로바이더는 어디에나 있을 것이고, 서비스를 주입하고자하는 모듈은 CatsModule
을 `imports' 배열에 import 할 필요가 없습니다.
모든 것을 전역으로 만드는 것은 좋은 디자인 결정이 아닙니다. 전역 모듈은 필요한 코드의 양을 줄이기 위해서 사용할 수 있습니다.
imports
배열은 모듈의 API를 사용자에게 사용할 수 있도록 만드는 우선되는 방법입니다.
동적 모듈(Dynamic modules)
Nest의 모듈 시스템은 동적 모듈이라는 강력한 기능을 가지고 있습니다. 이것은 동적으로 프로바이더를 등록하고 설정할 수 있는 커스텀 모듈을 쉽게 만들 수 있도록 해줍니다. 동적 모듈은 이곳에서 더 깊게 다룹니다. 이 챕터에서는 모듈을 간단히 다루기 위해서 개요만 소개 합니다.
아래의 예시가 DatabaseModule
을 위한 동적 모듈입니다.
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
forRoot()
메소드는 동기 또는 비동기 방식으로 동적 모듈을 반환할 수 있습니다.
이 모듈은 기본적으로 Connection
프로바이더를 정의합니다.(@Module()
데코레이터 메타데이터 안에), 하지만 추가로 - forRoot()
메소드에 전달되는 entities
와 options
객체에 따라서 - 예를 들면 레포지토리 같은 프로바이더 컬렉션(collection)을 노출 합니다. (역: 내부에 createDatabaseProviders
의 결과에 따라 제공되는 프로바이더가 달라짐) 동적모듈에 의해서 반환된 속성들은 @Module()
데코레이터에 정의된 기본 모듈 메타데이터를 확장합니다. 그래서 정적으로 선언된 Connection
프로바이더와 동적으로 생성된 레포지토리 프로바이더가 모듈로부터 내보내 집니다.
동적 모듈을 전역 범위로 사용하고 싶다면 global
속성을 true
로 설정하면 됩니다.
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}
다시 언급하자면 모든 것을 전역으로 만드는 것은 디자인(설계) 관점에서 좋지 않은 결정입니다.
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
동적 모듈을 다시 내보내기(re-exporting)하고 싶다면, export 배열에 forRoot()
메소드를 제외하고 설정하면 됩니다.
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule],
})
export class AppModule {}