이 시리즈는 Nest.js 공식 Document를 바탕으로 따라해보며 내용들을 요약 정리한 게시글입니다.
모듈은 @Module()
데코레이터로 주석이 달린 클래스이다. @Module()
데코레이터는 Nest가 어플리케이션 구조를 구성하는데 사용하는 메타데이터를 제공한다.
각 어플리케이션에는 루트모듈이라는 하나 이상의 모듈이 있다. 루트 모듈은 Nest가 어플리케이션 그래프를 빌드하는데 사용하는 시작점이다. Nest가 모듈과 Provider
관계 및 종속성을 해결하는데 사용하는 내부 데이터 구조이다. 매우 작은 어플리케이션에는 이론적으로 루트 모듈만 있을 수 있지만 일반적인 경우는 아니다. 모듈은 구성요소를 구성하는 효과적인 방법으로 적극 권장된다는 점을 강조하고 싶다. 그러므로 대부분의 어플리케이션에서 결과 아키텍처는 각각 밀접하게 관련된 기능 집합을 캡슐화하는 여러 모듈을 사용한다.
@Module()
데코레이터는 속성이 모듈을 설명하는 단일 객체를 사용한다.
Providers : Nest인젝터에 의해 인스턴스화 되고 적어도 이 모듈에서 공유될 수 있는 provider
controllers : 인스턴화 되어야 하는 이 모듈에 정의된 컨트롤러 세트
imports : 이 모듈에 필요한 프로바이더를 내보내는 가져온 모듈 목록
exports : 이 모듈에서 제공하고 이 모듈을 임포트하는 다른 모듈에서 사용할 수 있어야하는 Provider의 하위 집합
모듈은 기본적으로 캡슐화한다. 현재 모듈에 직접 포함되거나 import 된 모듈에서 export되지 않는 provider
를 삽입할 수 없다. 따라서 모듈에서 내보낸 프로바이더를 모듈의 공용 인터페이스 또는 API로 간주할 수 있다.
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 {}
위 코드에서 우리는 cats.module.ts
파일에 CatsModule
을 정의하고, 이 모듈과 관련된 모든 것을 cats
디렉토리로 옮겼다. 마지막으로 해야할 일은 이 모듈을 루트 모듈(app.module.ts
파일에 정의된 AppModule
)로 가져오는 것 이다.
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
아래는 현재의 디렉토리 구조 이다.
Nest에서 모듈은 기본적으로 싱글톤이므로 여러 모듈간에 쉽게 프로바이더의 동일한 인스턴스를 공유할 수 있다.
모든 모듈은 자동으로 공유 모듈이다. 일단 생성되면 모든 모듈에서 재사용할 수 있다. 여러 다른 모듈간에 CatsService
의 인스턴스를 공유하고 싶다고 가정하자. 이렇게 하려면 먼저 아래와 같이 모듈의 exports
배열에 CatsService
프로바이더를 추가하여 exports 해야한다.
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
을 가져오는 모든 모듈은 CatsService
에 억세스할 수 있으며 이를 가져오는 다른 모든 모듈과 동일한 이스턴스를 공유한다.
위에서 볼 수 있듯이 모듈은 내부 프로바이더를 내보낼 수 있다. 또한 가져온 모듈을 다시 내보낼 수 있다. 아래 예제에서 CommonModule
은 CoreModule
로 import
되고 export
되므로 이 모듈을 가져오는 다른 모듈에서 사용할 수 있다.
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
모듈 클래스는 Provider
주입도 가능하다.
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) {}
}
그러나 모듈 클래스 자체는 순환 종속성으로 인해 프로바이더로 삽입될 수 없다.
모든 곳에서 동일한 모듈 집합을 가져와야 한다면 지루할 수 있따. Nest는 모듈 범위내에서 Provider
를 캡슐화 한다. 캡슐화 모듈을 먼저 가져오지 않으면 모듈의 Provider
를 다른 곳에서 사용할 수 없다.
즉시 사용할 수 있어야하는 프로바이더 집합을 제공하려면 @Globl()
데코레이터를 사용하여 전역모듈을 만든다.
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 provider
는 어디에나 있을 것이며, 서비스를 주입하려는 모듈은 import 배열에서 CastsModule
을 가져올 필요가 없다.
모든 것을 글로벌하게 만드는 것은 좋은 디자인이 아니다. 필요한 보일러플레이트의 양을 줄이기 위해 전역 모듈을 사용할 수 있다.
imports
배열은 일반적으로 소비자가 모듈의 API를 사요할 수 있도록 하는 방법이다.
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,
};
}
}
이 모듈은 기본적으로 Connection Provider
를 정의하지만 추가로 forRoot()
메서드에 전달된 entities
및 options
객체에 따라 저장소와 같은 프로바이더 컬렉션을 추가로 노출한다. 동적모듈이 반환하는 속성은 @Module
데코레이터에 정의된 기본 모듈 메타데이터를 재 정의하는 대신 확장한다. 이것이 정적으로 선언된 Connection Provider
와 동적으로 생성된 저장소 Provider를 모듈에서 export하는 방법이다.
전역범위에서 동적 모듈을 동록하려면 global
속성을 true
로 설정한다.
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}
DatabaseModule
은 다음과 같은 방식으로 가져오고 구성할 수 있다.
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 {}
다시 동적 모듈을 내보내려면 exports 배열에서 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 {}