Skip to content

Split View: 마이크로서비스 아키텍처 2025 완전 가이드: 모놀리스에서 MSA로, 그리고 다시 모듈러 모놀리스로

✨ Learn with Quiz
|

마이크로서비스 아키텍처 2025 완전 가이드: 모놀리스에서 MSA로, 그리고 다시 모듈러 모놀리스로

들어가며

2024년 Amazon Prime Video 팀이 마이크로서비스를 모놀리스로 되돌려 비용을 90% 절감했다는 발표는 업계에 큰 충격을 줬습니다. DHH(Ruby on Rails 창시자)는 "마이크로서비스는 대부분의 팀에게 과잉 엔지니어링"이라고 일갈했고, Shopify는 모듈러 모놀리스로 대규모 트래픽을 성공적으로 처리하고 있습니다.

그렇다고 마이크로서비스가 죽은 것은 아닙니다. Netflix, Uber, Spotify는 여전히 수천 개의 마이크로서비스를 운영하며, 조직 규모와 도메인 복잡성에 따라 MSA가 올바른 선택인 경우는 분명히 존재합니다.

이 글에서는 아키텍처 진화의 역사부터 2025년 현재의 트렌드까지, 모놀리스/MSA/모듈러 모놀리스를 객관적으로 비교하고, 실전에서 필요한 패턴과 기술을 체계적으로 다룹니다.


1. 아키텍처 진화의 역사

1.1 모놀리스 시대 (2000년대)

모든 기능이 하나의 배포 단위에 포함된 전통적인 아키텍처입니다.

┌─────────────────────────────────────┐
Monolith Application│  ┌─────────┐ ┌─────────┐ ┌───────┐ │
│  │  User    │ │  Order  │ │Payment│ │
│  │  Module  │ │  Module │ │Module │ │
│  └────┬─────┘ └────┬────┘ └──┬────┘ │
│       └──────┬─────┴─────────┘      │
│         ┌────▼────┐                 │
│         │  Shared │                 │
│         │   DB    │                 │
│         └─────────┘                 │
└─────────────────────────────────────┘

장점: 단순한 개발/배포, 트랜잭션 관리 용이, 디버깅 편리

단점: 코드베이스 비대화, 배포 병목, 기술 스택 고정, 확장성 한계

1.2 SOA 시대 (2005~2015)

Service-Oriented Architecture는 엔터프라이즈 서비스 버스(ESB)를 중심으로 서비스를 연결했습니다.

┌────────┐    ┌────────┐    ┌────────┐
│Service A│    │Service B│    │Service C└───┬─────┘    └───┬─────┘    └───┬─────┘
    └──────────────┼──────────────┘
              ┌────▼────┐
ESB              (MessageBus)              └─────────┘

SOA는 서비스 재사용성과 표준화를 추구했지만, ESB가 단일 장애점이 되고, SOAP/WSDL의 복잡성이 문제였습니다.

1.3 마이크로서비스 시대 (2014~현재)

Martin Fowler와 James Lewis가 정의한 마이크로서비스는 SOA의 진화형으로, 각 서비스가 독립적으로 배포/확장됩니다.

┌─────────┐   ┌─────────┐   ┌─────────┐
User    │   │ Order   │   │ PaymentService │   │ Service │   │ Service  :8080  │   │  :8081  │   │  :8082└──┬──────┘   └──┬──────┘   └──┬──────┘
REST/gRPC  │   Event   ▼             ▼            ▼
┌──────┐    ┌──────┐    ┌──────┐
│UserDB│    │OrderDB│   │PayDB │
└──────┘    └──────┘    └──────┘

1.4 2025년: 모듈러 모놀리스의 재부상

Timeline:
2000 ──── 2005 ──── 2014 ──── 2020 ──── 2025
 │         │         │         │         │
 ▼         ▼         ▼         ▼         ▼
Monolith  SOA    Microservices  서비스    Modular
                               메시 확산  Monolith
                                          재부상

2. MSA가 과잉인 경우: 현실적인 교훈

2.1 Amazon Prime Video 사례

2023년 Amazon Prime Video 팀은 오디오/비디오 모니터링 서비스를 마이크로서비스에서 모놀리스로 전환하여 인프라 비용을 90% 절감했습니다.

문제점:

  • AWS Step Functions의 상태 전환 비용이 폭발적으로 증가
  • 서비스 간 데이터 전달을 위한 S3 중간 저장 비용
  • 마이크로서비스 오케스트레이션 오버헤드

해결:

  • 단일 프로세스 내에서 모든 처리 단계를 실행
  • 네트워크 호출 대신 메모리 내 통신
  • S3 중간 저장 제거

2.2 DHH의 비판

Ruby on Rails 창시자 DHH는 "대부분의 팀은 모놀리스로 충분하다"라고 주장합니다.

주장근거
분산 시스템 복잡성 과소평가네트워크 장애, 데이터 정합성, 디버깅 난이도
팀 규모와 불일치5~10명 팀이 20개 서비스 운영은 비효율
운영 비용 폭발인프라, 모니터링, 배포 파이프라인 비용
조기 분리의 위험도메인 이해 부족 시 잘못된 경계 설정

2.3 Shopify의 모듈러 모놀리스 성공

Shopify는 연간 수십조 원의 거래를 처리하면서도 모듈러 모놀리스 아키텍처를 사용합니다.

# Shopify의 Packwerk를 이용한 모듈 경계 정의
# packages/checkout/package.yml
enforce_dependencies: true
enforce_privacy: true
dependencies:
  - packages/inventory
  - packages/payment

3. 아키텍처 비교: 모놀리스 vs MSA vs 모듈러 모놀리스

3.1 비교 매트릭스

항목모놀리스모듈러 모놀리스마이크로서비스
배포 단위1개1개 (모듈별 빌드)N개 (서비스별)
팀 규모1~20명5~50명20~수백명
통신메서드 호출모듈 인터페이스네트워크(REST/gRPC)
데이터 저장소공유 DB모듈별 스키마서비스별 DB
트랜잭션ACIDACID (모듈 간 제한)Saga/보상
확장성수직수직 + 부분 수평수평 (서비스별)
운영 복잡성낮음중간높음
기술 다양성단일 스택단일 스택폴리글랏
장애 격리없음부분적완전 격리
초기 비용낮음중간높음

3.2 모듈러 모놀리스 아키텍처 상세

┌─────────────────────────────────────────────┐
Modular Monolith│                                             │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐    │
│  │  User   │  │  Order  │  │ Payment │    │
│  │ Module  │  │ Module  │  │ Module  │    │
│  │         │  │         │  │         │    │
│  │ Public  │  │ Public  │  │ Public  │    │
│  │   API   │◄─►   API   │◄─►   API   │    │
│  │         │  │         │  │         │    │
│  │ Private │  │ Private │  │ Private │    │
│  │  Impl   │  │  Impl   │  │  Impl   │    │
│  └──┬──────┘  └──┬──────┘  └──┬──────┘    │
│     │            │            │            │
│  ┌──▼──┐     ┌──▼──┐     ┌──▼──┐         │
│  │user │     │order│     │pay  │         │
│  │schema│    │schema│    │schema│         │
│  └─────┘     └─────┘     └─────┘         │
│     └────────────┼────────────┘            │
│           ┌──────▼──────┐                  │
│           │  Shared DB  │                  │
 (분리 스키마)│                  │
│           └─────────────┘                  │
└─────────────────────────────────────────────┘
// Java 모듈러 모놀리스 예시 (Spring Modulith)
@ApplicationModule(
    allowedDependencies = {"order", "shared"}
)
package com.example.payment;

// 모듈 간 통신은 이벤트로
@Service
public class PaymentService {

    private final ApplicationEventPublisher events;

    @Transactional
    public PaymentResult processPayment(PaymentRequest request) {
        Payment payment = Payment.create(request);
        paymentRepository.save(payment);

        // 모듈 간 이벤트 발행 (직접 의존 없음)
        events.publishEvent(new PaymentCompletedEvent(
            payment.getId(),
            payment.getOrderId(),
            payment.getAmount()
        ));

        return PaymentResult.success(payment);
    }
}

3.3 선택 기준 플로차트

시작: 새 프로젝트
팀 규모가 50명 이상인가? ──Yes──▶ 도메인이 명확히 분리되어 있는가?
  │                                    │
  No                                  Yes ──▶ MSA 고려
  │                                    │
No ──▶ 모듈러 모놀리스
독립적 확장이 필요한 서비스가 있는가?
  Yes ──▶ 해당 서비스만 분리 (하이브리드)
  No ──▶ 모놀리스 또는 모듈러 모놀리스

4. DDD와 서비스 분해

4.1 Bounded Context 식별

Domain-Driven Design에서 Bounded Context는 마이크로서비스의 자연스러운 경계입니다.

┌─────────────── E-Commerce Domain ───────────────┐
│                                                  │
│  ┌──────────────┐    ┌──────────────┐           │
│  │  Catalog     │    │  Order       │           │
│  │  Context     │    │  Context     │           │
│  │              │    │              │           │
│  │ - Product    │    │ - Order      │           │
│  │ - Category   │    │ - OrderItem  │           │
│  │ - Price      │    │ - Shipment   │           │
│  └──────────────┘    └──────────────┘           │
│                                                  │
│  ┌──────────────┐    ┌──────────────┐           │
│  │  Identity    │    │  Payment     │           │
│  │  Context     │    │  Context     │           │
│  │              │    │              │           │
│  │ - User       │    │ - Payment    │           │
│  │ - Role       │    │ - Refund     │           │
│  │ - Permission │    │ - Invoice    │           │
│  └──────────────┘    └──────────────┘           │
└──────────────────────────────────────────────────┘

4.2 Context Mapping 패턴

┌────────────┐                    ┌────────────┐
Upstream  │                    │ DownstreamContext   │ ──Conformist────▶  │  Context│            │                    │            │
  (Order)   │ ──ACL──────────▶   (Payment)│            │                    │            │
│            │ ──OHS/PL───────▶   (Shipping)└────────────┘                    └────────────┘

ACL: Anti-Corruption Layer (번역 계층)
OHS: Open Host Service (공개 API)
PL:  Published Language (공유 스키마)

4.3 서비스 분해 전략

// 1. Aggregate 기준 분해
// Order Aggregate Root
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderLine> lines;
    private OrderStatus status;
    private Money totalAmount;

    // Aggregate 내부의 비즈니스 로직
    public void addItem(ProductId productId, int quantity, Money price) {
        OrderLine line = new OrderLine(productId, quantity, price);
        this.lines.add(line);
        this.totalAmount = calculateTotal();
    }

    public void confirm() {
        if (this.lines.isEmpty()) {
            throw new OrderException("빈 주문은 확정할 수 없습니다");
        }
        this.status = OrderStatus.CONFIRMED;
    }
}

