전체 목록
설계Hard#100

마이크로서비스 아키텍처(MSA)와 모놀리식(Monolithic) 아키텍처의 장단점을 비교해주세요.

#설계#MSA#아키텍처#마이크로서비스
힌트

확장성, 배포 독립성, 운영 복잡도를 기준으로 생각해보세요.

정답 및 해설

마이크로서비스 아키텍처(MSA)와 모놀리식(Monolithic) 아키텍처의 장단점을 비교해주세요.

모놀리식(Monolithic) 아키텍처는 애플리케이션의 모든 기능을 하나의 코드베이스와 하나의 배포 단위로 구성하는 방식이고, 마이크로서비스 아키텍처(MSA)는 각 기능을 독립적으로 배포 가능한 작은 서비스들의 집합으로 구성하는 방식입니다. 어느 것이 더 좋다고 할 수 없으며, 팀의 규모, 서비스의 복잡성, 성장 단계에 따라 적합한 아키텍처가 다릅니다.

모놀리식 아키텍처

구조

┌─────────────────────────────────────────────────────┐
│                   모놀리식 애플리케이션               │
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │  사용자  │  │  주문    │  │    결제          │  │
│  │  모듈    │  │  모듈    │  │    모듈          │  │
│  └──────────┘  └──────────┘  └──────────────────┘  │
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │  상품    │  │  알림    │  │    검색           │  │
│  │  모듈    │  │  모듈    │  │    모듈          │  │
│  └──────────┘  └──────────┘  └──────────────────┘  │
│                                                      │
│              ┌──────────────────┐                   │
│              │   단일 데이터베이스 │                   │
│              └──────────────────┘                   │
└─────────────────────────────────────────────────────┘

모놀리식 코드 예시

프로젝트 구조:
my-ecommerce/
├── src/
│   ├── users/
│   │   ├── user.controller.ts
│   │   ├── user.service.ts
│   │   └── user.repository.ts
│   ├── orders/
│   │   ├── order.controller.ts
│   │   ├── order.service.ts
│   │   └── order.repository.ts
│   ├── payments/
│   │   └── payment.service.ts
│   └── products/
│       └── product.service.ts
├── package.json         ← 하나의 패키지
└── docker-compose.yml   ← 하나의 배포
// 모놀리식에서 서비스 간 호출 (직접 함수 호출)
// order.service.ts
class OrderService {
  constructor(
    private userService: UserService,    // 직접 의존성 주입
    private productService: ProductService,
    private paymentService: PaymentService,
    private notificationService: NotificationService,
  ) {}

  async createOrder(userId: number, items: OrderItem[]): Promise<Order> {
    // 트랜잭션 내에서 모든 작업 처리
    return await this.db.transaction(async (trx) => {
      const user = await this.userService.findById(userId);  // 직접 호출
      const products = await this.productService.findByIds(items.map(i => i.productId));

      // 재고 확인 및 차감
      await this.productService.decreaseStock(items, trx);  // 같은 트랜잭션 공유!

      const order = await this.orderRepository.create({ userId, items }, trx);

      // 결제 처리
      await this.paymentService.process(order, trx);

      // 알림 전송 (같은 프로세스에서 실행)
      await this.notificationService.sendOrderConfirmation(user, order);

      return order;
    });
    // 하나라도 실패하면 전체 롤백 → ACID 보장 용이
  }
}

장단점

✅ 모놀리식 장점:

1. 개발/배포 단순
   - 단일 코드베이스로 관리
   - 하나의 빌드 파이프라인
   - 로컬 개발 환경 설정 단순

2. 트랜잭션 처리 용이
   - 단일 DB 사용으로 ACID 트랜잭션 자연스럽게 보장
   - 분산 트랜잭션 불필요

3. 서비스 간 통신 오버헤드 없음
   - 함수 호출 수준의 빠른 통신
   - 네트워크 레이턴시 없음

4. 디버깅/모니터링 단순
   - 단일 로그 스트림
   - 스택 트레이스 명확

