Modules

Module은 @Module() 의 주석이 달린 class이다. @Module() 은 Nest에게 application의 구조를 조직하는데 필요한 metadata를 제공한다.


@Module() : NestJS 어플리케이션의 구조를 정의하는 데 사용된다. 모듈은 특정 기능을 캡슐화하여 애플리케이션을 더 구조적으로 만들고, 유지 보수성을 높여준다. 


각 application은 적어도 한 개의 모듈, 즉 root module을 가진다.
root module은 application graph(Nest가 module을 resolve하고 또 provider간의 관계와 의존성을 해결하기 위해 사용하는 internal data structure)를 build 하기위한 Nest의 시작점이다. 이론적으로 매우 작은 application들은 단지 하나의 root module만을 가지지만, 일반적인 경우는 아니다. module은 component를 조직하는 효과적인 방법으로 강력히 권장된다. 즉, 대부분의 application에서 최종 아키텍처는 여러 모듈을 사용하여 구성된다. 각 모듈은 밀접하게 관련된 기능 집합을 캡슐화한다.


@Module() decorator은 한 개의 object를 가지고, object의 속성들은 module을 설명해준다.


properties describe
providers Nest injector에 의해 인스턴스화된 providers이다. 이들은 적어도 이 module 내에서는 공유된다.
controllers 이 module 내에서 정의되고 인스턴스화된 controllers의 집합
imports 이 module에서 사용할 import된, 그리고 다른 module에서 export된 provider들의 리스트
exports providers의 subset이다. 이 module 내에서 정의되었지만 다른 module에서 사용할 필요가 있는 provider들이다. 이를 통해 모듈 간의 의존성을 관리하고, 재사용성을 높일 수 있다.

module은 provider들을 기본적으로 캡슐화한다. NestJs의 모듈 시스템에서는 provider가 module 내에서 선언되고, 다른 module에서 사용하려면 export되어야 한다. 이는 module 간의 의존성을 명확하게 관리하고, 의도하지 않은 의존성 주입을 방지한다.



Feature modules


CatsControllerCatsService는 같은 application domain에 속한다. 둘은 긴밀히 관련되어 있기에, 이를 하나의 기능 모듈(feature module)로 이동하는 것이 합리적이다. feature module은 관련된 기능을 가진 code들을 묶고, 유지하고 명확한 경계를 세워준다. 이러한 방식은 복잡함을 해소하고 SOLID원칙을 고수하게 해준다. 특히 application이 커지고, team이 성장할때 더 중요해진다.


아래는 CatsModule이다.


import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

<팁!>
$ nest g module cats를 통해 간단히 만들 수 있다.


위에서 간단히 **cats.module.ts**안에서 **CatsModule**을 만들 수 있다. 그리고 관련된 파일들을 **cats** 디렉터리로 옮긴다. 마지막으로 해야할 것은 이 module을 root module에서 import 해온다.


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.controller.ts
        cats.module.ts
        cats.service.ts
    app.module.ts
    main.ts


Shared modules


NestJS에서 모듈은 기본적으로 SingleTon이다. 즉, 어플리케이션 내에서 모듈이 한 번만 인스턴스화되며, 이로 인해 여러 모듈 간에 동일한 provider instance를 쉽게 공유할 수 있다.



모든 module은 자동적으로 shared module이다. 한번 만들어두면 다른 module에서 재 사용 가능하다. 만일 CatsService의 instance를 다른 module 사이에서 공유하고 싶다고하자. 이를 위해서는 CatsService providerexport에 추가해야 한다.


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

이를 통해 다른 module에서 CatsModule를 import해서 CatsService에 접근 할 수 있다. 그리고 같은 instance를 다른 모든 module에서 import해서 사용할 수 있다.



Module re-exporting


NestJS에서는 내부 provider를 export할 수 있다. 또한 한 module 내에서 import한 것을 다시 export할 수 있다. 아래의 예시처럼 CommonModule은 CoreModule에서 import과 export를 둘 다 하고 있다. 이를 통해 모듈 간의 의존성을 보다 효과적으로 관리하고, 재사용성을 높일 수 있다.


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


Dependency injection


module class는 provider를 inject할 수 있다. (e.g. 주로 설정 또는 초기화 작업을 위해 사용된다. )

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 class 그 자체는 provider로써 injected 될 수 없다. 왜냐하면 Circular dependency가 발생하기 때문이다.
https://docs.nestjs.com/fundamentals/circular-dependency



Global modules

만일 모든 곳에서 같은 module의 집합을 import해야한다면, 이는 꽤 tedius한 작업일 것 이다. Nest와는 달리 Angular providers는 global scope로 등록된다. 한번 정의되면, 어디에서든지 사용할 수 있다. 하지만 Nest는 provider들을 module 내부에서 캡슐화 한다. 그래서 캡슐화된 module을 import해야 사용할 수 있다.


만일 NestJS에서 어플리케이션 전체에서 기본적으로 사용할 수 있는 provider 세트를 제공하려면, module을 global module로 설정하면 된다. 이를 위해 @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() decorator은 module을 global-scope로 만들어준다. Global module은 일반적으로 root module이나 core module에서 한 번만 등록해야 한다. 위의 경우 CatsService provider는 어디에서든지 사용할 수 있다. 만일 이를 사용하고 싶은 module이 있다면 굳이 CatsModule을 import 할 필요가 없다.


<팁!>
global module은 boilerplate를 줄이는데 유용지만, 모든것을 global하게 만드는 것은 좋은 설계가 아니다. 일반적으로 module의 API를 소비자에게 제공하는 가장 권장되는 방법은 imports 배열을 통해 명시적으로 module을 import 하는 것이다.



Dynamic modules

Nest module system은 dynamic module이라는 기능을 제공한다. 이는 동적으로 module을 customize할 수 있다. 동적 module은 provider를 동적으로 등록하고 구성할 수 있는 customizable module을 쉽게 생성할 수 있게 해준다.
https://docs.nestjs.com/fundamentals/dynamic-modules

다음은 DatabaseModule을 위한 dynamic module 정의이다.

import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';

// forRoot: 이 정적 메서드는 동적 모듈을 반환한다. 기본적으로 entity와 opton을 받아 DB 제공자를 생성한다.
// createDatabaseProviders 함수를 호출하여 DB 제공자들을 생성하고, 이를 proividers와 exports 배열에 추가한다.

@Module({
  providers: [Connection],
  exports: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}

<팁!>
forRoot() method은 동기 혹은 비동기적인 dynamic module을 반환한다.(i.e. promise)


이 module은 기본적으로 connection provider를 정의한다. 허나 추가적으로 (forRoot() method에 전달되는 entity나 option들에 의존하여) providers, repository들 따위를 드러내준다. dynamic module이 반환하는 속성들은 @Module() 데코레이터에 정의된 metadata를 덮어쓰기 않고 확장한다. 이것이 정적으로 선언된 connection provider와 dynamic하게 생성된 repository providers들이 이 모듈에서 export되는 방법이다.


만일 dynamic module을 global scope에서 등록하고자 한다면, globla 속성을 true로 설정한다.


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

DatabaseModule은 다음과 같은 방식으로 imported 될 수 있고 configured 될 수 있다.


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

만일 DatabaseModule을 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 {}

Dynamic Module에서 더 자세히 다룰 것이다. working example 과 함께.

'NestJS > Docs_OVERVIEW' 카테고리의 다른 글

Exception filters  (0) 2024.06.05
Middleware  (1) 2024.06.04
Providers  (0) 2024.06.04
Controllers  (0) 2024.06.03
First Steps  (0) 2024.06.03

+ Recent posts