Nest.js 모듈과 도메인

윤종성·2025년 5월 8일

실무

목록 보기
4/5

NestJS에서 **모듈(Module)**은 앱을 구성하는 기본 단위이자 코드의 구조를 분리하고 재사용성을 높이기 위한 핵심 개념이야.
Nest는 모듈 기반 아키텍처를 강제함으로써 의존성 주입(DI), 내부 경계 명확화, 기능 캡슐화를 쉽게 만들어줘.


✅ NestJS 모듈이란?

NestJS에서 모듈은 관련된 컴포넌트(서비스, 컨트롤러, 프로바이더 등)를 그룹으로 묶는 단위야.

@Module({
  imports: [],         // 다른 모듈들
  controllers: [],     // 이 모듈이 가진 라우터들
  providers: [],       // 서비스/헬퍼 등 주입 가능한 클래스들
  exports: [],         // 외부 모듈에서 이 모듈의 어떤 provider를 쓸지
})
export class UserModule {}

📦 왜 모듈을 쓰는가?

Nest는 프로젝트를 기능 단위로 쪼개는 **도메인 기반 구조(Domain Driven Design)**를 권장하고,
모듈은 그 도메인을 캡슐화하는 단위야.

예를 들어:

  • UserModule → 회원 관련
  • AuthModule → 인증 관련
  • ProductModule → 상품 관련

각 모듈은 자기 내부에서만 동작하는 서비스/컨트롤러/엔티티를 가질 수 있고,
필요한 기능만 exports/imports를 통해 주고받을 수 있어.


⚙️ 모듈의 주요 구성요소

항목설명
controllers이 모듈이 가진 HTTP 엔드포인트
providers의존성 주입 대상 (서비스, 유틸, 헬퍼 등)
imports다른 모듈을 가져와 사용할 수 있음
exports이 모듈 외부에서 사용 가능한 provider만 공개함

🔁 예시: AuthModule과 UserModule 관계

