학습 목표
이벤트 기반 아키텍처(EDA) 가 느슨한 결합·비동기를 만드는 방식임을 설명할 수 있다.
pub/sub과 이벤트 스트리밍의 차이를 설명할 수 있다.
event notification과 event-carried state transfer를 구분할 수 있다.
CQRS가 명령·조회 분리로 읽기·쓰기를 다르게 모델링함을 설명할 수 있다.
문제 상황
- 주문 생성 API가 재고·결제·알림·포인트를 순서대로 REST 호출한다
- 한 곳 503이면 전체 실패, latency는 합산
- 재고 서비스가 주문 API 스키마 변경에 매번 끌려간다
- 동기 결합 + 공유 DTO
- "알림만 추가"인데 주문 팀이 배포해야 한다
- 새 consumer마다 producer 수정
- 조회 API가 쓰기 DB에 무거운 JOIN을 건다
- 읽기 트래픽이 주문 처리를 느리게
- 이벤트를 도입했는데 순서·중복 처리가 없어 데이터가 어긋난다
- at-least-once 전제 설계 없음
MSA에서 동기 체인·async queue를 봤다. 이번 편은 이벤트를 1급 시민으로 두는 EDA다.
1. EDA란
이벤트 기반 아키텍처 — 상태 변화를 이벤트로 발행하고, 관심 있는 consumer가 비동기로 반응한다.
| 개념 | 의미 |
|---|---|
| Event | 과거에 일어난 사실 (OrderPlaced, PaymentCaptured) |
| Producer | 이벤트 발행 (publisher) |
| Consumer | 이벤트 구독·처리 (subscriber) |
| Broker | 이벤트 중개 (Kafka, RabbitMQ, SNS/SQS) |
- Smart endpoints, dumb pipes — MSA와 맞물림: pipe는 전달만, 비즈니스는 endpoint
- Temporal decoupling — producer·consumer가 동시에 떠 있을 필요 없음
2. 동기 체인 vs 이벤트

| 동기 REST 체인 | 이벤트 EDA | |
|---|---|---|
| 결합 | 호출 순서·응답 강결합 | broker 경유 약결합 |
| 장애 | 중간 실패 → 전파 | consumer 독립 재시도 |
| 확장 | 새 기능 = caller 수정 | 새 subscriber 추가 |
| 일관성 | 즉시 강한 일관성 쉬움 | ** eventual consistency** |
| 디버깅 | 호출 스택 추적 | correlation ID·트레이스 필요 |
# 동기 (앞서 Order service 예)
Order → Payment → Inventory → Notification (4 hops)
# 이벤트
Order → publish OrderPlaced
├→ Inventory (reserve)
├→ Notification (email)
└→ Analytics (report)
- 핵심 경로는 동기 + 부가 기능은 이벤트 — 하이브리드가 흔함
3. Pub/Sub과 이벤트 스트리밍

Message queue (pub/sub)
- Topic/Queue에 publish → 한 consumer 또는 fan-out (SQS+SNS, RabbitMQ)
- 메시지 처리 후 삭제 (또는 TTL)
- 작업 분배·알림·백그라운드 job에 적합
Event streaming (log)
- Append-only log — Kafka, Pulsar (
OrderPlacedoffset 1024…) - 보존 기간 동안 재생(replay) 가능
- 여러 consumer group이 독립 offset으로 읽음
- CDC·분석·CQRS projection에 강함
| Queue | Stream (log) | |
|---|---|---|
| 소비 | 처리하면 사라짐 | offset으로 재읽기 |
| 순서 | 보통 파티션/단일 consumer | partition key 순서 |
| 용도 | task queue, notify | event sourcing, stream processing |
- Kafka = 스트리밍 플랫폼 + pub/sub semantics
4. 이벤트 설계
Event notification vs event-carried state
| 유형 | payload | consumer 동작 |
|---|---|---|
| Notification | ID·타입만 (orderId: 9001) |
API/DB 조회로 상세 확보 |
| Carried state | 필요한 필드 포함 (items, amount) | 조회 없이 처리 가능 |
{
"type": "OrderPlaced",
"eventId": "evt-8f2a",
"occurredAt": "2026-06-25T06:00:00Z",
"correlationId": "abc-123",
"data": {
"orderId": "9001",
"userId": "42",
"totalAmount": 12900,
"items": [{ "sku": "A1", "qty": 2 }]
}
}
- 전체 예시는
code/order-placed-event.json참고 - 과거 사실 —
OrderPlaced를 수정하지 않음, 취소는OrderCancelled새 이벤트 - 스키마 버전·호환 (필드 추가는 관대하게, 삭제는 신중)
Idempotency·순서
| 이슈 | 대응 |
|---|---|
| 중복 전달 | eventId 또는 business key로 멱등 처리 |
| 순서 | 같은 orderId는 같은 partition key |
| 지연 | consumer 시간 무관 설계 (재생 대비) |
5. CQRS 개요
CQRS (Command Query Responsibility Segregation) — 쓰기 모델과 읽기 모델을 분리한다.

