[NestJS] Module

곽태민·2023년 6월 23일
0

NestJS

목록 보기
4/8

Module

Module@Module() 데코레이터로 주석이 달린 클래스로, Nest가 어플리케이션 구조를 구성하는데 사용하는 meta data를 제공한다.

각 어플리케이션에는 적어도 하나의 module인 root module이 존재한다. root module은 Nest가 어플리케이션 그래프를 구축하는데 사용하는 시작점이다.

(Nest가 Module과 Provider 관계 및 종속성을 해결하는데 사용하는 내부 데이터 구조다.)

매우 작은 어플리케이션은 이론적으로 root module만 가질 수 있지만 일반적인 경우는 아니고, 구성 요소를 구성하는 효과적인 방법으로는 module을 강력하게 권장한다.

따라서 대부분의 애플리케이션에서 resulting architecture는 각각 밀접하게 관련된 기능 집합을 캡슐화하는 여러 module을 사용한다. @Module() 데코레이터는 module을 설명하는 속성을 가진 단일 객체를 사용한다.

  • provider: Nest 인젝터에 의해 인스턴스화되고, 적어도 이 모듈에서 공유될 수 있는 Provider
  • controller: 인스턴스화되어야 하는 이 Module에 정의된 controller 세트
  • imports: 이 Module에 필요한 Provider를 내보내서 가져온 Module List
  • exports: 이 Module을 가져오는 다른 Module에서 사용할 수 있어야하는 Provider의 하위 집단

Module은 기본적으로 Provider를 캡슐화한다. 즉, 현재 Module의 직접적인 일부도 아니고, 가져온 Module에서 내보낸 Provider도 주입할 수 있다.

Feature modules

CatsControllerCatsService는 동일한 애플리케이션 도메인에 속한다. 밀접하게 관련되어 있으므로 Feature module로 가져가는 것이 좋다.

Feature module은 단순히 특정 기능과 관련된 코드를 구성하여 코드 구성을 유지하고 명확한 경계를 설정한다. 특히 어플리케이션 및 팀의 규모가 커짐에따라 복잡성을 관리하고, SOLID 원칙에 따라 개발하는데 도움이 된다.

예제를 위해 CatsModule을 생성한다.
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.module.ts 파일에 CatsModule을 정의하고, 이 Module과 관련된 모든 것을 cats 디렉토리로 옮겼다.

마지막으로 해야 할 일은 이 Module을 root module 즉, app.module.ts 파일에 정의된 AppModule로 가져오는 것이다.

app.module.ts

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

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

Shared modules

Nest에서 Module은 기본적으로 싱글톤 패턴이므로, 여러 Module간에 동일한 Provider 인스턴스를 쉽게 공유할 수 있다.

모든 Module은 자동으로 Shared Module이다. 일단 생성되면 모든 Module에서 재사용할 수 있다. 다른 여러 Module 간에 CatsService 인스턴스를 공유하고 싶다면 먼저 CatsService Provider를 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을 가져오는 모든 Module은 CatsService에 엑세스할 수 있고, 다른 모든 Module과 동일한 인스턴스를 공유한다.

Dynamic modules

정적 Module 바인딩하면 consuming module이 host module의 provider가 구성되는 방식에 영향일 미칠 수 없다. 왜 중요하냐면 다른 사례에서 다르게 동작해야 하는 purpose module이 있는 경우를 고려해봐야 한다.

Nest의 좋은 예로는 configuration module이다. 많은 어플리케이션은 configuration module을 사용하여 configuration 세부 정보를 외부화하는 것이 유용하다는 것을 알고 있을 것이다.

이를 통해 다양한 배포에서 어플리케이션 설정을 동적으로 쉽게 변경할 수 있다. 예를 들면 개발 DB, 스테이징 및 테스트를 위한 DB 등 관리를 configuration module에 위임하여 코드는 configuration parameter와 독립적으로 유지된다.

동적 Module을 사용하면 consuming module이 API를 사용하여 configuration module을 가져올 때 사용자 정의하는 방법을 제어할 수 있도록 configuration module을 동적으로 만들 수 있다.

즉, 동적 Module은 지금까지 본 정적 바인딩을 사용하는 것과 달리 한 Module의 속성과 동작을 사용자 지정하기 위한 API를 제공한다.

우선 ConfigMoudle을 정적으로 가져오는 예제를 살펴보겠다.

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
	imports: [ConfigModule],
  	controllers: [AppController],
  	providers: [AppService],
})
export class AppModule {}

configuration object를 전달하는 동적 Module import가 어떻게 생겼는지 두 예제간의 imports 배열의 차이점을 비교를 해보겠다.

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './config/config.module';

@Module({
	imports: [ConfigModule.register({ folder: './config' })],
  	controllers: [AppController],
  	providers: [AppService],
})
export class AppModule {}

동적 Module 예제에서 코드를 살펴보겠다. ConfigModule은 일반 클래스이므로 register()라는 정적 메서드가 있어야 한다고 추론할 수 있다.

클래스의 인스턴스가 아니라 ConfigModule 클래스에서 호출하기 때문에 정적이라고 알 수 있다. register() 메서드는 본인이 정의해야하므로 원하는 모든 입력 인수를 받을 수 있다.

이 경우 일반적인 경우인 적절한 속성을 가진 간단한 옵션 객체를 허용할 것 이다.

register() 메서드가 module과 같은 것을 반환해야 한다고 추론할 수 있다. 그 반환 값이 지금까지 module list를 포함하는 친숙한 import list에 나타나기 때문이다.

사실 register() 메서드가 반환하는 것은 동적 module이다. 동적 module은 런타임에 생성된 module에 불과하며, 정적 module과 동일학 속성에 module이라는 추가 속성이 하나 더 있다.

동적 module은 정확히 동일한 interfacemodule이라는 추가 속성을 가진 객체를 반환해야한다. module 속성은 module의 이름 역할을 하며, module의 클래스 이름과 같아야 한다.

profile
Node.js 백엔드 개발자입니다!

0개의 댓글