// auth.module.ts
@Module({
  providers: [AuthService],
  exports: [AuthService],  // 외부에서 사용 가능하게
})
export class AuthModule {}
// user.module.ts
@Module({
  imports: [AuthModule],   // AuthService를 쓰기 위해 import
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
  • UserServiceAuthService를 DI 받을 수 있는 이유는:

    1. AuthModuleAuthServiceexports했고
    2. UserModuleAuthModuleimports 했기 때문이야.

🧠 모듈의 특징 요약

  • 자체적으로 완결된 기능 단위를 만든다
  • 의존성 주입의 범위를 제어할 수 있다 (exports)
  • 다른 모듈과 조합 가능하다 (imports)
  • **전역 모듈(Global Module)**로 설정하면 전체 앱에서 공유 가능하다
@Global()
@Module({ ... })
export class ConfigModule {}

🔐 모듈로 인해 생기는 장점

  • 기능 분리 및 재사용성 향상
  • 테스트 단위 설정 쉬움 (module 단위 mocking)
  • 애플리케이션 구조가 직관적
  • 의존성 명시가 명확해짐 (DI 기반)

✅ 모듈의 단위 = 도메인 단위가 권장됨

NestJS는 **모듈 단위가 도메인(업무 영역)**이 되는 걸 가장 강하게 권장해.

즉, 하나의 모듈은 하나의 "주제", "업무 영역", 혹은 "비즈니스 기능"을 표현함.

예:

  • UserModule → 유저 등록, 조회, 탈퇴
  • AuthModule → 로그인, 토큰 발급, 인증
  • OrderModule → 주문 생성, 상태 변경
  • ProductModule → 상품 등록, 수정

이렇게 나누면 유지보수와 테스트가 쉬워지고, 의존 관계도 명확하게 가늠할 수 있어.


🤔 그런데 도메인을 넘나드는 복잡한 작업은 어디에?

예:
"유저가 상품을 주문하면, 포인트 차감 + 재고 차감 + 주문 생성 + 알림 전송"

이건 여러 도메인의 모듈(User, Product, Order, Notification)이 얽힌 작업이지.

Nest에서 이런 걸 처리하는 일반적인 방식은 이거야:

도메인 서비스들(UserService, OrderService 등)을 조합하는 ‘Application Layer 서비스’를 별도로 만든다.


🏗️ 구조 예시

// order-orchestrator.service.ts
@Injectable()
export class OrderOrchestratorService {
  constructor(
    private readonly userService: UserService,
    private readonly productService: ProductService,
    private readonly orderService: OrderService,
    private readonly notificationService: NotificationService,
  ) {}

  async placeOrder(userId: string, productId: string) {
    const user = await this.userService.findById(userId);
    const product = await this.productService.checkAvailability(productId);

    await this.userService.deductPoints(user.id, product.price);
    await this.productService.decreaseStock(product.id);
    const order = await this.orderService.create(user.id, product.id);
    await this.notificationService.sendOrderConfirmation(user.email);

    return order;
  }
}
  • 이 서비스는 실제 도메인 서비스들을 **조립(Orchestration)**만 담당함.
  • 진짜 비즈니스 로직(포인트 차감, 재고 변경 등)은 각각의 도메인 서비스에서 처리함.
  • 이름은 보통 Orchestrator, ApplicationService, UseCaseService 등으로 짓기도 해.

📦 이걸 어디에 넣어야 할까?

방법 1. 별도 ApplicationModule 생성

@Module({
  imports: [UserModule, ProductModule, OrderModule, NotificationModule],
  providers: [OrderOrchestratorService],
})
export class ApplicationModule {}

이렇게 하면 도메인 간의 조합로직을 한 곳에 캡슐화할 수 있음.

방법 2. 주요 도메인 모듈(OrderModule 등)에 포함

작업의 중심이 되는 도메인이 명확할 경우, 해당 도메인 모듈 안에 넣기도 해.
예: 주문 흐름의 핵심이 주문(Order)이라면, OrderModule 안에 OrderService, OrderOrchestratorService 둘 다 넣는 식.


📚 정리

질문답변
모듈 단위는 도메인 기준이 맞나?✅ NestJS에서 권장하는 구조이며 현실적인 기준임
도메인을 넘는 복잡한 작업은 어디서?여러 도메인 서비스를 **조합하는 Application Service (Orchestrator)**로 따로 구현
그건 어떤 모듈에 넣어야?① 별도 ApplicationModule, ② 중심 도메인 모듈 중 택일 가능

**“도메인을 얼마나 작게 쪼개야 하냐”**는 건 NestJS뿐 아니라 DDD(Domain-Driven Design), 모듈 아키텍처 설계 전반에서 계속 나오는 핵심 이슈인데,
정답은 하나가 아니라 **"업무 의미 단위에 따라 쪼갤 수 있을 만큼 쪼개되, 불필요하게 분리하지는 말라"**는 게 기준이야.


✅ 핵심 기준: "한 가지 책임 / 의미 있는 비즈니스 영역"

하나의 모듈은 하나의 '업무 목적'을 가져야 해.
너무 작게 나누면 복잡도만 늘어나고, 너무 크게 두면 응집도가 떨어져.

권장되는 쪼갬 수준 예시:

모듈 이름괜찮은 분리?이유
UserModule"회원 등록/조회/탈퇴" 등 유저 관련 책임 묶음
AuthModule"로그인/토큰/인증"은 책임과 관심사가 다름
ProfileModule유저의 개인정보 수정, 조회 등만 따로 관리 가능
UserCreateModule, UserDeleteModule너무 세분화 → 오히려 관심사 중복 및 유지보수 어려움

📦 도메인 분리 판단 기준

1. 업무 용어 기준

  • 실제 기획자나 기획서, 비즈니스 문서에 회원, 인증, 주문, 배송 등의 용어가 각각 등장한다면 → 모듈 분리 대상
  • 반대로 "사용자 생성", "사용자 삭제"가 각각 독립적인 비즈니스는 아님 → 같은 모듈로 묶는 게 자연스러움

2. 데이터 및 책임의 응집도

  • 하나의 모듈에서 다루는 클래스/서비스들이 서로 긴밀하게 연관돼 있다면 → 하나로 묶기
  • 서로 거의 의존하지 않고, 각자 DB 모델도 따로 존재한다면 → 분리 고려

3. 변경 시점의 일치 여부 (Change Rate)

  • 자주 같이 변경되는 기능은 함께 두는 게 맞고
  • 서로 독립적으로 바뀌는 기능은 나누는 게 유지보수에 유리함

4. 테스트 또는 배포 단위로의 분리 가능성

  • 특정 기능은 API 버전, 클라이언트, 관리자/유저 등에서 별도 사용됨 → 분리 고려

🤔 NestJS에서는 보통 이 정도 수준으로 쪼갬

기능 영역모듈 예시
유저 관리UserModule
인증AuthModule
상품ProductModule
주문OrderModule
결제PaymentModule
알림NotificationModule
설정ConfigModule
공통 유틸SharedModule, CommonModule
  • 위 수준이 NestJS에서 일반적으로 실무에서 사용하는 쪼갬 단위
  • 그보다 더 세분화하고 싶다면 feature 단위 서브모듈을 모듈 안에 폴더 수준으로 넣는 방식 추천

🧠 요약

질문답변
도메인을 얼마나 작게 쪼개야 해?의미 있는 업무 단위로 쪼개되, 너무 작게 나누면 관리 지옥이 된다
기준은 뭐야?비즈니스 용어 / 책임 응집도 / 변경 시점 / 독립 배포 가능성
Nest에서 실무적인 쪼갬은?User, Auth, Order, Product 등 실제 도메인 중심으로 모듈 구성

한 줄 요약

NestJS의 모듈은 도메인 중심으로 적절히 작게 나누되,
여러 도메인 간 로직은 별도의 조합 서비스(Application Layer)에서 처리하고,
너무 큰 모듈은 테스트, 유지보수, 성능 측면에서 반드시 분리하는 게 좋다.

profile
알을 깬 개발자

0개의 댓글