코딩일상

[nest.js] nest.js 뿌수기 공식 docs 모조리 파헤치기[Modules] 본문

카테고리 없음

[nest.js] nest.js 뿌수기 공식 docs 모조리 파헤치기[Modules]

solutionMan 2025. 12. 11. 22:48
반응형

1. Module 기본 개념

Module은 @Module() 데코레이터로 정의된 클래스입니다.

NestJS가 애플리케이션 구조를 효율적으로 조직화하고 관리하는 데 사용하는 메타데이터를 제공

모든 NestJS 애플리케이션은 최소한 하나의 root module을 가지며, 이것이 Nest가 application graph를 구축하는 시작점이 됩니다. 이 그래프는 Nest가 모듈과 프로바이더 간의 관계와 의존성을 해결하는 내부 구조입니다.

@Module() 데코레이터 속성

providers Nest injector에 의해 인스턴스화되고 최소한 이 모듈 내에서 공유될 수 있는 프로바이더들
controllers 이 모듈에서 정의되고 인스턴스화되어야 하는 컨트롤러 세트
imports 이 모듈에서 필요한 프로바이더를 export하는 다른 모듈들의 리스트
exports 이 모듈에서 제공하고, 이 모듈을 import하는 다른 모듈에서 사용 가능해야 하는 프로바이더들의 부분집합

캡슐화: 모듈은 기본적으로 프로바이더를 캡슐화합니다. 현재 모듈의 일부이거나 다른 imported 모듈에서 명시적으로 export된 프로바이더만 주입할 수 있습니다.


2. Feature Modules

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

Feature Module은 특정 기능과 관련된 코드를 조직화하는 모듈입니다. 이것은 명확한 경계를 유지하고 더 나은 조직화를 돕습니다. 애플리케이션이나 팀이 성장할수록 중요하며, SOLID 원칙과 일치합니다.

CLI로 모듈 생성

$ nest g module cats

 

Root Module에 Import

// 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.module.ts
│   └── cats.service.ts
├── app.module.ts
└── main.ts

3. Shared Modules (공유 모듈)

NestJS에서 모듈은 기본적으로 싱글톤입니다. 따라서 여러 모듈 간에 동일한 프로바이더 인스턴스를 쉽게 공유할 수 있습니다.

// 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
})
export class CatsModule {}

핵심 개념:

  • 모든 모듈은 자동으로 shared module입니다
  • exports 배열에 추가하면 다른 모듈에서 사용 가능
  • 같은 인스턴스를 모든 모듈이 공유하여 메모리 효율적

왜 모듈에 캡슐화하는가?

  • 각 모듈에서 직접 CatsService를 등록하면 각각 별도의 인스턴스가 생성됨
  • 메모리 사용량 증가 및 상태 불일치 가능성
  • 모듈에 캡슐화하고 export하면 동일한 인스턴스 재사용

4. Module Re-exporting

모듈은 자신의 내부 프로바이더를 export할 뿐만 아니라, import한 모듈도 re-export할 수 있습니다.

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

이렇게 하면 CoreModule을 import하는 다른 모듈에서 CommonModule도 사용할 수 있습니다.


5. Dependency Injection in Modules

모듈 클래스 자체도 프로바이더를 주입받을 수 있습니다 (예: 설정 목적).

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

주의: 모듈 클래스 자체는 circular dependency 때문에 프로바이더로 주입될 수 없습니다.

6. Global Modules

동일한 모듈 세트를 어디서나 import해야 한다면 번거로울 수 있습니다. @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 모듈은 한 번만 등록되어야 함 (일반적으로 root 또는 core 모듈에서)
  • CatsService를 사용하려는 모듈이 CatsModule을 imports 배열에 추가할 필요 없음
  • 모든 것을 global로 만드는 것은 권장되지 않음 - imports 배열을 사용하는 것이 더 명확하고 유지보수 가능

7. Dynamic Modules(완벽 이해 부족 추가 공부 정리 예정)

