NestJS Logo

원문을 읽으며 정리합니다.

Table of Contents

  1. Modules

Modules

Module은 @Module() 데코레이터가 붙은 클래스이다. 이 데코레이터는 NestJS가 애플리케이션 구조를 조직할 수 있는 메타데이터를 제공해준다.

Modules

Image by: https://docs.nestjs.com/assets/Modules_1.png

각 애플리케이션은 적어도 하나의 모듈, root module을 갖는다. 루트 모듈은 NestJS가 애플리케이션 그래프(모듈, 프로바이더 관계 및 의존성을 결정짓기 위해 사용하는 내부 데이터 구조)를 빌드할때 사용하는 시작점 이다. 이례적으로 아주 작은 애플리케이션은 루트 모듈만 갖을 수 있다. 하지만 NestJS는 이러한 Module을 여러개로 구성하여 컴포넌트를 잘 조직 함으로써 역할별로 나뉜 훌륭한 아키텍처를 구성할 것을 강력하게 조언하곤 한다.

@Module()은 객체 하나를 요구하는데, 객체가 갖는 옵션은 다음과 같다.

providers NestJS 주입자에 의해 인스턴스화 될 Providers, 이들은 이 모듈 안에서 공유될 지도 모른다.
controllers 인스턴스화 되어야할 컨트롤러의 집합을 이 모듈에 정의한다.
imports import 된 모듈의 리스트다. 만약 이 모듈에서 import한 모듈안에 정의된 provider를 사용해야 한다면 import한 모듈은 그 provider를 exports 옵션으로 넣어줘야 한다.
exports providers의 하위 집합으로 이 모듈에 선언된 Provider의 일부를 export할 수 있다. 이는 다른 모듈에서 이 모듈을 import 할때 사용 가능하다.

Module은 자연스럽게 Providers를 캡슐화 한다. 즉, Providers(현재 모둘의 부분도 아니고 import 된 모듈로부터 export된 것도 아닌)를 주입하는것은 불가능하다. 그러므로, 모듈에서 export한 provider를 모듈의 public interface 또는 api로 간주할 수 있다.

이 부분은 해석이 영 안된다...😭

Feature modules

예제에서 CatsControllerCatsService는 같은 애플리케이션 도메인에 속한다. 따라서 feature module로 묶기에 타당하다. feature module은 간단하게 특정 기능과 코드를 관련있게 조직할 수 있다. 또, 코드를 명백한 경계로 유지할 수 있다. 이는 SOLID원칙과 함께 개발을 할때 복잡성을 줄여주고 애플리케이션의 크기와 팀의 성장을 도와준다.

CatsModule을 만들어 보자.

$ nest g module cats
// src/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 {}

이 모듈과 관련된 파일을 모두 cats 폴더에 모을 수 있다. 추가로 해야할 작업은 cats 모듈을 root module(src/app.module.ts file)에 import 해줘야 한다.

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

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

이제 프로젝트 구조는 아래와 같다.

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

Shared modules

NestJS에서 Module은 디폴트로 싱글톤이다. 따라서, 어느 Provider 인스턴스든 여러 모듈 사이에서 쉽게 공유할 수 있다.

Shared modules

Image by: https://docs.nestjs.com/assets/Shared_Module_1.png

모든 모듈은 자동적으로 shared module이다. 생성되자마자 모든 모듈에서 재사용 할 수 있다. 만약 CatsService를 다른 모듈에서 사용해야 한다고 하자. 그렇다면 우리는 CatsService Provier를 module의 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을 import 하기만 하면 CatsService를 사용할 수 있다.

Module re-exporting

위에서 본 것 처럼, 모듈들은 내부 Providers를 export 할 수 있다. 게다가, 모듈은 import한 것을 re-export 할 수 있다. 아래 예제처럼, CommonModuleCoreModule에서 import 뿐만 아니라 export 까지 한다. 이렇게 함으로써 다른 모듈에서는 CoreModule을 import하면 CommonModule 또한 이용 가능하다.

Dependency injection

Module 클래스는 provider를 주입할 수도 있다.

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

그러나 module 클래스는 그 자체로는 inject 될 수 없다. 순환 참조 문제 떄문이다.

Global modules

만약 같은 모듈을 모든 곳에서 import 해야한다면 굉장히 tedious(지루한)할 것이다. 그래서 NestJS에서는 Global Scope에 등록할 수 있다.

NestJS는 모듈 스코프 안에서 프로바이더를 캡슐화 한다. 그래서 캡슐화된 모듈을 처음 import하기 전까진 그 모듈의 프로바이더를 사용할 수 없다.

어디서든지 이용가능한 provider의 집합을 원한다면 @Global() 데코레이터를 붙여 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 {}

@Global() 데코레이터는 모듈을 global-scoped로 만들어 준다. Global module은 하나만 등록될 수 있다(app module, cats module에 둘다 데코레이터를 붙여봤는데 에러가 안난다. 한번만이 아닌가?). 일반적으로 root 또는 core 모듈에 등록한다. 위 예시에서는 CatsService Provider가 유비쿼터스 하고 모듈들이 inject되기를 바래서 CatsModule을 import할 필요가 없게 만들었다.

공식 문서에 따르면, 모든 것을 글로벌로 만드는 것은 좋은 결정이 아니라고 한다. Global module은 많은 boilerplate를 줄이기 위해 이용 가능하다. 모듈의 api를 consumers(??? 다른 모듈을 말하는 건가?)가 이용가능하게 만들기 위해 imports룰 취하는 방법이 일반적이다.

Dynamic modules

NestJS 모듈 시스템은 Dynamic modules라고 불리는 강력한 기능을 제공한다. 이 기능은 쉽게 커스텀 가능한 모듈을 만들 수 있게 해주는데, 이 모듈은 동적으로 등록 및 providers를 설정 가능케 한다. Dynamic modules은 여기서 더 알아볼 수 있다. 이번 글에선 간단한 예시를 보여준다.

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() 메서드에 넘어온 entitiesoptions에 의존해 새로운 Provider를 만든다(예를 들어 repositories라 치자.). dynamic module에 리턴되는 프로퍼티들은 override되는 것이 아니라 @Module()안 기본 모듈에 정의된 메타데이터를 확장한다. 이렇게 정적으로 선언된 Connection Provider와 동적으로 생성된 repository Provider가 이 모듈에서 export 된다.

만약 dynamic module을 글로벌 스코프로 등록하고 싶다면 리턴할 때 global 프로퍼티를 true로 설정하면 된다.

{
  global: true,
  module: DatabaseModule,
  providers: providers,
  exports: providers,
}

DatabaseModule 은 아래 방식으로 import 되고 configure 될 수 있다.

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-export하고 싶다면 forRoot() 호출 부분만 omit(빠트리고)하고 exports 배열에 넣어주면 된다.

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

dynamic modules에서 더 자세히 다루고 예제를 포함한다.

Next NestJS OVERVIEW(5) - Middleware
Intro NestJS OVERVIEW(0) - Intro