모듈은 @Module 데코레이터가 달린 클래스다. @Module 데코레이터는 Nest 가 어플리케이션 구조를 구성하는데 필요한 메타데이터를 제공한다.
각 어플리케이션에는 루트 모듈이라는 하나 이상의 모듈이 있다. 루트 모듈은 Nest가 애플리케이션 그래프를 구축하는 데 사용하는 시작점이다. 이는 Nest가 모듈과 프로바이더 관계 및 종속성을 해결하는데 사용하는 내부 데이터 구조이다. 매우 작은 응용 프로그램에서는 이론적으로 루트 모듈만 있을 수 있지만, 이는 일반적인 경우는 아니다. 구성 요소를 구성하는 효과적인 방법으로 모듈을 적극 권장한다. 대부분의 어플리케이션에서 최종 아키텍쳐는 밀접하게 관련된 기능 세트를 각각 캡슐화 하는 여러 모듈로 구성되기 마련이다.
@Module 데코레이터는 모듈임을 나타내는 하나의 객체를 갖는데, 그 객체의 속성은 다음과 같다.
providers | Nest 에 의해 인스턴화되고, 주입되면 적어도 이 모듈 전체에서 공유될 수 있는 프로바이더 |
controllers | 인스턴스화되어야 하는 이 모듈에 정의된 컨트롤러 세트 |
imports | 이 모듈에 필요한 프로바이더를 내보내는 임포트된 모듈 목록 |
exports | providers 의 하위 집합은 이 모듈에서 제공되며 이 모듈을 임포트하는 다른 모듈에서 사용할 수 있어야 한다. 프로바이더 자체 또는 해당 토큰(provide 값)만 사용할 수 있다. |
모듈은 기본적으로 프로바이더를 캡슐화한다. 이는 현재 모듈의 일부도 아니고, 내보내지지도 않은 프로바이더를 주입하는 것이 불가능하다는 것을 의미한다. 따라서 모듈에서 내보낸 프로바이더는 모듈의 공용 인터페이스 또는 API로 간주할 수 있다.
기능 모듈(Feature modules)
CatsController및 CatsService 동일한 애플리케이션 도메인에 속한다. 서로 밀접하게 관련되어 있으므로 기능 모듈로 이동하는 것이 좋다. 기능 모듈은 단순히 특정 기능과 관련된 코드를 구성하여 코드를 체계적으로 유지하면서 명확한 경계를 설정한다. 특히 애플리케이션 및/또는 팀의 규모가 커짐에 따라 복잡성을 관리하고 SOLID 원칙으로 개발하는 데 도움이 된다.
아래 예를 보자.
// 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 {}
힌트) 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
- cats
- dto
- create-cat.dto.ts
- interfaces
- cat.interface.ts
- cats.controller.ts
- cats.service.ts
- dto
- app.module.ts
- main.ts
- cats
공유 모듈
Nest 에서 모듈은 기본적으로 싱글톤이므로 여러 모듈 간에 모든 프로바이의 동일한 인스턴스를 쉽게 공유 할 수 있다.
모든 모듈은 자동으로 공유 모듈이 된다. 일단 생성되면 모든 모듈에서 재사용할 수 있다. CatsService를 다른 모듈간에 공유하고 싶다고 가정하면 먼저 아래와 같이 프로바이더에 정의된 CatsSerivce를 모듈의 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에 접근할 수 있다. 이는 동일한 인스턴스가 공유되는 것이다.
모듈 다시 내보내기(Module re-exporting)
위에서 본 것처럼, 모듈은 자기 내부의 프로바이더를 내보낼 수 있다. 이에 더해 임포트한 모듈을 다시 익스포트할 수 있다. 아래의 예에서 CommonModule은 CoreModule 에서 임포트되고 다시 익스포트 되었다. 다른 모듈에서 CoreModule을 임포트하면 CommonModule을 사용할 수 있다.
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
의존성 주입
모듈 클래스는 아래와 같은 방법으로 프로바이더를 주입할 수도 있다.
// cats.moudle.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) {}
}
그러나, 모듈 클래스 자체는 순환 종속성으로 인해 프로바이더로 주입될 수 없다.
전역 모듈
동일한 모듈 세트를 모든 곳에서 가져와야 한다면 지루해질 수 있다. Nest와 달리 Angular providers는 전역 범위에 등록된다. 일단 정의되면 어디에서나 사용할 수 있다. 그러나 Nest는 모듈 범위 내에서만 공급자를 캡슐화한다. 캡슐화 모듈을 먼저 가져오지 않으면 다른 곳에서 모듈 프로바이를 사용할 수 없다.
즉시 사용 가능한 모든 곳에서 사용할 수 있는 프로바이더 세트(예: 헬퍼, 데이터베이스 연결 등)를 제공하려는 경우 @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 프로바이더는 어디에나 존재하며 이 서비스를 주입하려는 모듈은 CatsModule을 imports 배열에서 가져올 필요가 없습니다.
힌트) 모든 것을 글로벌하게 만드는 것은 좋은 디자인이 아니다. 다만, 필요한 관용구의 양을 줄이기 위해 전역 모듈을 사용할 수 있다. 되도록 imports 배열에 선언하여 사용하자.
동적 모듈
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,
};
}
}
힌트) forRoot 메서드를 통해 동적 모듈을 동기 혹은 비동기로 반환할 수 있다. (예를 들어 Promise를 통해)
이 모듈은 기본적으로(@Module 데코레이터 메타데이터에서) Connection 공급자를 정의 하지만 추가적으로 - forRoot 메서드 에 전달된 entities및 options 개체에 따라 저장소와 같은 프로바이더 목록을 노출한다. 동적 모듈에서 반환된 속성은 @Module() 데코레이터에 정의된 기본 모듈 메타데이터를 확장 (재정의하는 대신) 한다 . 이것이 정적으로 선언된 Connection 프로바이더와 동적으로 생성된 저장소 프로바이가 모두 모듈에서 내보내지는 방식이다.
전역 범위에 동적 모듈을 등록하려면 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 {}
동적 모듈에 대한 자세한 설명은 다른 장에서 다루겠다.
'Backend(Framework) > NestJS 개요(공식문서 번역)' 카테고리의 다른 글
6. Exception filters (0) | 2023.11.17 |
---|---|
5. Middleware (1) | 2023.11.16 |
3. Provider (1) | 2023.11.16 |
2. Controller (0) | 2023.11.05 |
1. NestJS 시작 (0) | 2023.11.04 |