| 쪽 | 역할 | 예 |
|---|---|---|
| Command | 상태 변경 | PlaceOrder, CancelOrder |
| Query | 조회만 | GetOrderSummary, 검색·대시보드 |
Client ──command──> Order service (write DB)
│
└──event──> Projection updater ──> Read store (denormalized)
Client ──query───> Order read API ──> Read store
| 장점 | 비용 |
|---|---|
| 읽기 스케일·모델 최적화 | ** eventual consistency** |
| 쓰기 단순 트랜잭션 | projection·동기화 운영 |
| 이벤트 log와 자연스럽게 연결 | 디버깅 복잡도 |
- 모든 API에 CQRS는 과함 — 읽기·쓰기 부하·형태가 다를 때
- Event sourcing(상태=이벤트 재생)은 CQRS와 자주 짝지만 필수는 아님
6. EDA + MSA 실무
| 패턴 | 용도 |
|---|---|
| Outbox | DB commit + 이벤트 publish 원자성 |
| Saga | 분산 트랜잭션 → 로컬 TX + 이벤트 + 보상 |
| Dead letter queue | poison message 격리 |
| Schema registry | Avro/JSON schema 버전 관리 |
Order service:
1. INSERT order (local TX)
2. INSERT outbox row (same TX)
3. Relay publishes OrderPlaced to Kafka
Inventory consumer:
4. consume · idempotent reserve · commit offset
- Observability — correlationId로 publish~consume 추적
- Platform — K8s에서 consumer도 Deployment로 독립 스케일
7. 트레이드오프·안티패턴
| EDA 장점 | EDA 비용 |
|---|---|
| 결합↓ · 탄력적 확장 | 분산 디버깅 |
| 피크 흡수 (buffer) | ** eventual consistency** |
| 새 기능 구독만 | broker 운영·ordering |
| 안티패턴 | 증상 |
|---|---|
| Chatty events | 1 API → 이벤트 50개 |
| God topic | 모든 이벤트 한 topic — schema 지옥 |
| 동기 disguised as async | consumer가 blocking RPC 연쇄 |
| 이벤트 없이 CDC만 | 의미·버전 없는 row copy |
8. 정리
- EDA = 사실(event) 발행 + 관심 consumer의 비동기 반응
- Queue vs stream — 재생·fan-out 요구에 따라 선택
- notification vs carried state, 멱등·partition key 설계 필수
- CQRS — command/query 분리, projection으로 읽기 최적화
- MSA와 하이브리드 — 핵심은 동기, 부가·통합은 이벤트
다음에 다룰 것
- 스타일 비교와 선택
- 모놀리스 vs MSA vs EDA, 언제 무엇을 쓸지
해당 내용은 Fundamentals of Software Architecture (Mark Richards, Neal Ford), Designing Data-Intensive Applications (Martin Kleppmann) 의 내용을 기반으로 합니다.
'소프트웨어 아키텍처' 카테고리의 다른 글
| SOLID 원칙 (0) | 2026.06.27 |
|---|---|
| 아키텍처 스타일 비교와 선택 (0) | 2026.06.26 |
| 마이크로서비스 아키텍처 (0) | 2026.06.24 |
| 헥사고날과 클린 아키텍처 (3) | 2026.06.23 |
| 레이어드 아키텍처 (0) | 2026.06.22 |