5. 테스트 용이
   - 전체 시스템 통합 테스트 쉬움
   - 서비스 간 계약 테스트 불필요

❌ 모놀리식 단점:

1. 규모 커질수록 복잡성 증가
   - 코드베이스가 커질수록 빌드/배포 시간 증가
   - 팀 간 코드 충돌 증가

2. 부분 장애가 전체에 영향
   - 메모리 누수, 특정 기능 버그가 전체 다운 유발

3. 기술 스택 단일화
   - 특정 기능에 최적화된 언어/프레임워크 사용 어려움
   - 예: 데이터 처리에 Python, API에 Node.js 혼용 불가

4. 특정 기능만 확장 어려움
   - 검색 기능만 스케일아웃하고 싶어도 전체 서버 확장 필요

5. 긴 배포 주기
   - 작은 변경도 전체 재배포 필요
   - 큰 팀에서 배포 조율 어려움

마이크로서비스 아키텍처 (MSA)

구조

클라이언트
     ↓
┌─────────────────┐
│   API Gateway   │  ← 단일 진입점, 인증/라우팅/로드밸런싱
└────────┬────────┘
         │
    ┌────┴────────────────────────────┐
    ↓         ↓          ↓           ↓
┌───────┐ ┌───────┐ ┌────────┐ ┌──────────┐
│ User  │ │ Order │ │Payment │ │ Product  │
│Service│ │Service│ │Service │ │ Service  │
│:3001  │ │:3002  │ │:3003   │ │ :3004    │
└───┬───┘ └───┬───┘ └───┬────┘ └────┬─────┘
    │         │         │           │
┌───┴──┐  ┌──┴───┐  ┌──┴────┐  ┌──┴────┐
│ UserDB│  │OrderDB│  │PayDB  │  │ProdDB │
│(MySQL)│  │(MySQL)│  │(PG)   │  │(Mongo)│
└──────┘  └──────┘  └───────┘  └───────┘

서비스 간 통신: REST API 또는 메시지 큐(Kafka, RabbitMQ)

MSA 코드 예시

// 주문 서비스 (Order Service) - 독립적인 마이크로서비스
// 다른 서비스는 HTTP 요청 또는 메시지로 통신

class OrderService {
  constructor(
    private httpClient: HttpClient,
    private messageQueue: MessageQueue,
  ) {}

  async createOrder(userId: number, items: OrderItem[]): Promise<Order> {
    // 1. 사용자 서비스에 HTTP 요청 (네트워크 통신)
    const user = await this.httpClient.get(`http://user-service/users/${userId}`);

    // 2. 상품 서비스에 재고 확인 요청
    const products = await this.httpClient.post(
      'http://product-service/products/check-stock',
      { items }
    );

    // 3. 주문 생성 (자신의 DB에만 저장)
    const order = await this.orderRepository.create({ userId, items });

    // 4. 이벤트 발행 (다른 서비스가 구독하여 처리)
    await this.messageQueue.publish('order.created', {
      orderId: order.id,
      userId,
      items,
      totalAmount: order.total,
    });
    // Payment Service, Notification Service가 이 이벤트를 받아 각자 처리

    return order;
    // 분산 트랜잭션 필요 → Saga Pattern, 2PC 등 복잡한 처리 필요
  }
}
# Docker Compose로 MSA 로컬 환경 구성
version: '3.8'
services:
  api-gateway:
    image: nginx
    ports:
      - "80:80"
    depends_on:
      - user-service
      - order-service

  user-service:
    build: ./user-service
    ports:
      - "3001:3001"
    environment:
      - DB_URL=mysql://user-db:3306/users

  order-service:
    build: ./order-service
    ports:
      - "3002:3002"
    environment:
      - DB_URL=mysql://order-db:3306/orders

  payment-service:
    build: ./payment-service
    ports:
      - "3003:3003"

  kafka:
    image: confluentinc/cp-kafka
    ports:
      - "9092:9092"