4.4 분해 시 피해야 할 실수

  1. 데이터 기준 분해: 테이블 단위로 서비스를 나누면 과도한 통신 발생
  2. 기술 기준 분해: 프론트엔드/백엔드/DB 레이어로 분리하면 MSA의 이점 없음
  3. 조기 분해: 도메인 이해가 부족할 때 분리하면 잘못된 경계 설정
  4. 너무 세분화: 나노서비스(Nanoservice)는 운영 부담만 증가

5. 통신 패턴: REST vs gRPC vs 이벤트 기반

5.1 동기 통신 비교

항목REST (HTTP/JSON)gRPC (HTTP/2 + Protobuf)
직렬화JSON (텍스트)Protocol Buffers (바이너리)
성능상대적 느림2~10배 빠름
스트리밍제한적 (SSE/WebSocket)네이티브 양방향 스트리밍
코드 생성OpenAPI (선택적)필수 (proto 파일)
브라우저 지원네이티브gRPC-Web 필요
가독성높음 (JSON)낮음 (바이너리)
사용 케이스외부 API, 단순 CRUD서비스 간 내부 통신

5.2 gRPC 서비스 정의

// order_service.proto
syntax = "proto3";

package order.v1;

service OrderService {
  // 단항 RPC
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);

  // 서버 스트리밍 - 주문 상태 업데이트
  rpc WatchOrderStatus (WatchRequest) returns (stream OrderStatusUpdate);

  // 클라이언트 스트리밍 - 대량 주문 등록
  rpc BulkCreateOrders (stream CreateOrderRequest) returns (BulkCreateResponse);

  // 양방향 스트리밍 - 실시간 주문 처리
  rpc ProcessOrders (stream OrderAction) returns (stream OrderResult);
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
  ShippingAddress shipping = 3;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  int64 price_cents = 3;
}

message CreateOrderResponse {
  string order_id = 1;
  OrderStatus status = 2;
  int64 total_cents = 3;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_DELIVERED = 4;
}

5.3 이벤트 기반 비동기 통신

Producer                         Consumer
┌─────────┐    ┌─────────┐    ┌─────────┐
Order   │───▶│  Kafka  │───▶│ PaymentService │    │ Topic:  │    │ Service│         │    │ orders  │    │         │
└─────────┘    └─────────┘    └─────────┘
                    ├────────▶ ┌─────────┐
                    │          │Inventory│
                    │          │ Service                    │          └─────────┘
                    └────────▶ ┌─────────┐
                               │Notific-                               │ation    │
Service                               └─────────┘
// Kafka Producer - 주문 이벤트 발행
@Service
public class OrderEventPublisher {

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void publishOrderCreated(Order order) {
        OrderCreatedEvent event = OrderCreatedEvent.builder()
            .orderId(order.getId())
            .customerId(order.getCustomerId())
            .items(order.getItems())
            .totalAmount(order.getTotalAmount())
            .timestamp(Instant.now())
            .build();

        kafkaTemplate.send("orders.created", order.getId(), event)
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    log.error("이벤트 발행 실패: orderId={}",
                        order.getId(), ex);
                    // 보상 로직 또는 재시도
                }
            });
    }
}

// Kafka Consumer - 결제 처리
@Service
public class PaymentEventConsumer {

    @KafkaListener(
        topics = "orders.created",
        groupId = "payment-service",
        containerFactory = "kafkaListenerContainerFactory"
    )
    public void handleOrderCreated(OrderCreatedEvent event) {
        log.info("주문 이벤트 수신: orderId={}", event.getOrderId());
        paymentService.processPayment(event);
    }
}

5.4 통신 패턴 선택 가이드

동기 호출이 필요한가?
  Yes ──▶ 외부 클라이언트 대상인가?
  │          │
Yes ──▶ REST (OpenAPI)
  │          │
No ──▶ 고성능 필요? ──Yes──▶ gRPC
  │                    │
No ──▶ REST
  No ──▶ 순서 보장이 필요한가?
          Yes ──▶ Kafka (파티션 키로 순서 보장)
          No ──▶ 팬아웃이 필요한가?
                  Yes ──▶ SNS + SQS / Kafka 토픽
                  No ──▶ SQS / RabbitMQ

6. Service Mesh: Istio vs Linkerd

6.1 Service Mesh란?

서비스 메시는 마이크로서비스 간 통신을 인프라 레벨에서 관리하는 전용 계층입니다.

Without Service Mesh:          With Service Mesh:

┌─────────┐  ┌─────────┐     ┌─────────┐  ┌─────────┐
│Service A│──│Service B│     │Service A│  │Service B│         │  │         │     │ ┌─────┐ │  │ ┌─────┐ │
(retry,(retry,  │     │ │Proxy│─┼──┼─│Proxy│ │
│ auth,   │  │ auth,   │     │ (side│ │  │ (side│ │
│ metrics)│  │ metrics)│     │ │car) │ │  │ │car) │ │
└─────────┘  └─────────┘     │ └─────┘ │  │ └─────┘ │
                              └─────────┘  └─────────┘
   모든 서비스에                   통신 관심사를
   통신 로직 구현                  인프라로 위임

6.2 주요 기능

기능설명
트래픽 관리로드 밸런싱, 라우팅, 카나리 배포
보안mTLS 자동 적용, 인증/인가
관측성분산 추적, 메트릭 수집, 로그
복원력재시도, 서킷 브레이커, 타임아웃

6.3 Istio vs Linkerd 비교

항목IstioLinkerd
프록시Envoy (C++)linkerd2-proxy (Rust)
리소스 사용량높음 (100~200MB/pod)낮음 (20~30MB/pod)
기능 범위포괄적 (VM 지원 등)핵심 기능 집중
학습 곡선가파름완만함
mTLS수동 설정 필요기본 활성화
CNCF 등급졸업 프로젝트졸업 프로젝트
권장 규모대규모 (100+ 서비스)중소규모 (10~100 서비스)

6.4 Istio 트래픽 관리 예시

# VirtualService - 카나리 배포
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: order-service
spec:
  hosts:
    - order-service
  http:
    - match:
        - headers:
            x-canary:
              exact: "true"
      route:
        - destination:
            host: order-service
            subset: v2
    - route:
        - destination:
            host: order-service
            subset: v1
          weight: 90
        - destination:
            host: order-service
            subset: v2
          weight: 10
---
# DestinationRule - 서킷 브레이커
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service
spec:
  host: order-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

6.5 Service Mesh 없이도 되는 경우

  • 서비스 수가 10개 미만
  • Kubernetes의 기본 기능으로 충분할 때
  • 팀에 서비스 메시 운영 경험이 없을 때
  • 지연 시간에 매우 민감한 시스템 (사이드카 오버헤드)

7. 분산 트랜잭션과 Saga 패턴

7.1 분산 트랜잭션의 문제

마이크로서비스에서는 각 서비스가 자체 DB를 가지므로, 여러 서비스에 걸친 ACID 트랜잭션이 불가능합니다.

주문 생성 시나리오:
1. Order Service: 주문 생성
2. Inventory Service: 재고 차감
3. Payment Service: 결제 처리
4. Notification Service: 알림 발송

만약 3단계에서 결제 실패하면?
-> 1, 2단계 롤백 필요!

7.2 Saga 패턴 - 코레오그래피 방식

┌─────────┐  OrderCreated  ┌─────────┐  StockReserved  ┌─────────┐
Order  │──────────────▶│Inventory│───────────────▶│ PaymentService │               │ Service │                │ Service└─────────┘               └─────────┘                └─────────┘
     ▲                         ▲                          │
StockReleasePaymentFailed         (보상)               │◀──────────────────────────┘
     │◀────────────────────────┘
OrderCancelled (보상)

각 서비스가 이벤트를 구독하고 자체적으로 다음 단계를 트리거합니다.

7.3 Saga 패턴 - 오케스트레이션 방식

                    ┌────────────────┐
Order SagaOrchestrator                    └───┬──┬──┬──┬──┘
           1.주문생성  │  │  │  │  4.알림
                ┌──────┘  │  │  └──────┐
                ▼         │  │         ▼
          ┌─────────┐     │  │   ┌──────────┐
Order  │     │  │   │Notificat-Service │     │  │   │ion Svc          └─────────┘     │  │   └──────────┘
              2.재고차감   │  │  3.결제
                    ┌─────┘  └─────┐
                    ▼              ▼
              ┌─────────┐   ┌─────────┐
              │Inventory│   │ PaymentService │   │ Service              └─────────┘   └─────────┘
// Saga Orchestrator 구현
@Service
public class OrderSagaOrchestrator {

    public Mono<OrderResult> createOrder(CreateOrderCommand command) {
        return Mono.just(new SagaState(command))
            // Step 1: 주문 생성
            .flatMap(state -> orderService.createOrder(state.getCommand())
                .map(order -> state.withOrder(order)))

            // Step 2: 재고 예약
            .flatMap(state -> inventoryService
                .reserveStock(state.getOrder())
                .map(reservation -> state.withReservation(reservation))
                .onErrorResume(e -> compensateOrder(state, e)))

            // Step 3: 결제 처리
            .flatMap(state -> paymentService
                .processPayment(state.getOrder())
                .map(payment -> state.withPayment(payment))
                .onErrorResume(e ->
                    compensateReservation(state, e)))

            // Step 4: 완료
            .flatMap(state -> {
                orderService.confirmOrder(
                    state.getOrder().getId());
                return Mono.just(OrderResult.success(state));
            });
    }

    private Mono<SagaState> compensateReservation(
            SagaState state, Throwable e) {
        log.warn("결제 실패, 재고 보상 시작: orderId={}",
            state.getOrder().getId());
        return inventoryService
            .releaseStock(state.getReservation())
            .then(compensateOrder(state, e));
    }

    private Mono<SagaState> compensateOrder(
            SagaState state, Throwable e) {
        log.warn("주문 보상 시작: orderId={}",
            state.getOrder().getId());
        return orderService
            .cancelOrder(state.getOrder().getId())
            .then(Mono.error(
                new SagaFailedException("Saga 실패", e)));
    }
}

7.4 코레오그래피 vs 오케스트레이션

항목코레오그래피오케스트레이션
결합도느슨함중앙 조정자 의존
복잡도서비스 수 증가 시 높음일정하게 유지
디버깅어려움 (이벤트 추적)비교적 쉬움
단일 장애점없음오케스트레이터
적합한 경우3~4단계 이하 단순 플로우5+ 단계 복잡 플로우

