Skip to content

4. Modules


모듈은 @Module() 데코레이터로 주석이 달린 클래스이다. @Module() 데코레이터는 Nest가 애플리케이션 구조를 구성하는 데 사용하는 메타데이터를 제공한다.


001


각 애플리케이션에는 하나 이상의 모듈(루트 모듈)이 있다. 루트 모듈은 Nest가 애플리케이션 그래프를 빌드하는 데 사용하는 시작점이다. Nest가 모듈과 프로바이더 관계 및 종속성을 리졸브하는 데 사용하는 내부 데이터 구조이다. 매우 작은 애플리케이션에는 이론적으로 루트 모듈만 있을 수 있지만 일반적인 경우는 아니다. 컴포넌트를 구성하는 효과적인 방법으로 모듈을 사용하는 것이 좋다. 따라서 대부분의 애플리케이션에서 최종 아키텍처는 각각 밀접하게 관련된 기능 집합을 캡슐화하는 여러 모듈을 사용한다.


@Module() 데코레이터는 모듈을 나타내는 프로퍼티를 가진 단일 객체를 사용한다.


프로퍼티 설명
providers Nest 인젝터에 의해 인스턴스화되고 해당 모듈에서 공유될 수 있는 프로바이더 목록
controllers 해당 모듈에서 인스턴스화해야 하는 컨트롤러 목록
imports 해당 모듈에 필요한 다른 모듈 목록
exports 다른 모듈에 내보내는 프로바이더 목록


모듈은 기본적으로 프로바이더를 캡슐화한다. 즉, 현재 모듈의 일부가 아니거나 가져온 모듈에서 내보내지 않은 프로바이더를 주입할 수 없다.


1. Feature modules

CatsControllerCatsService는 동일한 애플리케이션 도메인에 속한다. 밀접하게 관련되어 있으므로 기능 모듈로 이동하는 것이 좋다. 기능 모듈은 단순히 특정 기능과 관련된 코드를 구성하여 명확한 경계를 설정한다. 이는 특히 애플리케이션의 규모가 커짐에 따라 복잡성을 관리하고 SOLID 원칙으로 개발하는 데 도움이 된다.


이를 보여주기 위해 CatsModule을 생성한다.


cats/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 {}


Note

CLI를 사용하여 모듈을 생성하려면 $ nest g module cats 명령을 실행하면 된다.


위에서 cats.module.ts 파일에 CatsModule을 정의하고 이 모듈과 관련된 모든 것을 cats 디렉터리로 옮겼다. 마지막으로 해야할 일은 이 모듈을 루트 모듈(app.module.ts 파일에 정의된 AppModule)로 가져오는 것이다.


app.module.ts
import { Module } from "@nestjs/common";
import { CatsModule } from "./cats/cats.module";

@Module({
  imports: [CatsModule],
})
export class AppModule {}


다음은 디렉터리 구조가 현재 어떻게 보이는지 나타낸다.


src
├── app.module.ts
├── cats
│   ├── cats.controller.ts
│   ├── cats.module.ts
│   ├── cats.service.ts
│   ├── dto
│   │   └── create-cat.dto.ts
│   └── interfaces
│       └── cat.interface.ts
└── main.ts


2. Shared modules

Nest에서 모듈은 기본적으로 싱글톤이므로 여러 모듈 간에 동일한 프로바이더 인스턴스를 공유할 수 있다.


002


모든 모듈은 자동으로 공유 모듈이다. 일단 생성되면 모든 모듈에서 재사용할 수 있다. 여러 다른 모듈 간에 CatsService의 인스턴스를 공유하려고 한다고 가정해 보자. 그렇게 하려면 먼저 CatsService 프로바이더를 아래와 같이 모듈의 exports 배열에 추가하여 내보내야 한다.


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],
})
export class CatsModule {}


이제 CatsModule을 가져오는 모든 모듈은 CatsService에 액세스할 수 있으며 이를 가져오는 다른 모든 모듈과 동일한 인스턴스를 공유한다.


3. Module re-exporting

위에서 볼 수 있듯이 모듈은 내부 프로바이더를 내보낼 수 있다. 또한 가져온 모듈을 다시 내보낼 수도 있다. 아래 예제에서 CommonModuleCoreModule로 가져오고, 다시 내보내어 이 모듈을 가져오는 다른 모듈에서 사용할 수 있다.


@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}


4. Dependency injection

모듈 클래스는 프로바이더도 주입할 수 있다.


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) {}
}


그러나 순환 종속성으로 인해 모듈 클래스 자체를 프로바이더로 주입할 수는 없다.


5. Global modules

모든 모듈에서 동일한 특정 모듈을 가져와야 하는 경우 굉장히 번거롭다. Nest는 기본적으로 모듈 범위 내에서 프로바이더를 캡슐화하며, 캡슐화 모듈을 가져오지 않고는 모듈의 프로바이더를 다른 모듈에서 사용할 수 없다.


이는 @Global() 데코레이터를 사용하여 모듈을 글로벌로 만들면 해결할 수 있다.


cats.module.ts
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 프로바이더를 삽입하려는 모듈은 imports 배열에서 CatsModule을 가져올 필요가 없다.


6. 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,
    };
  }
}


이 모듈은 기본적으로 Connection 프로바이더(@Module() 데코레이터 메타데이터)를 정의하지만 추가적으로 forRoot() 메서드에 전달된 엔터티 및 옵션 객체에 따라 프로바이더 모음을 노출한다. 동적 모듈은 @Module() 데코레이터에 정의된 기본 모듈 메타데이터를 확장하여 프로퍼티를 동적으로 반환한다.


글로벌 범위에 동적 모듈을 등록하려면 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 {}

References