nest의 공식문서를 토대로 작성합니다.
모듈은 @Module() 데코레이터가 달린 클래스입니다. @Module() 데코레이터는 Nest가 애플리케이션 구조를 구성하는 데 사용하는 메타데이터를 제공합니다.
각 애플리케이션에는 하나 이상의 모듈, 즉 루트 모듈이 있습니다. 루트 모듈은 Nest가 모듈과 provider 관계 및 종속성을 해결하는 데 사용하는 내부 데이터 구조인 애플리케이션 그래프를 구축하는 데 사용하는 시작점입니다.
아주 작은 애플리케이션에는 이론적으로 루트 모듈만 있을 수 있지만 일반적인 경우는 아닙니다.
모듈은 컴포넌트를 아주 효과적으로 구성하는 방법입니다. 따라서 대부분의 애플리케이션에서 여러 개의 모듈을 사용하며 각 모듈은 밀접하게 관련된 기능 집합을 캡슐화합니다.
@Module() 데코레이터는 모듈을 설명하는 속성을 가진 단일 객체를 받습니다.
providers | Nest injector에 의해 인스턴스화되고 적어도 이 모듈 전체에서 공유될 수 있는 공급자 |
controllers | 모듈에서 정의된 인스턴스화해야 하는 컨트롤러의 집합 |
imports | 이 모듈에 필요한 provider가 import될 모듈의 리스트 |
exports | 이 모듈에서 제공되며 이 모듈을 가져오는 다른 모듈에서 사용할 수 있어야 하는 provider의 하위 집합 provider 자체 또는 토큰만 사용할 수 있음 |
모듈은 기본적으로 provider를 캡슐화합니다. 즉, 현재 모듈에 직접 포함되지 않거나 가져온 모듈에서 내보낸 provider를 삽입할 수 없습니다. 따라서 모듈에서 내보낸 provider를 모듈의 공용 인터페이스 또는 API로 간주할 수 있습니다.
이렇게 번역본으로 보면 무슨 말인지 잘 감이 안 잡힐 수도 있습니다. 괜찮습니다! 이 시리즈를 끝까지 보시거나 직접 앱을 만들어 보고 다시 읽으시면 감이 잡힐 것입니다.😎
CatsController
와 CatsService
는 동일한 애플리케이션 도메인에 속합니다. 서로 밀접하게 연관되어 있으므로 feature module로 이동하는 것이 좋습니다. feature module은 특정 기능과 관련된 코드를 간단히 정리하여 코드를 체계적으로 유지하고 명확한 경계를 설정합니다. 이는 특히 애플리케이션 및 팀의 규모가 커짐에 따라 복잡성을 관리하고 SOLID 원칙에 따라 개발하는 데 도움이 됩니다.
SOLID 원칙은 이전에 다뤘었죠? 다시 보고 싶으신 분들은 여기로~
CatsModule
을 만들어 봅시다.
직접 파일을 생성하셔도 되고 저처럼 CLI를 이용하셔도 됩니다!
$ nest g mo 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 {}
CatsModule
을 위 파일에서 정의했습니다. 마지막으로 해야 할 일은 이 모듈을 루트 모듈(app.module.ts
파일에 정의된 AppModule
)로 import 하는 것입니다.
# app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
CLI를 이용하면 자동으로 여기 등록이 됩니다. 그리고 공식 문서와 파일이 다를 수 있는데 제 파일은
# app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
controllers: [AppController, CatsController],
providers: [AppService, CatsService],
})
export class AppModule {}
이렇게 되어있습니다. 차이는 controllers, providers의 배열에 cats 관련 provider가 들어가 있습니다. CLI로 파일을 생성해서 그런 겁니다.
CatsModule
파일을 보시면 이미 CatsController
와 CatsService
를 담고 있는 걸 보실 수 있습니다. 따라서 AppModule
의 imports 배열에 CatsModule
만 넣으면 나머지는 안 넣어도 된다는 뜻입니다. 이렇게요!
# app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
AppController
랑 AppService
는 그냥 뒀습니다.ㅎㅎ
이제 우리 애플리케이션의 파일 구조는
이렇게 되어있을 것입니다.
Nest에서 모듈은 기본적으로 싱글톤이므로 여러 모듈 간에 모든 provider의 동일한 인스턴스를 손쉽게 공유할 수 있습니다.
싱글톤 패턴?
간단히 말씀드리자면 여러 디자인 패턴 종류 중 하나로, 하나의 클래스에 오직 하나의 객체 인스턴스만 가지는 패턴입니다.
즉 하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 어디에서든 공유하며 접근, 사용할 수 있는 것입니다.
저도 이분 블로그를 참조했습니다. 😀 감사합니다.
https://cheershennah.tistory.com/223
위에서 본 것처럼 모듈은 내부 provider를 내보낼 수 있습니다. 또한 가져온 모듈을 다시 내보낼 수도 있습니다. 아래 예에서는 CommonModule
을 CoreModule
에서 가져오고 내보내서 이 모듈을 import하는 다른 모듈에서 사용할 수 있도록 합니다.
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
그러니까 그냥 가져온 거 다시 내보내기 가능!
모듈 클래스는 configuration 목적 등으로 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) {}
}
그러나 모듈 클래스 자체는 순환 종속성으로 인해 provider로 주입할 수 없습니다.
순환 종속성? 순환 의존성? 저도 몰라서 찾아봤습니다ㅎㅎ
순환 의존성?
모든 곳에서 동일한 모듈 세트를 가져와야 한다면 귀찮을 수 있습니다. Nest와 달리 Angularprovider는 전역 범위에 등록됩니다. 한 번 정의하면 어디서나 사용할 수 있습니다. 그러나 Nest는 모듈 범위 내에 provider를 캡슐화합니다. 캡슐화된 모듈을 먼저 가져오지 않으면 다른 곳에서 모듈의 provider를 사용할 수 없습니다.
helpers, database connections 등 모든 곳에서 즉시 사용할 수 있어야 하는 provider 집합을 제공하려는 경우 @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() 데코레이터는 모듈을 전역 범위로 만듭니다. 전역 모듈은 일반적으로 루트 또는 코어 모듈에 의해 한 번만 등록되어야 합니다. 위의 예시에서 CatsService
provider는 어디에나 있으며 이 서비스를 삽입하려는 모듈은 import 배열에서 CatsModule
을 import 할 필요가 없습니다.
HINT
모든 것을 전역으로 만드는 것은 좋은 디자인 결정이 아님. 필요한 상용구의 양을 줄이기 위해 전역 모듈을 사용할 수 있음. imports 배열은 일반적으로 모듈의 API를 사용할 수 있도록 하는 데 선호되는 방법임.
그러니까 모듈을 전역으로 만드는 것은 최대한 자제해라라는 말인 것 같습니다.
Nest 모듈 시스템에는 동적 모듈이라는 강력한 기능이 포함되어 있습니다. 이 기능을 사용하면 provider를 동적으로 등록하고 구성할 수 있는 모듈을 쉽게 만들 수 있습니다. 여기서는 동적 모듈에 대해 광범위하게 다룹니다.
이 챕터에서는 모듈에 대한 소개를 완료하기 위해 간략한 개요를 제공합니다.
나중에 더 자세히 나오니까 그냥 이런 게 있구나~ 정도로 생각하시면 됩니다.
다음은 데이터베이스 모듈에 대한 동적 모듈 정의의 예입니다.
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,
};
}
HINT
forRoot() 메소드는 동적 모듈을 동기식 또는 비동기식으로 반환할 수 있음.(Promise를 통해)
이 모듈은 기본적으로 Connection provider를 정의하지만 추가로 forRoot() 메소드에 전달된 엔티티 및 옵션 객체에 따라 repositories 같은 provider collection을 노출합니다. 동적 모듈이 반환하는 properties는 @Module() 데코레이터에 정의된 기본 모듈 메타데이터를 재정의하지 않고 확장한다는 것을 유의하세요. 이것이 정적으로 선언된 Connection provider와 동적으로 생성된 repository provider가 모두 모듈에서 내보내는 방식입니다.
전역 범위에서 동적 모듈을 등록하려면 전역 속성을 true로 설정하세요.
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}
WARNING
위에서 언급했듯이 모든 것을 전역으로 만드는 것은 좋지 않습니다.
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 {}
동적 모듈 챕터에서 이 주제를 자세히 다루며 예제가 포함되어 있습니다.
HINT
고도로 사용자 정의 가능한 동적 모듈을 빌드하는 방법은 여기로
저는 해당 챕터가 나오면 그때 자세히 공부하겠습니다. 지금은 그냥 이런 게 있구나! 정도로 알아가겠습니다!
여기까지의 제 코드입니다.
https://github.com/cxzaqq/cxzaqq-velog/tree/2.4-module
고생하셨습니다!
다음 글에서 만나요~~😀
저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!