8. 관측성: OpenTelemetry

8.1 관측성의 3가지 축

         ┌─────────────┐
Observability│
         └──────┬──────┘
     ┌──────────┼──────────┐
     ▼          ▼          ▼
┌─────────┐ ┌──────┐ ┌─────────┐
Logs   │ │Metrics│ │ Traces(이벤트)(수치)(요청흐름)└─────────┘ └──────┘ └─────────┘
 무엇이       얼마나     어디서
 발생했는가   발생하는가  발생했는가

8.2 OpenTelemetry 통합 아키텍처

┌─────────────────────────────────────────────────────┐
Application│  ┌─────────────────────────────────────────────┐   │
│  │        OpenTelemetry SDK                    │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐   │   │
│  │  │  Tracer  │ │  Meter   │ │  Logger  │   │   │
│  │  └──────────┘ └──────────┘ └──────────┘   │   │
│  └────────────────────┬────────────────────────┘   │
└───────────────────────┼─────────────────────────────┘
OTLP
              ┌─────────────────┐
OTel Collector              │  ┌───────────┐  │
              │  │ Receivers │  │
              │  │Processors│  │
              │  │ Exporters │  │
              │  └───────────┘  │
              └────┬──┬──┬──────┘
                   │  │  │
          ┌────────┘  │  └────────┐
          ▼           ▼           ▼
     ┌─────────┐ ┌────────┐ ┌─────────┐