Dynamic Modules는 런타임에 구성할 수 있는 모듈을 만들 수 있게 합니다. 특정 옵션이나 설정에 따라 프로바이더를 생성할 때 유용합니다.

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,
    };
  }
}

주요 특징

  1. 동적 프로바이더 생성: entities와 options 객체에 따라 프로바이더 컬렉션(예: repositories) 생성
  2. 메타데이터 확장: 동적 모듈이 반환하는 속성은 @Module() 데코레이터에 정의된 기본 메타데이터를 확장(덮어쓰지 않음)
  3. 동기/비동기 지원: forRoot() 메서드는 동기적으로 또는 비동기적으로(Promise를 통해) 반환 가능

Global Dynamic Module

{
  global: true,  // 전역 범위로 설정
  module: DatabaseModule,
  providers: providers,
  exports: providers,
}

사용 예시

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

Re-exporting Dynamic Module

@Module({
  imports: [DatabaseModule.forRoot([User])],
  exports: [DatabaseModule],  // forRoot() 호출 생략 가능
})
export class AppModule {}

ConfigurableModuleBuilder

더 고급 사용을 위해 ConfigurableModuleBuilder를 사용하여 고도로 커스터마이징 가능한 동적 모듈을 구축할 수 있습니다.

핵심 정리

  1. 모듈은 NestJS의 기본 조직 단위 - 관련된 컴포넌트들을 그룹화
  2. 싱글톤 패턴 - 모듈은 기본적으로 싱글톤이며 프로바이더 인스턴스를 공유
  3. 캡슐화 - 명시적으로 export하지 않으면 프로바이더는 모듈 외부에서 사용 불가
  4. 재사용성 - export를 통해 다른 모듈에서 프로바이더 재사용
  5. 전역 범위 - @Global() 데코레이터로 어디서나 사용 가능하게 만들 수 있지만, 남용 금지
  6. 동적 구성 - Dynamic Modules로 런타임에 모듈 구성 가능

 


Module을 사용하는 것 vs 안 하는 것 - 실생활 비유로 이해하기

1. 🏢 회사 조직 비유

Module 없이 개발 (모든 직원이 한 사무실)

// 😱 모든 것이 한 곳에!
class AppModule {
  providers: [
    // 유저 관련 (20개)
    UserService, UserRepository, UserValidator, EmailService, PasswordHasher, ...
    
    // 상품 관련 (30개)
    ProductService, ProductRepository, InventoryService, PriceCalculator, ...
    
    // 주문 관련 (25개)
    OrderService, OrderRepository, PaymentService, ShippingService, ...
    
    // 인증 관련 (15개)
    AuthService, JwtService, TokenValidator, RefreshTokenService, ...
    
    // 총 90개의 Service들이 한 파일에!
  ]
}

문제점:

  • 📦 모든 직원(Service)이 한 사무실에 모여있음
  • 🤯 누가 누구랑 일하는지 알 수 없음
  • 🐌 회사 규모가 커지면 혼란만 가중
  • 🔍 특정 팀 찾기가 어려움

Module 사용 (부서별로 조직화)

// 👥 유저 팀 (HR팀 같은 느낌)
@Module({
  providers: [UserService, UserRepository, EmailService],
  exports: [UserService]  // 다른 부서에서 쓸 수 있게 공개
})
class UserModule {}

// 📦 상품 팀 (영업팀 같은 느낌)
@Module({
  providers: [ProductService, ProductRepository, InventoryService],
  exports: [ProductService]
})
class ProductModule {}

// 💳 주문 팀 (재무팀 같은 느낌)
@Module({
  imports: [UserModule, ProductModule],  // 다른 팀과 협업
  providers: [OrderService, PaymentService],
  exports: [OrderService]
})
class OrderModule {}

// 🏢 본사 (전체 통합)
@Module({
  imports: [UserModule, ProductModule, OrderModule]
})
class AppModule {}

장점:

  • ✅ 각 팀이 자기 일에 집중
  • ✅ 팀 간 협업 관계가 명확
  • ✅ 새 직원이 와도 어느 팀인지 바로 알 수 있음
  • ✅ 팀 단위로 테스트 가능
반응형
Comments