[Microservices] 마이크로서비스 패턴 완벽 가이드 — 개요부터 실전(Java·Docker·K8s)까지
마이크로서비스는 하나의 큰 애플리케이션을 작고 독립적인 서비스 단위로 나눠 운영하는 아키텍처 스타일입니다. 이 글에서는 5대 핵심 패턴을 정의부터 정리하고, 주문-결제-배송 시스템을 Java 도메인 모델링·이벤트 기반 Saga·Docker/Kubernetes 배포로 구현하는 실전 예제까지 한 흐름으로 다룹니다.
🏗️ 마이크로서비스란? #
마이크로서비스 아키텍처(Microservice Architecture) 는 단일 책임을 가진 작은 서비스들을 독립적으로 배포·운영하는 방식입니다.
🔑 핵심: 작은 서비스 단위 + 독립 배포 + 명확한 책임
| 특징 | 설명 |
|---|---|
| 작은 단위 | 각 서비스는 단일 책임(Single Responsibility) 중심 |
| 독립 배포 | 서비스별로 독립 빌드/배포 |
| 독립 데이터 | 각 서비스가 자체 데이터 저장소 보유 |
| 통신 방식 | REST API, gRPC, 메시지 큐 |
| 팀 단위 개발 | 서비스 단위로 팀이 독립 개발 |
모놀리식과 비교하면 마이크로서비스는 유연성·확장성을 강조하지만, 분산 관리 복잡성·데이터 일관성·운영 부담이라는 비용이 따릅니다.
🧩 5대 핵심 패턴 #
1) Database per Service #
각 서비스가 자체 데이터베이스를 가집니다. 결합도를 낮추지만, 데이터 일관성은 별도 전략(Saga·이벤트)으로 관리해야 합니다.
1Order Service DB <-- 독립
2Payment Service DB <-- 독립
3Shipping Service DB <-- 독립2) API Gateway #
클라이언트 요청을 단일 진입점에서 받아 인증·로깅·라우팅·응답 집계를 수행합니다.
1Client → API Gateway → Order Service
2 → Payment Service
3 → Shipping Service💡 클라이언트(웹/모바일)별로 응답을 최적화하려면 BFF(Backend for Frontend) 패턴으로 게이트웨이를 클라이언트별로 두기도 합니다.
3) Circuit Breaker #
서비스 호출이 반복 실패하면 회로를 열어 연쇄 장애를 막고 fallback으로 처리합니다.
1try {
2 paymentService.pay(order);
3} catch (ServiceUnavailableException e) {
4 fallbackPayment(order); // 캐시/기본값/지연 큐 등 의미 있는 대체
5}실무에서는 Java용 resilience4j를 많이 씁니다. 회로는 CLOSED → OPEN → HALF_OPEN 상태를 오가며, 권장 설정은 다음과 같습니다.
- 실패율 임계치는 평소 에러율 기준으로(평소 5%면 10% 정도)
- 고트래픽 서비스는 시간 기반 슬라이딩 윈도우(예: 60초)가 카운트 기반보다 안정적
- fallback은 의미 있게(예외 재던지기·null 반환은 무의미)
4) Saga #
여러 서비스에 걸친 분산 트랜잭션을 보상(rollback)으로 관리합니다.
1Order → Payment → Shipping
2 실패 → Payment 취소 → Order 취소구현 방식은 두 가지입니다.
| 방식 | 설명 | 적합 |
|---|---|---|
| Choreography(코레오그래피) | 각 서비스가 이벤트에 반응해 다음 단계 트리거 | 작고 느슨한 결합, 확장성 |
| Orchestration(오케스트레이션) | 중앙 코디네이터가 순서·보상 제어 | 큰 워크플로·롤백 가시성 필요 |
5) Event-Driven #
서비스 간 이벤트 기반 비동기 통신으로 느슨한 결합과 확장성을 얻습니다.
1OrderCreatedEvent → Payment Service → PaymentProcessedEvent → Shipping Service🚀 실전: 주문-결제-배송 시스템 #
서비스 구조 #
1microservice-app/
2├── order-service/ (src/, database/)
3├── payment-service/ (src/, database/)
4├── shipping-service/ (src/, database/)
5└── api-gateway/ (src/)도메인 모델링 (Java) #
1public class Order {
2 private final String id;
3 private final String customerId;
4 private final List<OrderItem> items = new ArrayList<>();
5 private String status = "PENDING";
6
7 public void addItem(String productId, int quantity) {
8 items.add(new OrderItem(productId, quantity));
9 }
10
11 public void confirm() { status = "CONFIRMED"; }
12}1public class PaymentService {
2 public boolean pay(Order order, String method) {
3 if (!order.getStatus().equals("CONFIRMED")) {
4 throw new IllegalStateException("결제 전 주문만 처리 가능");
5 }
6 return true; // 실제 결제 로직
7 }
8}1public class OrderCreatedEvent {
2 private final String orderId;
3 public OrderCreatedEvent(String orderId) { this.orderId = orderId; }
4}이벤트 기반 Saga 흐름 #
1// 주문 생성 → 결제 → 배송
2OrderService.createOrder(order);
3eventBus.publish(new OrderCreatedEvent(order.getId()));
4
5// PaymentService subscribes OrderCreatedEvent
6// → 결제 성공 시 PaymentCompletedEvent 발행
7// → 실패 시 OrderCancelledEvent 발행 (보상 트랜잭션)
8
9// ShippingService subscribes PaymentCompletedEvent
10// → 배송 처리위 예시는 Choreography 방식입니다. 단계가 많아지고 롤백 가시성이 중요해지면 Orchestration으로 전환을 고려하세요.
🐳 Docker·Kubernetes 배포 #
Dockerfile (Order Service 예시) #
1FROM openjdk:17-jdk-slim
2WORKDIR /app
3COPY target/order-service.jar .
4ENTRYPOINT ["java", "-jar", "order-service.jar"]docker-compose #
1version: '3'
2services:
3 order-service:
4 build: ./order-service
5 ports: ["8081:8081"]
6 networks: [micro-net]
7 payment-service:
8 build: ./payment-service
9 ports: ["8082:8082"]
10 networks: [micro-net]
11 shipping-service:
12 build: ./shipping-service
13 ports: ["8083:8083"]
14 networks: [micro-net]
15networks:
16 micro-net:Kubernetes Deployment (Order Service) #
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: order-service
5spec:
6 replicas: 2
7 selector:
8 matchLabels:
9 app: order-service
10 template:
11 metadata:
12 labels:
13 app: order-service
14 spec:
15 containers:
16 - name: order-service
17 image: order-service:latest
18 ports:
19 - containerPort: 8081📦 컨테이너 이미지 연계 #
마이크로서비스는 서비스 단위로 독립 이미지를 관리하므로, 이미지 크기·레이어 최적화가 배포 속도와 CI/CD 효율에 직결됩니다. 멀티스테이지 빌드·레이어 캐시·경량 베이스 전략은 별도 글에서 자세히 다룹니다.
🤔 실무 팁 / 고려사항 #
- 통합 로깅·모니터링: 분산된 서비스 로그를 한곳에서(예: Loki/ELK, Prometheus). 추적은 OpenTelemetry.
- 데이터 일관성: Saga 또는 이벤트 기반으로 최종 일관성 확보.
- 장애 격리: Circuit Breaker + 의미 있는 fallback 필수.
- 점진적 전환: 모놀리식에서 시작한다면 Strangler Fig 패턴으로 게이트웨이가 기능을 하나씩 새 서비스로 옮겨 가며 기존 시스템을 점진적으로 대체.
- 배포 자동화: CI/CD + Docker + Kubernetes.
❓ 자주 묻는 질문 #
Q. Saga는 Choreography와 Orchestration 중 무엇이 좋나요? 서비스가 적고 느슨하면 Choreography가 깔끔하고, 단계가 많고 롤백·가시성이 중요하면 Orchestration이 안전합니다.
Q. Circuit Breaker는 직접 구현하나요? Java/Spring이라면 resilience4j 같은 검증된 라이브러리를 쓰는 편이 좋습니다. 상태 전이·메트릭·윈도우 설정을 제공합니다.
Q. 모놀리식을 한 번에 마이크로서비스로 바꿔야 하나요? 아닙니다. Strangler Fig 패턴으로 기능을 하나씩 떼어내 점진적으로 전환하는 것이 안전합니다.
Q. 서비스마다 DB를 꼭 분리해야 하나요? Database per Service가 원칙이지만, 그만큼 데이터 일관성 관리(Saga·이벤트) 부담이 커집니다. 도메인 경계에 맞춰 판단하세요.