Jaeger │ │Promethe│ │  Loki      (Traces)│ │us/Mimir│  (Logs)     │         │ (Metrics│ │         │
     └─────────┘ └────────┘ └─────────┘
          │           │           │
          └─────┬─────┘───────────┘
          ┌───────────┐
Grafana          (Dashboard)          └───────────┘

8.3 분산 추적 구현

// Spring Boot + OpenTelemetry 자동 설정
// build.gradle
// implementation 'io.opentelemetry.instrumentation:
//   opentelemetry-spring-boot-starter'

// 커스텀 스팬 생성
@Service
public class OrderService {

    private final Tracer tracer;

    public Order createOrder(CreateOrderRequest request) {
        Span span = tracer.spanBuilder("order.create")
            .setAttribute("order.customer_id",
                request.getCustomerId())
            .setAttribute("order.item_count",
                request.getItems().size())
            .startSpan();

        try (Scope scope = span.makeCurrent()) {
            // 주문 생성 로직
            Order order = processOrder(request);

            span.setAttribute("order.id", order.getId());
            span.setAttribute("order.total",
                order.getTotal().doubleValue());
            span.setStatus(StatusCode.OK);

            return order;
        } catch (Exception e) {
            span.setStatus(StatusCode.ERROR, e.getMessage());
            span.recordException(e);
            throw e;
        } finally {
            span.end();
        }
    }
}

8.4 핵심 메트릭 (RED/USE)

RED Method (서비스 관점):
┌──────────────────────────────────────┐
R - Rate:     초당 요청 수           │
E - Errors:   에러율 (%)D - Duration: 응답 시간 (p50/p95/p99)└──────────────────────────────────────┘

USE Method (리소스 관점):
┌──────────────────────────────────────┐
U - Utilization: 리소스 사용률       │
S - Saturation:  대기열 길이         │
E - Errors:      리소스 에러 수      │
└──────────────────────────────────────┘

9. API Gateway 패턴

9.1 API Gateway의 역할

                    ┌─────────────────────┐
API Gateway   Client ────────▶│                     │
- 인증/인가          │
- Rate Limiting- 요청 라우팅        │
- 로드 밸런싱        │
- 응답 캐싱          │
- 요청/응답 변환     │
- Circuit Breaker- 로깅/모니터링      │
                    └──┬──────┬──────┬────┘
                       │      │      │
                       ▼      ▼      ▼
                    ┌─────┐┌─────┐┌─────┐
                    │Svc A││Svc B││Svc C                    └─────┘└─────┘└─────┘

9.2 BFF (Backend for Frontend) 패턴

┌────────┐     ┌──────────────┐
Web   │────▶│  Web BFF     │──┐
Client(GraphQL)     │  │
└────────┘     └──────────────┘  │
                                 │    ┌──────────┐
┌────────┐     ┌──────────────┐  ├───▶│ OrderMobile │────▶│ Mobile BFF   │──┤    │ ServiceApp(REST, 경량)  │  │    └──────────┘
└────────┘     └──────────────┘  │
                                 │    ┌──────────┐
┌────────┐     ┌──────────────┐  ├───▶│ UserIoT   │────▶│  IoT BFF     │──┤    │ ServiceDevice(MQTT 변환)   │  │    └──────────┘
└────────┘     └──────────────┘  │
                                 │    ┌──────────┐
                                 └───▶│ ProductService                                      └──────────┘

9.3 주요 API Gateway 비교

항목KongAWS API GatewayEnvoy GatewayAPISIX
기반Nginx + LuaAWS 관리형Envoy ProxyNginx + Lua
배포셀프호스팅/클라우드AWS 전용Kubernetes셀프호스팅
프로토콜REST, gRPC, WSREST, WS, HTTPREST, gRPCREST, gRPC, MQTT
플러그인풍부함Lambda 통합확장 가능풍부함
가격오픈소스/엔터프라이즈요청당 과금오픈소스오픈소스

10. 배포 전략

10.1 블루-그린 배포

Phase 1: Blue(현재) 운영 중
┌─────────────┐     ┌──────────┐
Load        │────▶│ Blue v1    (100% 트래픽)
Balancer (Active)└─────────────┘     └──────────┘
                    ┌──────────┐
Green v2   (대기 중)
                     (Idle)                    └──────────┘

Phase 2: Green으로 전환
┌─────────────┐     ┌──────────┐
Load        │     │ Blue v1    (대기)
Balancer (Idle)└─────────────┘     └──────────┘
       │            ┌──────────┐
       └───────────▶│ Green v2   (100% 트래픽)
                     (Active)                    └──────────┘

10.2 카나리 배포

# Kubernetes + Argo Rollouts 카나리 배포
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: order-service
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 5
        - pause:
            duration: 5m
        - analysis:
            templates:
              - templateName: success-rate
            args:
              - name: service-name
                value: order-service
        - setWeight: 25
        - pause:
            duration: 10m
        - analysis:
            templates:
              - templateName: success-rate
        - setWeight: 50
        - pause:
            duration: 15m
        - setWeight: 100
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: order-service:v2
          ports:
            - containerPort: 8080

10.3 배포 전략 비교

전략다운타임롤백 속도리소스 비용위험도
롤링 업데이트없음보통낮음중간
블루-그린없음즉시높음 (2배)낮음
카나리없음빠름중간낮음
A/B 테스트없음빠름중간낮음
재생성있음느림낮음높음

11. 마이그레이션: Strangler Fig 패턴

11.1 패턴 개요

교살자 무화과 패턴은 레거시 모놀리스를 점진적으로 마이크로서비스로 전환하는 전략입니다. 무화과나무가 숙주 나무를 감싸며 자라듯이, 새로운 서비스가 레거시를 점진적으로 대체합니다.

Phase 1: 프록시 배치
┌────────┐     ┌──────────┐     ┌────────────┐
Client │────▶│  Proxy   │────▶│  Monolith└────────┘     (Facade)  (모든기능)               └──────────┘     └────────────┘

Phase 2: 일부 기능 분리
┌────────┐     ┌──────────┐     ┌────────────┐
Client │────▶│  Proxy   │─┬──▶│  Monolith└────────┘     │          │ │    (기능 축소)               └──────────┘ │   └────────────┘
                            └──▶┌────────────┐
New Service│
                                 (분리된기능)                                └────────────┘

Phase 3: 대부분 기능 이관
┌────────┐     ┌──────────┐     ┌────────────┐
Client │────▶│  Proxy   │─┬──▶│  Monolith└────────┘     │          │ │    (최소 기능)               └──────────┘ │   └────────────┘
                            ├──▶┌────────────┐
                            │   │ Service A                            │   └────────────┘
                            ├──▶┌────────────┐
                            │   │ Service B                            │   └────────────┘
                            └──▶┌────────────┐
Service C                                └────────────┘

Phase 4: 모놀리스 제거
┌────────┐     ┌──────────┐
Client │────▶│  API GW  │─┬──▶ Service A
└────────┘     └──────────┘ ├──▶ Service B
                            └──▶ Service C

11.2 마이그레이션 단계

1. 분석 (2~4)
   ├── 도메인 모델링 (Event Storming)
   ├── 의존성 분석 (코드/데이터)
   └── 우선순위 결정

2. 기반 구축 (4~8)
   ├── CI/CD 파이프라인
   ├── 컨테이너/오케스트레이션 환경
   ├── 관측성 스택
   └── API Gateway 배치

3. 첫 서비스 분리 (4~6)
   ├── 가장 독립적인 도메인 선택
   ├── Anti-Corruption Layer 구현
   ├── 데이터 마이그레이션
   └── 듀얼 라이트 / 이벤트 브리지

4. 반복 분리 (서비스당 2~4)
   ├── 다음 서비스 분리
   ├── 모놀리스 코드 제거
   └── 통합 테스트

5. 모놀리스 제거
   ├── 잔여 기능 이관
   ├── 데이터 정리
   └── 인프라 해제

11.3 데이터 마이그레이션 전략

// Dual Write 패턴 - 전환 기간 동안 양쪽에 쓰기
@Service
public class UserServiceMigration {

    private final MonolithUserRepository monolithRepo;
    private final NewUserServiceClient newServiceClient;
    private final FeatureFlag featureFlag;

    public User createUser(CreateUserRequest request) {
        // 항상 모놀리스에 쓰기 (기존)
        User user = monolithRepo.save(toEntity(request));

        // 새 서비스에도 쓰기 (마이그레이션)
        try {
            newServiceClient.createUser(
                toNewServiceRequest(user));
        } catch (Exception e) {
            log.warn("새 서비스 동기화 실패, 나중에 재시도", e);
            migrationQueue.enqueue(new SyncEvent(user));
        }

        return user;
    }

    public User getUser(String userId) {
        // Feature Flag로 읽기 전환
        if (featureFlag.isEnabled("read-from-new-service")) {
            try {
                return newServiceClient.getUser(userId);
            } catch (Exception e) {
                log.warn("새 서비스 읽기 실패, 폴백", e);
                return monolithRepo.findById(userId);
            }
        }
        return monolithRepo.findById(userId);
    }
}

12. 안티패턴

12.1 대표적인 MSA 안티패턴

안티패턴설명해결책
분산 모놀리스서비스를 나눴지만 강결합 유지Bounded Context 재설정
나노서비스너무 작은 서비스 (함수 단위)서비스 병합, 관련 기능 그룹화
공유 DB여러 서비스가 같은 DB 직접 접근DB per Service 원칙
동기 체인A에서 B, C, D 순차 동기 호출이벤트 기반 비동기화
골든 해머모든 문제에 MSA 적용적합성 평가 후 아키텍처 선택
버전 지옥API 버전이 난립하위 호환성 유지, 계약 테스트

12.2 분산 모놀리스 징후

분산 모놀리스 체크리스트:
[ ] 하나의 서비스를 배포하려면 다른 서비스도 함께 배포해야 한다
[ ] 서비스 간 동기 호출이 3단계 이상 연쇄된다
[ ] 여러 서비스가 같은 데이터베이스를 직접 읽고 쓴다
[ ] 서비스 간 공유 라이브러리의 버전을 맞춰야 한다
[ ] 하나의 서비스 장애가 전체 시스템을 다운시킨다
[ ] 서비스 경계가 기술 레이어(프론트//DB)로 나뉘어 있다

3개 이상 해당되면 분산 모놀리스일 가능성이 높습니다.

12.3 동기 호출 체인 문제

문제: 동기 체인
Client -> Order -> Inventory -> Payment -> Shipping
                                           (3)
         전체 응답시간: 3+ 각 서비스 처리시간
         하나라도 실패하면 전체 실패

해결: 비동기 이벤트 + 응답은 즉시
Client -> Order (200 OK, 주문 접수됨)
              |
              +-- Event: OrderCreated
              |        |
              |        +-- Inventory (비동기 처리)
              |        +-- Payment (비동기 처리)
              |        +-- Shipping (비동기 처리)
              |
              +-- 상태 조회 API 제공

13. 의사결정 프레임워크

13.1 아키텍처 선택 매트릭스

다음 질문에 점수를 매기세요 (1~5점).

질문1점 (낮음)5점 (높음)
팀 규모5명 이하50명 이상
도메인 복잡성단순 CRUD복잡한 비즈니스 로직
독립적 확장 필요성전체 동일 부하서비스별 극도로 다른 부하
배포 빈도월 1회일 수십 회
기술 다양성 필요단일 스택서비스별 최적 기술 필요
팀 자율성중앙 집중팀별 독립 운영
운영 역량DevOps 초보성숙한 플랫폼 팀

점수 해석:

  • 7~15점: 모놀리스 (또는 모듈러 모놀리스)
  • 16~25점: 모듈러 모놀리스 (또는 하이브리드)
  • 26~35점: 마이크로서비스

13.2 기술 스택 추천

모놀리스:
├── Spring Boot + JPA
├── Django + PostgreSQL
├── Rails + PostgreSQL
└── Next.js Full Stack

모듈러 모놀리스:
├── Spring Modulith
├── .NET Aspire
├── Go (모듈 패턴)
└── Rust (Workspace)

마이크로서비스:
├── 통신: gRPC (내부) + REST (외부)
├── 메시징: Kafka / RabbitMQ
├── 오케스트레이션: Kubernetes
├── 서비스 메시: Istio / Linkerd
├── 관측성: OpenTelemetry + Grafana
├── CI/CD: ArgoCD / GitHub Actions
└── API Gateway: Kong / Envoy Gateway

14. 면접 대비 Q&A (15선)

Q1. 마이크로서비스의 핵심 특성 5가지를 설명하세요.

  1. 단일 책임: 각 서비스가 하나의 비즈니스 기능 담당
  2. 독립 배포: 다른 서비스와 무관하게 배포 가능
  3. 분산 데이터: 서비스별 자체 데이터 저장소 보유
  4. 기술 다양성: 서비스별 최적의 기술 스택 선택 가능
  5. 장애 격리: 한 서비스 장애가 전체 시스템에 전파되지 않음

Q2. CAP 정리와 마이크로서비스의 관계를 설명하세요.

CAP 정리에 따르면 분산 시스템은 일관성(Consistency), 가용성(Availability), 분할 내성(Partition Tolerance) 중 2가지만 보장할 수 있습니다. 마이크로서비스는 네트워크 분할이 불가피하므로 P를 항상 선택해야 하며, AP(가용성 우선) 또는 CP(일관성 우선) 중 선택합니다. 대부분의 MSA는 최종 일관성(Eventual Consistency)을 수용하는 AP 시스템을 선택합니다.

Q3. Saga 패턴의 두 가지 구현 방식과 차이점은?

코레오그래피: 각 서비스가 이벤트를 발행/구독하여 자율적으로 다음 단계를 수행합니다. 중앙 조정자가 없어 결합도가 낮지만, 서비스가 많아지면 이벤트 흐름 추적이 어렵습니다.

오케스트레이션: 중앙 오케스트레이터가 각 서비스를 순서대로 호출하고 보상 로직을 관리합니다. 플로우가 명확하지만 오케스트레이터가 단일 장애점이 될 수 있습니다.

Q4. Service Mesh의 Sidecar 패턴을 설명하세요.

각 서비스 Pod에 프록시 컨테이너(사이드카)를 함께 배치합니다. 서비스의 모든 인바운드/아웃바운드 트래픽이 사이드카를 통과하며, 인증(mTLS), 라우팅, 재시도, 관측성 등의 횡단 관심사를 애플리케이션 코드 수정 없이 처리합니다. Istio는 Envoy 프록시를, Linkerd는 linkerd2-proxy를 사이드카로 사용합니다.

Q5. 분산 추적(Distributed Tracing)의 원리를 설명하세요.

요청이 시스템에 진입할 때 고유한 Trace ID가 생성됩니다. 각 서비스는 이 Trace ID를 전파하며, 자신의 처리 단위를 Span으로 기록합니다. 부모-자식 관계로 연결된 Span들이 모여 하나의 Trace를 구성하고, 이를 통해 전체 요청 경로와 각 구간의 소요 시간을 시각화할 수 있습니다. W3C Trace Context 표준 헤더를 사용합니다.

Q6. Strangler Fig 패턴의 장점과 주의사항은?

장점: 점진적 전환으로 위험 최소화, 빅뱅 마이그레이션 회피, 모놀리스와 새 서비스 공존 가능, 롤백 용이

주의사항: 전환 기간 동안 두 시스템 동시 운영 비용, 데이터 동기화 복잡성, 라우팅 규칙 관리 부담, 전환 완료 시점 결정의 어려움

Q7. API Gateway와 Service Mesh의 차이는?

API Gateway: 외부 트래픽(North-South)을 관리합니다. 인증, Rate Limiting, 요청 변환, API 집약 등 에지 기능을 담당합니다.

Service Mesh: 내부 서비스 간 트래픽(East-West)을 관리합니다. mTLS, 서비스 디스커버리, 로드 밸런싱, 서킷 브레이커 등 서비스 간 통신을 담당합니다.

둘은 보완 관계이며, 대규모 MSA에서는 함께 사용합니다.

Q8. Event Sourcing과 CQRS의 관계를 설명하세요.

Event Sourcing: 상태를 직접 저장하는 대신, 상태 변경 이벤트의 시퀀스를 저장합니다. 현재 상태는 이벤트를 리플레이하여 재구성합니다.

CQRS: 명령(쓰기)과 조회(읽기) 모델을 분리합니다. 쓰기는 이벤트 스토어에, 읽기는 최적화된 뷰(Read Model)에서 처리합니다. Event Sourcing은 CQRS 없이도 사용 가능하지만, 함께 사용하면 쓰기 성능과 읽기 성능을 독립적으로 최적화할 수 있습니다.

Q9. 마이크로서비스에서 데이터 정합성을 어떻게 보장하나요?

강한 일관성 대신 최종 일관성(Eventual Consistency)을 수용합니다. 구체적 패턴으로는 Saga 패턴(분산 트랜잭션), Outbox 패턴(이벤트 발행 보장), Change Data Capture(DB 변경 감지), 멱등성 보장(중복 처리 안전)을 사용합니다. 데이터 정합성이 반드시 필요한 경우에는 해당 기능들을 같은 서비스에 배치합니다.

Q10. gRPC가 REST보다 유리한 시나리오는?

서비스 간 내부 통신에서 높은 처리량이 필요할 때, 양방향 스트리밍이 필요할 때, 강타입 계약(proto 파일)이 중요할 때, 바이너리 직렬화로 페이로드 크기를 줄여야 할 때 유리합니다. 반면, 브라우저 클라이언트, 서드파티 개발자용 공개 API, 단순 CRUD에서는 REST가 더 적합합니다.

Q11. Circuit Breaker 패턴을 설명하세요.

전기 회로의 차단기처럼, 하위 서비스 장애 시 호출을 차단하여 장애 전파를 방지합니다. 세 가지 상태가 있습니다. Closed(정상 호출), Open(호출 차단, 즉시 실패 반환), Half-Open(일부 요청으로 복구 확인). 연속 실패가 임계값을 넘으면 Open으로 전환하고, 일정 시간 후 Half-Open에서 복구를 테스트합니다.

Q12. 서비스 디스커버리 방식을 비교하세요.

클라이언트 사이드 디스커버리: 클라이언트가 서비스 레지스트리(Eureka, Consul)에 직접 질의하여 대상 인스턴스를 선택합니다. 로드 밸런싱 로직이 클라이언트에 있습니다.

서버 사이드 디스커버리: 로드 밸런서가 서비스 레지스트리를 참조하여 라우팅합니다. Kubernetes의 Service가 대표적입니다. 클라이언트가 디스커버리 로직을 알 필요가 없어 더 단순합니다.

Q13. 모듈러 모놀리스에서 MSA로 전환하는 기준은?

다음 조건이 충족될 때 전환을 고려합니다. 특정 모듈의 확장 요구가 다른 모듈과 현저히 다를 때, 팀 규모가 50명 이상으로 성장하여 독립 배포가 필요할 때, 특정 모듈에 다른 기술 스택이 필요할 때, 장애 격리가 반드시 필요한 비즈니스 요구가 있을 때입니다.

Q14. Outbox 패턴이란 무엇인가요?

로컬 트랜잭션에서 비즈니스 데이터와 함께 이벤트를 Outbox 테이블에 저장합니다. 별도의 프로세스(Polling Publisher 또는 CDC)가 Outbox 테이블에서 이벤트를 읽어 메시지 브로커에 발행합니다. 이를 통해 비즈니스 로직과 이벤트 발행의 원자성을 보장합니다.

Q15. 마이크로서비스 테스트 전략(Testing Pyramid)을 설명하세요.

단위 테스트(서비스 내부 로직), 통합 테스트(DB/외부 연동), 계약 테스트(서비스 간 API 호환성, Pact 등), 컴포넌트 테스트(단일 서비스 E2E), E2E 테스트(전체 시스템)로 구성합니다. 계약 테스트가 특히 중요한데, 프로바이더 서비스의 변경이 컨슈머를 깨뜨리지 않는지 자동으로 검증합니다.


15. 퀴즈

Q1. 모듈러 모놀리스의 주요 장점 3가지와 MSA 대비 한계 2가지를 설명하세요.

장점: (1) MSA의 복잡성(네트워크 통신, 분산 트랜잭션, 서비스 메시) 없이 모듈 단위 분리 가능, (2) ACID 트랜잭션 사용 가능으로 데이터 정합성 보장 용이, (3) 단일 배포 단위로 운영/배포 간편.

한계: (1) 서비스별 독립 확장 불가(특정 모듈만 수평 확장 어려움), (2) 기술 스택이 단일 언어/프레임워크로 제한됨.

Q2. Saga 패턴에서 보상 트랜잭션(Compensating Transaction)이 실패하면 어떻게 처리하나요?

보상 트랜잭션 자체의 실패는 시스템을 비정합 상태로 만들 수 있어 매우 중요한 문제입니다. 해결 방안: (1) 재시도 정책(지수 백오프)으로 보상 작업을 반복 시도, (2) Dead Letter Queue에 실패한 보상 이벤트를 저장하여 수동/자동 복구, (3) 보상 트랜잭션을 멱등하게 설계하여 안전한 재시도 보장, (4) 모니터링/알림으로 운영팀에 즉시 통보하여 수동 개입.

Q3. Service Mesh 도입 시 사이드카 프록시의 오버헤드를 최소화하는 방법은?

(1) 경량 프록시 선택 (Linkerd의 Rust 기반 프록시는 Envoy 대비 메모리 사용량이 크게 적음), (2) 사이드카 리소스 요청/제한을 적절히 설정 (CPU 100m, Memory 50Mi 등), (3) 메시 기능 중 필요한 것만 활성화 (불필요한 텔레메트리 비활성화), (4) eBPF 기반 서비스 메시(Cilium Service Mesh) 고려 - 사이드카 없이 커널 레벨에서 처리, (5) 지연시간에 극도로 민감한 서비스는 메시에서 제외.

Q4. Strangler Fig 패턴 적용 시 가장 먼저 분리해야 할 서비스를 고르는 기준은?

(1) 다른 모듈과의 의존성이 가장 적은 기능 (독립적으로 동작 가능), (2) 비즈니스 가치가 높아 팀의 동기 부여에 도움, (3) 잘 정의된 도메인 경계가 존재, (4) 독립적 확장이나 별도 기술이 필요한 영역, (5) 테스트가 충분하여 동작 검증이 용이. 반대로 핵심 결제, 인증 같은 고위험 영역은 나중에 분리하는 것이 안전합니다.

Q5. OpenTelemetry에서 Context Propagation이 중요한 이유와 동작 방식을 설명하세요.

Context Propagation은 분산 환경에서 하나의 요청을 추적하기 위해 Trace ID와 Span ID를 서비스 간에 전달하는 메커니즘입니다. 중요한 이유: 이것 없이는 각 서비스의 로그/메트릭/트레이스가 분리되어 요청 흐름 파악이 불가능합니다. 동작 방식: (1) 첫 서비스에서 Trace ID 생성, (2) HTTP 헤더(W3C traceparent) 또는 gRPC 메타데이터로 다음 서비스에 전파, (3) 메시지 큐 환경에서는 메시지 헤더에 컨텍스트 포함, (4) 각 서비스에서 수신한 컨텍스트를 바탕으로 자식 Span 생성.


참고 자료

Microservices Architecture 2025 Complete Guide: From Monolith to MSA, and Back to Modular Monolith

Introduction

In 2024, Amazon Prime Video announced that they reverted from microservices to a monolith, cutting costs by 90% -- a revelation that sent shockwaves through the industry. DHH (creator of Ruby on Rails) declared that "microservices are over-engineering for most teams," and Shopify successfully handles massive traffic with a modular monolith architecture.

That said, microservices are far from dead. Netflix, Uber, and Spotify still operate thousands of microservices, and there are clear cases where MSA is the right choice depending on organizational scale and domain complexity.

This article provides an objective comparison of monolith, MSA, and modular monolith -- from the history of architectural evolution to current 2025 trends -- along with a systematic coverage of patterns and technologies needed in practice.


1. The History of Architectural Evolution

1.1 The Monolith Era (2000s)

The traditional architecture where all functionality is contained in a single deployment unit.

┌─────────────────────────────────────┐
Monolith Application│  ┌─────────┐ ┌─────────┐ ┌───────┐ │
│  │  User    │ │  Order  │ │Payment│ │
│  │  Module  │ │  Module │ │Module │ │
│  └────┬─────┘ └────┬────┘ └──┬────┘ │
│       └──────┬─────┴─────────┘      │
│         ┌────▼────┐                 │
│         │  Shared │                 │
│         │   DB    │                 │
│         └─────────┘                 │
└─────────────────────────────────────┘

Pros: Simple development/deployment, easy transaction management, convenient debugging

Cons: Codebase bloat, deployment bottlenecks, locked-in tech stack, scalability limits

1.2 The SOA Era (2005-2015)

Service-Oriented Architecture connected services around an Enterprise Service Bus (ESB).

┌────────┐    ┌────────┐    ┌────────┐
│Service A│    │Service B│    │Service C└───┬─────┘    └───┬─────┘    └───┬─────┘
    └──────────────┼──────────────┘
              ┌────▼────┐
ESB              (MessageBus)              └─────────┘

SOA pursued service reusability and standardization, but the ESB became a single point of failure, and the complexity of SOAP/WSDL was problematic.

1.3 The Microservices Era (2014-Present)

Microservices, defined by Martin Fowler and James Lewis, are an evolution of SOA where each service is independently deployable and scalable.

┌─────────┐   ┌─────────┐   ┌─────────┐
User    │   │ Order   │   │ PaymentService │   │ Service │   │ Service  :8080  │   │  :8081  │   │  :8082└──┬──────┘   └──┬──────┘   └──┬──────┘
REST/gRPC  │   Event   ▼             ▼            ▼
┌──────┐    ┌──────┐    ┌──────┐
│UserDB│    │OrderDB│   │PayDB │
└──────┘    └──────┘    └──────┘

1.4 2025: The Rise of the Modular Monolith

Timeline:
2000 ──── 2005 ──── 2014 ──── 2020 ──── 2025
 │         │         │         │         │
 ▼         ▼         ▼         ▼         ▼
Monolith  SOA    Microservices Service   Modular
                               Mesh      Monolith
                               Spread    Resurgence

2. When MSA Is Overkill: Real-World Lessons

2.1 The Amazon Prime Video Case

In 2023, the Amazon Prime Video team migrated their audio/video monitoring service from microservices to a monolith, reducing infrastructure costs by 90%.

Problems:

  • Explosive growth of AWS Step Functions state transition costs
  • S3 intermediate storage costs for inter-service data transfer
  • Microservice orchestration overhead

Solution:

  • Execute all processing steps within a single process
  • In-memory communication instead of network calls
  • Eliminate S3 intermediate storage

2.2 DHH's Critique

DHH, the creator of Ruby on Rails, argues that "most teams are perfectly fine with a monolith."

ArgumentRationale
Distributed system complexity underestimatedNetwork failures, data consistency, debugging difficulty
Mismatch with team sizeA team of 5-10 running 20 services is inefficient
Operational cost explosionInfrastructure, monitoring, deployment pipeline costs
Premature decomposition riskPoor boundary definition when domain understanding is lacking

2.3 Shopify's Modular Monolith Success

Shopify processes tens of billions of dollars in transactions annually while using a modular monolith architecture.

# Module boundary definition using Shopify's Packwerk
# packages/checkout/package.yml
enforce_dependencies: true
enforce_privacy: true
dependencies:
  - packages/inventory
  - packages/payment

3. Architecture Comparison: Monolith vs MSA vs Modular Monolith

3.1 Comparison Matrix

AspectMonolithModular MonolithMicroservices
Deployment Unit11 (module-level build)N (per service)
Team Size1-205-5020-hundreds
CommunicationMethod callsModule interfacesNetwork (REST/gRPC)
Data StoreShared DBSchema per moduleDB per service
TransactionsACIDACID (limited across modules)Saga/Compensation
ScalabilityVerticalVertical + partial horizontalHorizontal (per service)
Operational ComplexityLowMediumHigh
Tech DiversitySingle stackSingle stackPolyglot
Fault IsolationNonePartialFull isolation
Initial CostLowMediumHigh

3.2 Modular Monolith Architecture in Detail

┌─────────────────────────────────────────────┐
Modular Monolith│                                             │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐    │
│  │  User   │  │  Order  │  │ Payment │    │
│  │ Module  │  │ Module  │  │ Module  │    │
│  │         │  │         │  │         │    │
│  │ Public  │  │ Public  │  │ Public  │    │
│  │   API   │◄─►   API   │◄─►   API   │    │
│  │         │  │         │  │         │    │
│  │ Private │  │ Private │  │ Private │    │
│  │  Impl   │  │  Impl   │  │  Impl   │    │
│  └──┬──────┘  └──┬──────┘  └──┬──────┘    │
│     │            │            │            │
│  ┌──▼──┐     ┌──▼──┐     ┌──▼──┐         │
│  │user │     │order│     │pay  │         │
│  │schema│    │schema│    │schema│         │
│  └─────┘     └─────┘     └─────┘         │
│     └────────────┼────────────┘            │
│           ┌──────▼──────┐                  │
│           │  Shared DB  │                  │
(Separated   │                  │
│           │  Schemas)   │                  │
│           └─────────────┘                  │
└─────────────────────────────────────────────┘
// Java modular monolith example (Spring Modulith)
@ApplicationModule(
    allowedDependencies = {"order", "shared"}
)
package com.example.payment;

// Inter-module communication via events
@Service
public class PaymentService {

    private final ApplicationEventPublisher events;

    @Transactional
    public PaymentResult processPayment(PaymentRequest request) {
        Payment payment = Payment.create(request);
        paymentRepository.save(payment);

        // Publish event to other modules (no direct dependency)
        events.publishEvent(new PaymentCompletedEvent(
            payment.getId(),
            payment.getOrderId(),
            payment.getAmount()
        ));

        return PaymentResult.success(payment);
    }
}

3.3 Decision Flowchart

Start: New Project
Team size > 50? ──Yes──▶ Are domains clearly separated?
  │                                    │
  No                                  Yes ──▶ Consider MSA
  │                                    │
No ──▶ Modular Monolith
Need independently scalable services?
  Yes ──▶ Extract only those services (Hybrid)
  No ──▶ Monolith or Modular Monolith

4. DDD and Service Decomposition

4.1 Identifying Bounded Contexts

In Domain-Driven Design, the Bounded Context serves as the natural boundary for microservices.

┌─────────────── E-Commerce Domain ───────────────┐
│                                                  │
│  ┌──────────────┐    ┌──────────────┐           │
│  │  Catalog     │    │  Order       │           │
│  │  Context     │    │  Context     │           │
│  │              │    │              │           │
│  │ - Product    │    │ - Order      │           │
│  │ - Category   │    │ - OrderItem  │           │
│  │ - Price      │    │ - Shipment   │           │
│  └──────────────┘    └──────────────┘           │
│                                                  │
│  ┌──────────────┐    ┌──────────────┐           │
│  │  Identity    │    │  Payment     │           │
│  │  Context     │    │  Context     │           │
│  │              │    │              │           │
│  │ - User       │    │ - Payment    │           │
│  │ - Role       │    │ - Refund     │           │
│  │ - Permission │    │ - Invoice    │           │
│  └──────────────┘    └──────────────┘           │
└──────────────────────────────────────────────────┘

4.2 Context Mapping Patterns

┌────────────┐                    ┌────────────┐
Upstream  │                    │ DownstreamContext   │ ──Conformist────▶  │  Context│            │                    │            │
  (Order)   │ ──ACL──────────▶   (Payment)│            │                    │            │
│            │ ──OHS/PL───────▶   (Shipping)└────────────┘                    └────────────┘

ACL: Anti-Corruption Layer (translation layer)
OHS: Open Host Service (public API)
PL:  Published Language (shared schema)

4.3 Service Decomposition Strategies

// 1. Decomposition by Aggregate
// Order Aggregate Root
public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderLine> lines;
    private OrderStatus status;
    private Money totalAmount;

    // Business logic within the aggregate
    public void addItem(ProductId productId, int quantity, Money price) {
        OrderLine line = new OrderLine(productId, quantity, price);
        this.lines.add(line);
        this.totalAmount = calculateTotal();
    }

    public void confirm() {
        if (this.lines.isEmpty()) {
            throw new OrderException("Cannot confirm an empty order");
        }
        this.status = OrderStatus.CONFIRMED;
    }
}

4.4 Decomposition Mistakes to Avoid

  1. Data-driven decomposition: Splitting services by table leads to excessive inter-service communication
  2. Technology-driven decomposition: Splitting by frontend/backend/DB layers loses MSA benefits
  3. Premature decomposition: Splitting before understanding the domain leads to incorrect boundaries
  4. Over-granularity: Nanoservices only increase operational burden

5. Communication Patterns: REST vs gRPC vs Event-Driven

5.1 Synchronous Communication Comparison

AspectREST (HTTP/JSON)gRPC (HTTP/2 + Protobuf)
SerializationJSON (text)Protocol Buffers (binary)
PerformanceRelatively slow2-10x faster
StreamingLimited (SSE/WebSocket)Native bidirectional streaming
Code GenerationOpenAPI (optional)Required (proto files)
Browser SupportNativeRequires gRPC-Web
ReadabilityHigh (JSON)Low (binary)
Use CaseExternal APIs, simple CRUDInternal inter-service communication

5.2 gRPC Service Definition

// order_service.proto
syntax = "proto3";

package order.v1;

service OrderService {
  // Unary RPC
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);

  // Server streaming - order status updates
  rpc WatchOrderStatus (WatchRequest) returns (stream OrderStatusUpdate);

  // Client streaming - bulk order registration
  rpc BulkCreateOrders (stream CreateOrderRequest) returns (BulkCreateResponse);

  // Bidirectional streaming - real-time order processing
  rpc ProcessOrders (stream OrderAction) returns (stream OrderResult);
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
  ShippingAddress shipping = 3;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  int64 price_cents = 3;
}