장단점

✅ MSA 장점:

1. 독립 배포 및 확장
   - 결제 서비스만 배포하고 싶으면 결제 서비스만 재배포
   - 트래픽 많은 검색 서비스만 스케일아웃 가능

2. 기술 스택 자유
   - 각 서비스가 최적의 언어/DB 사용 가능
   - 검색: Elasticsearch, 결제: Java, 알림: Node.js

3. 장애 격리
   - 알림 서비스 다운되어도 주문은 정상 처리
   - 하나의 서비스 문제가 전체로 전파 안 됨

4. 팀 자율성 (Conway's Law 활용)
   - 각 팀이 독립적으로 서비스 개발/배포
   - 서비스 간 계약(API)만 지키면 됨

5. 점진적 기술 마이그레이션
   - 레거시 서비스를 하나씩 새 기술로 교체 가능

❌ MSA 단점:

1. 분산 시스템 복잡성
   - 네트워크 장애, 타임아웃, 재시도 로직 필요
   - 서비스 디스커버리 (Consul, Eureka)
   - 분산 트랜잭션 처리 (Saga Pattern)

2. 운영 오버헤드 증가
   - 각 서비스별 모니터링, 로깅, 추적 (Distributed Tracing)
   - Kubernetes, Service Mesh (Istio) 등 인프라 복잡성
   - 다수의 배포 파이프라인 관리

3. 데이터 일관성 어려움
   - 각 서비스가 자체 DB → 조인 불가
   - 이벤트 소싱(Event Sourcing), CQRS 패턴 필요

4. 높은 초기 비용
   - 인프라 설정 (K8s, 서비스 메시, API 게이트웨이)
   - 팀의 분산 시스템 지식 필요

5. 테스트 복잡성
   - 서비스 간 계약 테스트 (Consumer-Driven Contract Testing)
   - 통합 테스트 환경 구성 어려움

아키텍처 선택 기준

초기 스타트업/소규모 팀 → 모놀리식 추천
  - 빠른 개발 속도가 생존에 직결
  - MSA 운영 역량 부족
  - 비즈니스 도메인이 아직 불분명

성장기/중규모 팀 → 점진적 분리 고려
  - 특정 기능의 독립 배포 필요성 증가
  - 팀이 여러 개로 분화
  - 모놀리식의 병목 명확해짐

대규모 서비스/큰 팀 → MSA 고려
  - 트래픽이 특정 기능에 집중
  - 팀이 10개 이상
  - 배포 주기 단축이 비즈니스 요구사항

모놀리스 퍼스트 (Monolith First)

Martin Fowler의 권장 전략:

1단계: 모놀리식으로 시작
  - 빠른 개발, 도메인 이해 집중
  - "모듈식 모놀리스"로 내부 경계 명확히

2단계: 병목 식별 후 분리
  - 성능 병목 서비스 분리
  - 팀이 독립 개발 원하는 서비스 분리
  - 도메인 경계가 명확한 서비스부터 분리

3단계: 전체 MSA (필요시)
  - 대부분의 서비스가 독립 필요할 때
  - 전담 인프라 팀 확보 후

정리

항목모놀리식MSA
배포 단위전체 애플리케이션각 서비스 독립
트랜잭션ACID 보장 용이분산 트랜잭션 복잡
확장성전체 스케일아웃서비스별 스케일아웃
장애 영향전체에 영향격리 가능
기술 스택단일서비스별 자유
개발 복잡성낮음 (초기)높음 (분산 시스템)
운영 복잡성낮음높음 (K8s, 서비스 메시 등)
적합한 규모초기, 소~중규모성장 후, 대규모

핵심: "모놀리식으로 시작해서 MSA로 진화하라 (Monolith First)". 처음부터 MSA로 시작하면 불필요한 복잡성을 초기에 감당하게 됩니다. 서비스가 성장하고 도메인이 명확해진 후 점진적으로 분리하는 것이 더 실용적입니다.