상세 컨텐츠

본문 제목

Monolithic Module Architecture

Architecture

by devTak 2025. 7. 18. 17:14

본문

반응형

Module 관리

모듈도 하나의 프로젝트처럼 생겼는데, 어떻게 관리하는게 좋을까?

SubModule 로의 관리?

서브모듈이란,

Git 저장소 안에 다른 Git 저장소를 디렉토리로 분리해 넣는 것이 서브모듈이다. 다른 독립된 Git 저장소를 Clone 해서 내 Git 저장소 안에 포함할 수 있으며 각 저장소의 커밋은 독립적으로 관리한다.

API 프로젝트를 SubModule 로 구성하는것에 대한 장점?

  • MSA 구성에 앞서 프로젝트 레포지토리를 분리하는것
  • 각 프로젝트별 강제적인 분리를 보다 직관적으로 표현
  • 각 도메인(모듈)별 별도의 CI/CD 구성과 커밋을 관리

API 프로젝트를 SubModule 로 구성하는것에 대한 단점

  • MSA 구성에 앞서 프로젝트 레포지토리를 미리 분리하는것이 맞는 판단인가?
  • 각 프로젝트별 강제적인 분리를 직관적으로 하는것이 트러블슈팅 / 유지보수등 운영에 용이한가?
  • 각 도메인(모듈)별 별도의 CI/CD 구성과 커밋을 관리하는 것이 모놀리식 방식에 맞는것인가?
  • SubModule 을 구성함으로써 CI/CD 스크립트 작성 및 구성에 리소스가 들어가게 된다.
    • 그렇다고 MSA 로 분리하였을 때, CI/CD 구성을 변경하지 않아도 되는가? NO!
  • 각각의 모듈들이 MSA 구성이 아닌 환경에서 독립적으로 사용이 불가능하다.
  • 모듈별 의존성이 있는 기능에 대해 배포를 다수 처리
  • 각각의 모듈별로 존재할 때, 특정 모듈만 빌드하는것이 불가능
  • 각 모듈별 시멘틱버저닝에 대한 각 모듈별 체킹이 필수적으로 필요

SubModule 방식은 적합하지 않음

무엇이 하고싶은가?

각 모듈별 의존성을 최소화하여 MSA 구성 시, 빠르게 구성할 수 있도록 하는 것

모놀리식 아키텍쳐에서 어떻게 의존성을 최소화 할 수 있는가?

1. Adapter Pattern

Interface

export interface ProductService {
  getProduct(input: getProductDto): Promise<ProductResponse>;
}

Adapter (Internal)

@Injectable()
export class LocalProductServiceImpl implements ProductService {
  constructor(private readonly repo: ProductRepository) {}

  async getProduct(input: getProductDto): Promise<ProductResponse> {
    // 내부 도메인 로직 실행
  }
}

Adapter (gRPC)

@Injectable()
export class GrpcProductServiceImpl implements ProductService {
  constructor(@Inject(GRPC_CLIENT) private client: ProductGrpcClient) {}

  async getProduct(input: getProductDto): Promise<ProductResponse> {
    return this.client.getProduct(input); // gRPC 호출
  }
}

헥사고날아키텍쳐(포트앤 어댑터)에서 영감을 받음

주문 모듈 구현체 provider 등록

// order/order.module.ts
@Module({
  providers: [
    {
      provide: ProductService,
      useClass: LocalProductServiceImpl, // 해당영역 gRPC로 수정
    },
  ],
  exports: [OrderService],
})
export class OrderModule {}

프로젝트 디렉토리 구성안

src/
├── order/
│   ├── application/
│   │   └── order.service.ts
│   ├── domain/
│   └── order.module.ts
│
├── member/
│   ├── application/
│   │   └── member.service.ts
│   └── member.module.ts
│
├── shared/
│   ├── adapters/
│   │   ├── member/
│   │   │   ├── grpc-member.service.ts
│   │   │   └── local-member.service.ts
│   │   └── order/
│   │       ├── grpc-order.service.ts
│   │       └── local-order.service.ts
│   ├── interfaces/
│   │   ├── member.service.interface.ts
│   │   └── order.service.interface.ts
│   └── domain/
│       └── proto/
│           └── order.proto

Member Module

// src/member/member.module.ts
import { Module } from '@nestjs/common';
import { MemberService } from './application/member.service';
import { OrderService } from '../shared/interfaces/order.service.interface';
import { LocalOrderService } from '../shared/adapters/order/local-order.service';
// import { GrpcOrderService } from '../shared/adapters/order/grpc-order.service';
import { OrderModule } from '../order/order.module';

@Module({
  imports: [OrderModule],
  providers: [
    MemberService,
    {
      provide: OrderService,
      useClass: process.env.USE_GRPC === 'true' ? GrpcOrderService : LocalOrderService,
    },
  ],
})
export class MemberModule {}

여기까지는 괜찮을 수 있는데, MSA 로 분리하게 된다면 어떻게 될까

Order Service MSA 분리

order-service/
├── src/
│   ├── order/
│   │   ├── order.controller.ts
│   │   ├── order.service.ts
│   │   └── ...
│   └── member/
│       ├── grpc-member.service.ts    ✅
│       └── member.service.interface.ts
├── proto/
│   └── member.proto                  ✅

???? 이건 아닌 것 같다..

  • 공통 gRPC client SDK를 만들어 npm 패키지화..?
order-service/
├── package.json  // depends on: "@company/member-client"
  • 대안이 될 수는 있고, 공통 로직을 SDK 화 함으로써, 각 MSA 서비스들이 불필요한 코드를 작성하지 않아도 되는 장점이 있다.

그리고 MSA 구성을 준비할 때에도 각각의 서비스별 gRPC Client 는 어찌됐던 중복되기 마련이다.

그럼.. 이렇게 진행하는 것이 맞을까?

결론 : MSA 분리를 위해 모놀리틱서비스의 장점을 해치고, 코드 복잡도가 지나치게 높아진다.

NestJS 에서 제공하는 Resource 구조로 구현

import { Module } from '@nestjs/common';
import { TestCommerceService } from './test-commerce.service';
import { TestCommerceController } from './test-commerce.controller';

@Module({
  controllers: [TestCommerceController],
  providers: [TestCommerceService], // 각 타 모듈 서비스 주입
})
export class TestCommerceModule {}

순환참조

선택적 SDK 구성

  • MSA 구성을 대비해 각 서비스 모듈 별 공통 처리를 위한 패키지는 SDK로 관리한다.
  • EX ) HttpClient, Redis Connector, 등
반응형