message CreateOrderResponse {
  string order_id = 1;
  OrderStatus status = 2;
  int64 total_cents = 3;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_DELIVERED = 4;
}

5.3 Event-Driven Asynchronous Communication

Producer                         Consumer
┌─────────┐    ┌─────────┐    ┌─────────┐
Order   │───▶│  Kafka  │───▶│ PaymentService │    │ Topic:  │    │ Service│         │    │ orders  │    │         │
└─────────┘    └─────────┘    └─────────┘
                    ├────────▶ ┌─────────┐
                    │          │Inventory│
                    │          │ Service                    │          └─────────┘
                    └────────▶ ┌─────────┐
                               │Notific-                               │ation    │
Service                               └─────────┘
// Kafka Producer - publish order events
@Service
public class OrderEventPublisher {

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void publishOrderCreated(Order order) {
        OrderCreatedEvent event = OrderCreatedEvent.builder()
            .orderId(order.getId())
            .customerId(order.getCustomerId())
            .items(order.getItems())
            .totalAmount(order.getTotalAmount())
            .timestamp(Instant.now())
            .build();

        kafkaTemplate.send("orders.created", order.getId(), event)
            .whenComplete((result, ex) -> {
                if (ex != null) {
                    log.error("Event publish failed: orderId={}",
                        order.getId(), ex);
                    // Compensation logic or retry
                }
            });
    }
}

