4. Modules
모듈은 @Module() 데코레이터로 어노테이션되는 클래스입니다. 이 데코레이터는 Nest가 애플리케이션 구조를 효율적으로 구성하고 관리하는 데 사용하는 메타데이터를 제공합니다.
모든 Nest 애플리케이션에는 하나 이상의 모듈, 즉 루트 모듈이 있으며, 이는 Nest가 애플리케이션 그래프를 구축하기 위한 시작점 역할을 합니다. 이 그래프는 Nest가 모듈과 공급자 간의 관계와 종속성을 해결하는 데 사용하는 내부 구조입니다. 소규모 애플리케이션에는 루트 모듈만 있을 수도 있지만 일반적으로는 그렇지 않습니다. 모듈은 컴포넌트를 효과적으로 구성하는 방법으로 적극 권장됩니다. 대부분의 애플리케이션에는 밀접하게 관련된 기능 집합을 캡슐화하는 여러 개의 모듈이 있을 것입니다.
providers |
Nest Injector에 의해 인스턴스화되고 적어도 이 모듈 전체에서 공유될 수 있는 공급자 |
controllers |
인스턴스화해야 하는 이 모듈에 정의된 컨트롤러 집합 |
imports |
이 모듈에 필요한 공급자를 내보내는 import 모듈 목록 |
exports |
이 모듈에서 제공되며, 이 모듈을 임포트하는 다른 모듈에서 사용할 수 있어야하는 provider 의 하위 집합입니다. 공급자 자체 또는 토큰값 중 하나만 사용할 수 있습니다. |
모듈은 기본적으로 공급자를 캡슐화하므로 현재 모듈의 일부이거나 가져온 다른 모듈에서 명시적으로 내보낸 공급자만 삽입할 수 있습니다. 모듈에서 내보낸 프로바이더는 기본적으로 모듈의 공용 인터페이스 또는 API 역할을 합니다.
Feature modules
이 예제에서 CatsController와 CatsService는 밀접하게 관련되어 있으며 동일한 애플리케이션 도메인에 서비스를 제공합니다. 따라서 이들을 기능 모듈로 그룹화하는 것이 좋습니다. 기능 모듈은 특정 기능과 관련된 코드를 정리하여 명확한 경계를 유지하고 더 잘 정리할 수 있도록 도와줍니다. 이는 애플리케이션이나 팀이 성장함에 따라 특히 중요하며 SOLID 원칙과도 일치합니다.
다음으로 컨트롤러와 서비스를 그룹화하는 방법을 보여주기 위해 CatsModule을 만들어 보겠습니다.
// 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.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 {}
이제 다음과 같은 폴더 구조로 이루어지게 됩니다:
Shared Module
Nest에서 모듈은 기본적으로 싱글톤이므로 여러 모듈 간에 동일한 공급자의 인스턴스를 손쉽게 공유할 수 있습니다.
모든 모듈은 자동으로 공유 모듈이 됩니다. 일단 생성되면 모든 모듈에서 재사용할 수 있습니다. 여러 다른 모듈 간에 CatsService 인스턴스를 공유하고자 한다고 가정해 보겠습니다. 이를 위해서는 먼저 아래 그림과 같이 모듈의 내보내기 배열에 CatsService 공급자를 추가하여 내보내야 합니다:
// 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에 액세스할 수 있으며 이를 임포트하는 다른 모든 모듈과도 동일한 인스턴스를 공유하게 됩니다.
CatsService를 필요로 하는 모든 모듈에 직접 등록하면 실제로 작동하겠지만 각 모듈이 별도의 CatsService 인스턴스를 갖게 됩니다. 이렇게 하면 동일한 서비스의 인스턴스가 여러 개 생성되므로 메모리 사용량이 증가할 수 있으며, 서비스가 내부 상태를 유지하는 경우 상태 불일치와 같은 예기치 않은 동작이 발생할 수도 있습니다.
CatsModule과 같은 모듈 내부에 CatsService를 캡슐화하여 내보내면 CatsModule을 가져오는 모든 모듈에서 동일한 CatsService 인스턴스가 재사용되도록 할 수 있습니다. 이렇게 하면 메모리 소비가 줄어들 뿐만 아니라 모든 모듈이 동일한 인스턴스를 공유하므로 공유 상태나 리소스를 더 쉽게 관리할 수 있으므로 동작을 더 예측할 수 있습니다. 이는 NestJS와 같은 프레임워크에서 모듈성과 종속성 주입의 주요 이점 중 하나로, 애플리케이션 전체에서 서비스를 효율적으로 공유할 수 있게 해줍니다.
Module re-exporting
위에서 보았듯이 모듈은 내부 공급자를 내보낼 수 있습니다. 또한 가져온 모듈을 다시 내보낼 수도 있습니다. 아래 예시에서는 CommonModule을 CoreModule에서 가져오고 내보내서 이 모듈을 가져오는 다른 모듈에서 사용할 수 있도록 합니다.
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
Dependency injection
모듈 클래스는 구성 목적으로 프로바이더를 inject할 수도 있습니다. (예: 설정 목적)
// 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) {}
}
그러나 모듈 클래스 자체는 순환 종속성 때문에 프로바이더로 주입할 수 없습니다.
Global modules
모든 곳에서 동일한 모듈 세트를 가져와야 한다면 지루할 수 있습니다. Nest와 달리 Angular provider
는 글로벌 범위에 등록됩니다. 한번 정의하면 어디서나 사용할 수 있습니다. 그러나 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을 임포트할 필요가 없습니다.
힌트:
모든 것을 전역으로 만드는 것은 설계 관행으로 권장되지 않습니다. 전역 모듈은 상용구를 줄이는 데 도움이 될 수 있지만 일반적으로 다른 모듈에서 모듈의 API를 제어되고 명확한 방식으로 사용할 수 있도록 하려면 가져오기 배열을 사용하는 것이 더 좋습니다. 이 접근 방식은 모듈의 필요한 부분만 다른 모듈과 공유하면서 애플리케이션의 관련 없는 부분 간의 불필요한 결합을 피할 수 있어 구조와 유지 관리성이 향상됩니다.
Dynamic modules
Nest의 동적 모듈을 사용하면 런타임에 구성할 수 있는 모듈을 만들 수 있습니다. 특정 옵션이나 구성에 따라 공급자를 만들 수 있는 유연하고 사용자 지정 가능한 모듈을 제공해야 할 때 특히 유용합니다. 다음은 동적 모듈의 작동 방식에 대한 간략한 개요입니다.
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@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() 메서드는 동적 모듈을 동기식 또는 비동기식(즉, 프로미스를 통해)으로 반환할 수 있습니다.
이 모듈은 기본적으로 연결 프로바이더를 정의하지만(@Module() 데코레이터 메타데이터에서), 추가로 forRoot() 메서드로 전달된 엔티티 및 옵션 객체에 따라 리포지토리와 같은 프로바이더 컬렉션을 노출합니다. 동적 모듈이 반환하는 프로퍼티는 @Module() 데코레이터에 정의된 기본 모듈 메타데이터를 재정의하지 않고 확장한다는 점에 유의하세요. 이것이 정적으로 선언된 연결 공급자와 동적으로 생성된 리포지토리 공급자가 모두 모듈에서 내보내는 방식입니다.
전역 범위에서 동적 모듈을 등록하려면 전역 속성을 true로 설정하세요.
주의: 위에서 언급했듯, 모든 것을 글로벌로 만드는 것은 좋은 디자인 결정이 아닙니다.
데이터베이스 모듈은 다음과 같은 방법으로 가져오고 구성할 수 있습니다.
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 {}
동적 모듈을 다시 내보내려면 내보내기 배열에서 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 {}
동적 모듈 장에서는 이 주제를 더 자세히 다루며 작동 예제를 포함하고 있습니다.
힌트
이 장에서는 ConfigurableModuleBuilder를 사용하여 고도로 사용자 정의 가능한 동적 모듈을 구축하는 방법을 알아보세요.