// Kafka Consumer - payment processing
@Service
public class PaymentEventConsumer {

    @KafkaListener(
        topics = "orders.created",
        groupId = "payment-service",
        containerFactory = "kafkaListenerContainerFactory"
    )
    public void handleOrderCreated(OrderCreatedEvent event) {
        log.info("Order event received: orderId={}", event.getOrderId());
        paymentService.processPayment(event);
    }
}

5.4 Communication Pattern Selection Guide

Need synchronous call?
  Yes ──▶ For external clients?
  │          │
Yes ──▶ REST (OpenAPI)
  │          │
No ──▶ Need high performance? ──Yes──▶ gRPC
  │                    │
No ──▶ REST
  No ──▶ Need ordering guarantee?
          Yes ──▶ Kafka (ordering via partition key)
          No ──▶ Need fan-out?
                  Yes ──▶ SNS + SQS / Kafka topics
                  No ──▶ SQS / RabbitMQ

6. Service Mesh: Istio vs Linkerd

6.1 What is a Service Mesh?

A service mesh is a dedicated infrastructure layer for managing inter-service communication at the infrastructure level.

Without Service Mesh:          With Service Mesh:

┌─────────┐  ┌─────────┐     ┌─────────┐  ┌─────────┐
│Service A│──│Service B│     │Service A│  │Service B│         │  │         │     │ ┌─────┐ │  │ ┌─────┐ │
(retry,(retry,  │     │ │Proxy│─┼──┼─│Proxy│ │
│ auth,   │  │ auth,   │     │ (side│ │  │ (side│ │
│ metrics)│  │ metrics)│     │ │car) │ │  │ │car) │ │
└─────────┘  └─────────┘     │ └─────┘ │  │ └─────┘ │
                              └─────────┘  └─────────┘
   Communication logic          Cross-cutting concerns
   in every service             delegated to infrastructure

6.2 Key Features

FeatureDescription
Traffic ManagementLoad balancing, routing, canary deployments
SecurityAutomatic mTLS, authentication/authorization
ObservabilityDistributed tracing, metrics collection, logging
ResilienceRetries, circuit breakers, timeouts

6.3 Istio vs Linkerd Comparison

AspectIstioLinkerd
ProxyEnvoy (C++)linkerd2-proxy (Rust)
Resource UsageHigh (100-200MB/pod)Low (20-30MB/pod)
Feature ScopeComprehensive (VM support, etc.)Core features focused
Learning CurveSteepGentle
mTLSManual configuration neededEnabled by default
CNCF StatusGraduated projectGraduated project
Recommended ScaleLarge (100+ services)Small-medium (10-100 services)

6.4 Istio Traffic Management Example

# VirtualService - Canary Deployment
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: order-service
spec:
  hosts:
    - order-service
  http:
    - match:
        - headers:
            x-canary:
              exact: "true"
      route:
        - destination:
            host: order-service
            subset: v2
    - route:
        - destination:
            host: order-service
            subset: v1
          weight: 90
        - destination:
            host: order-service
            subset: v2
          weight: 10
---
# DestinationRule - Circuit Breaker
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service
spec:
  host: order-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

6.5 When You Don't Need a Service Mesh

  • Fewer than 10 services
  • Kubernetes built-in features are sufficient
  • Your team has no service mesh operational experience
  • Systems extremely sensitive to latency (sidecar overhead)

7. Distributed Transactions and the Saga Pattern

7.1 The Problem with Distributed Transactions

In microservices, each service owns its own database, making ACID transactions across multiple services impossible.

Order creation scenario:
1. Order Service: Create order
2. Inventory Service: Reserve stock
3. Payment Service: Process payment
4. Notification Service: Send notification

What if payment fails at step 3?
-> Steps 1 and 2 need to be rolled back!

7.2 Saga Pattern - Choreography

┌─────────┐  OrderCreated  ┌─────────┐  StockReserved  ┌─────────┐
Order  │──────────────▶│Inventory│───────────────▶│ PaymentService │               │ Service │                │ Service└─────────┘               └─────────┘                └─────────┘
     ▲                         ▲                          │
StockReleasePaymentFailed         (compensate)         │◀──────────────────────────┘
     │◀────────────────────────┘
OrderCancelled (compensate)

Each service subscribes to events and autonomously triggers the next step.

7.3 Saga Pattern - Orchestration

                    ┌────────────────┐
Order SagaOrchestrator                    └───┬──┬──┬──┬──┘
           1.Create    │  │  │  │  4.Notify
            Order ┌────┘  │  │  └──────┐
                  ▼       │  │         ▼
          ┌─────────┐     │  │   ┌──────────┐
Order  │     │  │   │Notificat-Service │     │  │   │ion Svc          └─────────┘     │  │   └──────────┘
              2.Reserve   │  │  3.Payment
                Stock┌────┘  └─────┐
                     ▼              ▼
              ┌─────────┐   ┌─────────┐
              │Inventory│   │ PaymentService │   │ Service              └─────────┘   └─────────┘
// Saga Orchestrator Implementation
@Service
public class OrderSagaOrchestrator {

    public Mono<OrderResult> createOrder(CreateOrderCommand command) {
        return Mono.just(new SagaState(command))
            // Step 1: Create order
            .flatMap(state -> orderService.createOrder(state.getCommand())
                .map(order -> state.withOrder(order)))

            // Step 2: Reserve stock
            .flatMap(state -> inventoryService
                .reserveStock(state.getOrder())
                .map(reservation -> state.withReservation(reservation))
                .onErrorResume(e -> compensateOrder(state, e)))

            // Step 3: Process payment
            .flatMap(state -> paymentService
                .processPayment(state.getOrder())
                .map(payment -> state.withPayment(payment))
                .onErrorResume(e ->
                    compensateReservation(state, e)))

            // Step 4: Complete
            .flatMap(state -> {
                orderService.confirmOrder(
                    state.getOrder().getId());
                return Mono.just(OrderResult.success(state));
            });
    }

    private Mono<SagaState> compensateReservation(
            SagaState state, Throwable e) {
        log.warn("Payment failed, starting stock compensation: orderId={}",
            state.getOrder().getId());
        return inventoryService
            .releaseStock(state.getReservation())
            .then(compensateOrder(state, e));
    }

    private Mono<SagaState> compensateOrder(
            SagaState state, Throwable e) {
        log.warn("Starting order compensation: orderId={}",
            state.getOrder().getId());
        return orderService
            .cancelOrder(state.getOrder().getId())
            .then(Mono.error(
                new SagaFailedException("Saga failed", e)));
    }
}

7.4 Choreography vs Orchestration

AspectChoreographyOrchestration
CouplingLooseDependent on central coordinator
ComplexityGrows with service countRemains consistent
DebuggingDifficult (event tracing)Relatively easy
Single Point of FailureNoneOrchestrator
Best ForSimple flows (3-4 steps or fewer)Complex flows (5+ steps)

8. Observability: OpenTelemetry

8.1 The Three Pillars of Observability

         ┌─────────────┐
Observability│
         └──────┬──────┘
     ┌──────────┼──────────┐
     ▼          ▼          ▼
┌─────────┐ ┌──────┐ ┌─────────┐
Logs   │ │Metrics│ │ Traces(events)(values)(request │
└─────────┘ └──────┘ │ flow) What        How much └─────────┘
 happened?   is happening? Where?

8.2 OpenTelemetry Integrated Architecture

┌─────────────────────────────────────────────────────┐
Application│  ┌─────────────────────────────────────────────┐   │
│  │        OpenTelemetry SDK                    │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐   │   │
│  │  │  Tracer  │ │  Meter   │ │  Logger  │   │   │
│  │  └──────────┘ └──────────┘ └──────────┘   │   │
│  └────────────────────┬────────────────────────┘   │
└───────────────────────┼─────────────────────────────┘
OTLP
              ┌─────────────────┐
OTel Collector              │  ┌───────────┐  │
              │  │ Receivers │  │
              │  │Processors│  │
              │  │ Exporters │  │
              │  └───────────┘  │
              └────┬──┬──┬──────┘
                   │  │  │
          ┌────────┘  │  └────────┐
          ▼           ▼           ▼
     ┌─────────┐ ┌────────┐ ┌─────────┐
Jaeger │ │Promethe│ │  Loki      (Traces)│ │us/Mimir│  (Logs)     │         │ (Metrics│ │         │
     └─────────┘ └────────┘ └─────────┘
          │           │           │
          └─────┬─────┘───────────┘
          ┌───────────┐
Grafana          (Dashboard)          └───────────┘

8.3 Distributed Tracing Implementation

// Spring Boot + OpenTelemetry auto-configuration
// build.gradle
// implementation 'io.opentelemetry.instrumentation:
//   opentelemetry-spring-boot-starter'

// Custom span creation
@Service
public class OrderService {

    private final Tracer tracer;

    public Order createOrder(CreateOrderRequest request) {
        Span span = tracer.spanBuilder("order.create")
            .setAttribute("order.customer_id",
                request.getCustomerId())
            .setAttribute("order.item_count",
                request.getItems().size())
            .startSpan();

        try (Scope scope = span.makeCurrent()) {
            // Order creation logic
            Order order = processOrder(request);

            span.setAttribute("order.id", order.getId());
            span.setAttribute("order.total",
                order.getTotal().doubleValue());
            span.setStatus(StatusCode.OK);

            return order;
        } catch (Exception e) {
            span.setStatus(StatusCode.ERROR, e.getMessage());
            span.recordException(e);
            throw e;
        } finally {
            span.end();
        }
    }
}

8.4 Key Metrics (RED/USE)

RED Method (Service perspective):
┌──────────────────────────────────────┐
R - Rate:     Requests per second    │
E - Errors:   Error rate (%)D - Duration: Response time          │
               (p50/p95/p99)└──────────────────────────────────────┘

USE Method (Resource perspective):
┌──────────────────────────────────────┐
U - Utilization: Resource usage rate │
S - Saturation:  Queue length        │
E - Errors:      Resource error count│
└──────────────────────────────────────┘

9. API Gateway Pattern

9.1 The Role of API Gateway

                    ┌─────────────────────┐
API Gateway   Client ────────▶│                     │
- Auth/AuthZ- Rate Limiting- Request Routing- Load Balancing- Response Caching- Request/ResponseTransformation- Circuit Breaker- Logging/Monitoring│
                    └──┬──────┬──────┬────┘
                       │      │      │
                       ▼      ▼      ▼
                    ┌─────┐┌─────┐┌─────┐
                    │Svc A││Svc B││Svc C                    └─────┘└─────┘└─────┘

9.2 BFF (Backend for Frontend) Pattern

┌────────┐     ┌──────────────┐
Web   │────▶│  Web BFF     │──┐
Client(GraphQL)     │  │
└────────┘     └──────────────┘  │
                                 │    ┌──────────┐
┌────────┐     ┌──────────────┐  ├───▶│ OrderMobile │────▶│ Mobile BFF   │──┤    │ ServiceApp(REST, light) │  │    └──────────┘
└────────┘     └──────────────┘  │
                                 │    ┌──────────┐
┌────────┐     ┌──────────────┐  ├───▶│ UserIoT   │────▶│  IoT BFF     │──┤    │ ServiceDevice(MQTT bridge) │  │    └──────────┘
└────────┘     └──────────────┘  │
                                 │    ┌──────────┐
                                 └───▶│ ProductService                                      └──────────┘

9.3 API Gateway Comparison

AspectKongAWS API GatewayEnvoy GatewayAPISIX
FoundationNginx + LuaAWS ManagedEnvoy ProxyNginx + Lua
DeploymentSelf-hosted/CloudAWS onlyKubernetesSelf-hosted
ProtocolsREST, gRPC, WSREST, WS, HTTPREST, gRPCREST, gRPC, MQTT
PluginsExtensiveLambda integrationExtensibleExtensive
PricingOpen-source/EnterprisePer-request billingOpen-sourceOpen-source

10. Deployment Strategies

10.1 Blue-Green Deployment

Phase 1: Blue (current) running
┌─────────────┐     ┌──────────┐
Load        │────▶│ Blue v1    (100% traffic)
Balancer (Active)└─────────────┘     └──────────┘
                    ┌──────────┐
Green v2   (idle)
                     (Idle)                    └──────────┘

Phase 2: Switch to Green
┌─────────────┐     ┌──────────┐
Load        │     │ Blue v1    (idle)
Balancer (Idle)└─────────────┘     └──────────┘
       │            ┌──────────┐
       └───────────▶│ Green v2   (100% traffic)
                     (Active)                    └──────────┘

10.2 Canary Deployment

# Kubernetes + Argo Rollouts canary deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: order-service
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 5
        - pause:
            duration: 5m
        - analysis:
            templates:
              - templateName: success-rate
            args:
              - name: service-name
                value: order-service
        - setWeight: 25
        - pause:
            duration: 10m
        - analysis:
            templates:
              - templateName: success-rate
        - setWeight: 50
        - pause:
            duration: 15m
        - setWeight: 100
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: order-service:v2
          ports:
            - containerPort: 8080

10.3 Deployment Strategy Comparison

StrategyDowntimeRollback SpeedResource CostRisk Level
Rolling UpdateNoneModerateLowMedium
Blue-GreenNoneInstantHigh (2x)Low
CanaryNoneFastMediumLow
A/B TestingNoneFastMediumLow
RecreateYesSlowLowHigh

11. Migration: The Strangler Fig Pattern

11.1 Pattern Overview

The Strangler Fig Pattern is a strategy for progressively migrating a legacy monolith to microservices. Just as a strangler fig tree wraps around its host tree, new services gradually replace the legacy system.

Phase 1: Place proxy
┌────────┐     ┌──────────┐     ┌────────────┐
Client │────▶│  Proxy   │────▶│  Monolith└────────┘     (Facade)(all funcs)               └──────────┘     └────────────┘

Phase 2: Extract some features
┌────────┐     ┌──────────┐     ┌────────────┐
Client │────▶│  Proxy   │─┬──▶│  Monolith└────────┘     │          │ │   (reduced)               └──────────┘ │   └────────────┘
                            └──▶┌────────────┐
New Service│
                                (extracted)                                └────────────┘

Phase 3: Most features migrated
┌────────┐     ┌──────────┐     ┌────────────┐
Client │────▶│  Proxy   │─┬──▶│  Monolith└────────┘     │          │ │   (minimal)               └──────────┘ │   └────────────┘
                            ├──▶┌────────────┐
                            │   │ Service A                            │   └────────────┘
                            ├──▶┌────────────┐
                            │   │ Service B                            │   └────────────┘
                            └──▶┌────────────┐
Service C                                └────────────┘

Phase 4: Remove monolith
┌────────┐     ┌──────────┐
Client │────▶│  API GW  │─┬──▶ Service A
└────────┘     └──────────┘ ├──▶ Service B
                            └──▶ Service C

11.2 Migration Phases

1. Analysis (2-4 weeks)
   ├── Domain modeling (Event Storming)
   ├── Dependency analysis (code/data)
   └── Priority determination

2. Foundation (4-8 weeks)
   ├── CI/CD pipeline
   ├── Container/orchestration environment
   ├── Observability stack
   └── API Gateway placement

3. First service extraction (4-6 weeks)
   ├── Select most independent domain
   ├── Implement Anti-Corruption Layer
   ├── Data migration
   └── Dual write / Event bridge

4. Iterative extraction (2-4 weeks per service)
   ├── Extract next service
   ├── Remove monolith code
   └── Integration testing

5. Monolith removal
   ├── Migrate remaining features
   ├── Data cleanup
   └── Infrastructure decommission

11.3 Data Migration Strategy

// Dual Write Pattern - write to both during transition
@Service
public class UserServiceMigration {

    private final MonolithUserRepository monolithRepo;
    private final NewUserServiceClient newServiceClient;
    private final FeatureFlag featureFlag;

    public User createUser(CreateUserRequest request) {
        // Always write to monolith (existing)
        User user = monolithRepo.save(toEntity(request));

        // Also write to new service (migration)
        try {
            newServiceClient.createUser(
                toNewServiceRequest(user));
        } catch (Exception e) {
            log.warn("New service sync failed, will retry later", e);
            migrationQueue.enqueue(new SyncEvent(user));
        }

        return user;
    }

    public User getUser(String userId) {
        // Switch reads using Feature Flag
        if (featureFlag.isEnabled("read-from-new-service")) {
            try {
                return newServiceClient.getUser(userId);
            } catch (Exception e) {
                log.warn("New service read failed, falling back", e);
                return monolithRepo.findById(userId);
            }
        }
        return monolithRepo.findById(userId);
    }
}

12. Anti-Patterns

12.1 Common MSA Anti-Patterns

Anti-PatternDescriptionSolution
Distributed MonolithServices are split but remain tightly coupledRedefine Bounded Contexts
NanoserviceServices too small (function-level)Merge services, group related features
Shared DBMultiple services accessing the same DBDB per Service principle
Synchronous ChainSequential sync calls: A to B to C to DEvent-driven asynchronous
Golden HammerApplying MSA to every problemEvaluate suitability before choosing architecture
Version HellAPI versions proliferateMaintain backward compatibility, contract testing

12.2 Distributed Monolith Symptoms

Distributed Monolith Checklist:
[ ] Deploying one service requires deploying others too
[ ] Synchronous call chains span 3+ services
[ ] Multiple services read/write the same database
[ ] Shared library versions must be coordinated across services
[ ] One service failure brings down the entire system
[ ] Service boundaries are drawn by tech layer (front/back/DB)

If 3 or more apply, you likely have a distributed monolith.

12.3 The Synchronous Call Chain Problem

Problem: Synchronous chain
Client -> Order -> Inventory -> Payment -> Shipping
                                           (3 sec)
         Total response time: 3 sec + each service processing time
         If any service fails, the entire chain fails

Solution: Async events + immediate response
Client -> Order (200 OK, order accepted)
              |
              +-- Event: OrderCreated
              |        |
              |        +-- Inventory (async)
              |        +-- Payment (async)
              |        +-- Shipping (async)
              |
              +-- Status query API provided

13. Decision Framework

13.1 Architecture Selection Matrix

Score the following questions (1-5 points).

Question1 point (Low)5 points (High)
Team size5 or fewer50+
Domain complexitySimple CRUDComplex business logic
Independent scaling needsUniform loadVastly different load per service
Deployment frequencyMonthlyDozens per day
Tech diversity needsSingle stackOptimal tech per service needed
Team autonomyCentralizedIndependent per team
Operational maturityDevOps beginnersMature platform team

Score Interpretation:

  • 7-15 points: Monolith (or Modular Monolith)
  • 16-25 points: Modular Monolith (or Hybrid)
  • 26-35 points: Microservices
Monolith:
├── Spring Boot + JPA
├── Django + PostgreSQL
├── Rails + PostgreSQL
└── Next.js Full Stack

Modular Monolith:
├── Spring Modulith
├── .NET Aspire
├── Go (module pattern)
└── Rust (Workspace)

Microservices:
├── Communication: gRPC (internal) + REST (external)
├── Messaging: Kafka / RabbitMQ
├── Orchestration: Kubernetes
├── Service Mesh: Istio / Linkerd
├── Observability: OpenTelemetry + Grafana
├── CI/CD: ArgoCD / GitHub Actions
└── API Gateway: Kong / Envoy Gateway

14. Interview Prep Q&A (Top 15)

Q1. Explain five core characteristics of microservices.

  1. Single Responsibility: Each service handles one business capability
  2. Independent Deployment: Deployable independently from other services
  3. Distributed Data: Each service owns its own data store
  4. Tech Diversity: Each service can choose its optimal tech stack
  5. Fault Isolation: One service failure does not propagate to the entire system

Q2. Explain the CAP theorem and its relationship with microservices.

According to the CAP theorem, a distributed system can guarantee only 2 out of 3: Consistency, Availability, and Partition Tolerance. Since network partitions are inevitable in microservices, P must always be chosen, leaving the choice between AP (availability-first) or CP (consistency-first). Most MSA systems choose AP with Eventual Consistency.

Q3. What are the two implementation approaches for the Saga pattern?

Choreography: Each service publishes and subscribes to events, autonomously performing the next step. There is no central coordinator, resulting in loose coupling, but event flow tracking becomes difficult as services increase.

Orchestration: A central orchestrator calls each service in sequence and manages compensation logic. The flow is clear, but the orchestrator can become a single point of failure.

Q4. Explain the Sidecar pattern in Service Mesh.

A proxy container (sidecar) is co-located with each service pod. All inbound/outbound traffic from the service passes through the sidecar, handling cross-cutting concerns like authentication (mTLS), routing, retries, and observability without modifying application code. Istio uses Envoy proxy, while Linkerd uses linkerd2-proxy as sidecars.

Q5. Explain the principles of Distributed Tracing.

When a request enters the system, a unique Trace ID is generated. Each service propagates this Trace ID and records its processing unit as a Span. Parent-child Spans form a complete Trace, enabling visualization of the entire request path and timing of each segment. The W3C Trace Context standard header is used.

Q6. What are the benefits and caveats of the Strangler Fig Pattern?

Benefits: Minimized risk through gradual migration, avoids big-bang migration, monolith and new services can coexist, easy rollback

Caveats: Dual-system operational costs during transition, data synchronization complexity, routing rule management burden, difficulty determining transition completion point

Q7. What is the difference between API Gateway and Service Mesh?

API Gateway: Manages external traffic (North-South). Handles edge features like authentication, rate limiting, request transformation, and API aggregation.

Service Mesh: Manages internal inter-service traffic (East-West). Handles mTLS, service discovery, load balancing, circuit breakers, and other inter-service communication concerns.

They are complementary, and large-scale MSA systems use both together.

Q8. Explain the relationship between Event Sourcing and CQRS.

Event Sourcing: Instead of storing state directly, it stores a sequence of state-change events. Current state is reconstructed by replaying events.

CQRS: Separates command (write) and query (read) models. Writes go to the event store; reads come from optimized views (Read Models). Event Sourcing can be used without CQRS, but combining them allows independent optimization of write and read performance.

Q9. How do you ensure data consistency in microservices?

Instead of strong consistency, we accept Eventual Consistency. Specific patterns include Saga Pattern (distributed transactions), Outbox Pattern (guaranteed event publishing), Change Data Capture (DB change detection), and Idempotency guarantees (safe duplicate processing). When strong data consistency is absolutely required, those capabilities should be placed in the same service.

Q10. When is gRPC more advantageous than REST?

gRPC excels in internal inter-service communication requiring high throughput, bidirectional streaming, strongly-typed contracts (proto files), and reduced payload size through binary serialization. REST is more suitable for browser clients, public APIs for third-party developers, and simple CRUD operations.

Q11. Explain the Circuit Breaker pattern.

Like an electrical circuit breaker, it blocks calls to a failing downstream service to prevent fault propagation. It has three states: Closed (normal calls), Open (calls blocked, immediate failure returned), Half-Open (testing recovery with some requests). When consecutive failures exceed a threshold, it transitions to Open, and after a timeout, tests recovery in Half-Open.

Q12. Compare service discovery approaches.

Client-Side Discovery: The client directly queries a service registry (Eureka, Consul) and selects target instances. Load balancing logic resides in the client.

Server-Side Discovery: A load balancer references the service registry for routing. Kubernetes Service is a prime example. Simpler as clients do not need to know about discovery logic.

Q13. When should you transition from Modular Monolith to MSA?

Consider transitioning when: a specific module's scaling requirements significantly differ from others, the team grows beyond 50 people requiring independent deployment, a specific module requires a different technology stack, or there is a business need for mandatory fault isolation.

Q14. What is the Outbox Pattern?

Within a local transaction, business data and events are saved together to an Outbox table. A separate process (Polling Publisher or CDC) reads events from the Outbox table and publishes them to a message broker. This ensures atomicity between business logic and event publishing.

Q15. Explain the microservices Testing Pyramid.

It consists of unit tests (internal service logic), integration tests (DB/external integration), contract tests (inter-service API compatibility using tools like Pact), component tests (single service E2E), and E2E tests (full system). Contract tests are especially important as they automatically verify that provider service changes do not break consumers.


15. Quiz

Q1. Explain 3 key advantages and 2 limitations of Modular Monolith compared to MSA.

Advantages: (1) Module-level separation without MSA complexity (network communication, distributed transactions, service mesh), (2) ACID transactions available for easy data consistency, (3) Single deployment unit for simpler operations/deployment.

Limitations: (1) Cannot independently scale specific services (difficult to horizontally scale individual modules), (2) Tech stack is limited to a single language/framework.

Q2. What happens when a compensating transaction fails in the Saga pattern?

Compensating transaction failure can leave the system in an inconsistent state, making it a critical issue. Solutions: (1) Retry policy (exponential backoff) for repeated compensation attempts, (2) Dead Letter Queue stores failed compensation events for manual/automatic recovery, (3) Design compensating transactions to be idempotent for safe retries, (4) Monitoring/alerting to immediately notify the operations team for manual intervention.

Q3. How can you minimize sidecar proxy overhead when adopting a Service Mesh?

(1) Choose a lightweight proxy (Linkerd's Rust-based proxy uses significantly less memory than Envoy), (2) Set appropriate resource requests/limits for sidecars (e.g., CPU 100m, Memory 50Mi), (3) Enable only the mesh features you need (disable unnecessary telemetry), (4) Consider eBPF-based service mesh (Cilium Service Mesh) which processes at kernel level without sidecars, (5) Exclude latency-critical services from the mesh.

Q4. What criteria should you use to select the first service to extract when applying the Strangler Fig Pattern?

(1) Features with the fewest dependencies on other modules (can operate independently), (2) High business value to motivate the team, (3) Well-defined domain boundaries exist, (4) Areas requiring independent scaling or different technology, (5) Sufficient test coverage for easy behavior verification. Conversely, high-risk areas like core payment or authentication should be extracted later.

Q5. Explain why Context Propagation in OpenTelemetry is important and how it works.

Context Propagation is the mechanism for passing Trace ID and Span ID between services to track a single request across a distributed environment. Its importance: without it, logs/metrics/traces from each service remain isolated, making it impossible to understand request flow. How it works: (1) Trace ID is generated at the first service, (2) Propagated to the next service via HTTP headers (W3C traceparent) or gRPC metadata, (3) In message queue environments, context is included in message headers, (4) Each service creates child Spans based on the received context.


References