Message-Driven vs. Event-Driven Architecture

메시지 기반 (Message‑Driven) 과 이벤트 기반 (Event‑Driven) 은 비동기 통신을 통한 분산 시스템 설계 방식이다.

메시지 기반 (Message‑Driven) 는 명령 (Command) 또는 Request-Response 워크플로우 중심이며 수신자 주소를 알고 직접 메시지를 주고 받는다. 반면 이벤트 기반 (Event‑Driven) 는 상태 변화 (State Change) 를 이벤트로 정의하고 Publish-Subscribe (pub/sub) 나 Event Bus 로 브로드캐스트 (Broadcast) 하며, 소비자 목록을 알 필요 없이 느슨하게 결합된 구조로 동작한다.

두 방식은 구현 목적, 응답 실시간성, 확장성, 복잡성 등에서 차이를 보이며, 실무에서는 상호 보완적으로 병용되기도 한다.

핵심 개념

요약 정의

구분Message-Driven Architecture (MDA)Event-Driven Architecture (EDA)
정의명시적 수신자에게 명령 또는 요청을 메시지로 전달하는 아키텍처 스타일상태 변화 등 **사실 (fact)**을 알리는 이벤트를 브로드캐스트하는 아키텍처 스타일
주요 흐름Point-to-Point 메시지 기반 요청/응답 흐름Publish-Subscribe 기반 비동기 반응 흐름
주된 목적작업 실행 지시 (Command)상태 변화 알림 (Event Notification)

핵심 차이 분석

항목Message-DrivenEvent-Driven
의도 (Intent)수신자에게 특정 작업을 수행하라는 명시적 명령상태 변화가 발생했음을 알리는 사실 기반 알림
메시지 타입Command, Request, ResponseEvent (fact-based)
수신자 인식 여부알고 있음 (Direct)–명시적으로 대상 지정모름 (Decoupled)–브로드캐스트, 수신자는 선택적 구독
보통의 처리 흐름1:1 메시지 라우팅 → 작업 수행 후 응답1:N 이벤트 발행 → 여러 소비자가 병렬 반응
결합도느슨하지만 명시적 수신자 존재로 일부 결합느슨한 결합 (완전한 Decoupling 가능)
순서 보장큐 기반 FIFO 순서 보장 가능기본적으로 순서 미보장 (설정으로 보장 가능)
트리거요청에 의해 명령 실행상태 변화 발생 시 이벤트 발행
실행 제어 흐름생산자가 통제소비자가 반응
기술 예시RabbitMQ (Command Queue), JMSKafka (Event Log), AWS SNS

구조 비교 다이어그램

flowchart TD
  subgraph MDA[Message-Driven Architecture]
    A1["Service A (Producer)"] -->|Command: createOrder| Q1[Queue]
    Q1 --> B1["Service B (Consumer)"]
  end

  subgraph EDA[Event-Driven Architecture]
    A2["Service A (Producer)"] -->|Event: OrderCreated| T1[Topic]
    T1 --> B2["Service B (Consumer)"]
    T1 --> C2["Service C (Consumer)"]
  end
  • MDA: 수신자가 명시되어 있고, 메시지가 큐로 전달됨 → Point-to-Point
  • EDA: 이벤트는 브로드캐스트되며, 구독자가 선택적으로 반응 → Publish-Subscribe

실무 혼동 방지 포인트

구분설명
Event ≠ CommandEvent 는 발생한 일, Command 는 수행하라는 지시. 동일한 메시지 구조를 갖더라도 의미는 완전히 다름.
EDA ≠ 비동기 메시징비동기라고 모두 EDA 가 되는 것은 아님. 핵심은 상태 변화 기반의 반응형 설계인지 여부임.
MDA 도 pub/sub 로 구현 가능예: Command 를 Kafka Topic 에 게시 → Consumer 가 명령 처리. 구조는 pub/sub 지만 의도는 MDA임.
복합 사용 가능MDA 와 EDA 는 상호 배타적이지 않음. 보통 Command → 처리 → Event 발행 패턴으로 같이 사용됨.

Message-Driven vs. Event-Driven Architecture 비교

항목Message-Driven Architecture (MDA)Event-Driven Architecture (EDA)
핵심 철학명령 (Command)/요청 (Request) 기반 처리. 명시적 수신자에게 작업을 지시함.사실 (Fact)/상태 변화 (State Change) 중심. 이벤트가 발생하면 여러 수신자가 반응함.
통신 모델Point-to-Point (1:1), 큐 기반 명령 전달Publish-Subscribe (1:N), 이벤트 브로드캐스트
결합도상대적으로 낮음 (비동기), 그러나 수신자 인식이 필요해 일부 의존성 존재매우 낮음. 이벤트 생산자는 소비자를 전혀 알 필요 없음
메시지 성격명령, 요청, 응답 (Command/Request/Response)상태 변화 또는 발생된 사실 (Event/Notification)
처리 흐름정의된 워크플로우에 따라 순차적 또는 동기·비동기 처리 가능비동기, 병렬 트리거 처리. 순차성 보장은 어려움.
트리거 방식명시적 메시지 호출이 트리거상태 변화가 트리거되면 이벤트 발생
확장성큐 및 컨슈머 확장으로 처리량 증가 가능, 구조적 확장에는 제약이 있음구독자 추가만으로 확장 가능. 고유 이벤트 흐름 유지하며 동적 확장에 유리
복원력 및 재처리메시지 ID, 상태 기반으로 재처리 용이 (DLQ 지원, 재시도 등)이벤트 중복 및 순서 처리 로직 별도 필요. 보통 Idempotency 또는 Event Replay 사용
장애 추적메시지 ID 기반 추적 용이. 큐 단위 관찰 가능이벤트 흐름이 분산되기 때문에 End-to-End Trace 도구 필수 (예: OpenTelemetry, Jaeger)
데이터 일관성 모델강한 일관성 (Strong Consistency) 설계 가능최종 일관성 (Eventual Consistency) 중심. 수신자마다 상태가 비동기적으로 반영됨
대표 기술 스택RabbitMQ, ActiveMQ, Amazon SQS, Azure Service BusApache Kafka, AWS SNS/EventBridge, Azure Event Grid, Google Pub/Sub
실무 적용 예시주문 처리, 워크플로우 자동화, 이메일 전송, 비동기 백오피스 작업실시간 데이터 분석, 사용자 활동 이벤트, IoT, 로그 수집, 알림 시스템 등
운영/모니터링 복잡도비교적 단순. 큐 상태, 메시지 실패율 등으로 모니터링 가능이벤트 흐름 추적 복잡. 분산 트레이싱 필요. 이벤트 로스 여부 판단 어렵고 누락 방지 전략 필요
설계 난이도비교적 단순. 송수신 정의 명확. 흐름 제어 용이복잡. 이벤트 정의, 버전 관리, 처리 순서 보장, idempotency 보장 등 추가 고려 요소 다수

심화 분석–설계/운영 관점에서의 고려 요소

항목MDA 고려사항EDA 고려사항
메시지 설계명령 유형 정의, 메시지 순서 보장, 응답 처리 모델 포함이벤트 정의, 불변성 설계, 중복 허용 처리 설계 필요
일관성 보장트랜잭션 메시지 처리, SAGA 패턴 또는 Outbox 패턴 활용 가능이벤트 기반 일관성. eventual consistency 허용, 보완을 위한 event chaining 필요
장애 대응DLQ 구성, 메시지 재시도 및 순서 재정렬 메커니즘 필요이벤트 중복 제거, 순서 보장 옵션 설정, Retry → Duplicate 여부 식별 로직 필요
메시지 추적큐 단위 추적 가능, 로그 기반 문제 진단 용이end-to-end 추적 어려움. 분산 트레이싱 도구 활용 필수 (ex. Jaeger + OpenTelemetry)
테스트 전략Consumer 단위 단위 테스트 및 통합 테스트 비교적 용이이벤트 흐름 기반 테스트 구성 어려움. Mock Event Stream, Consumer Simulation 등 필요

강점과 약점

구분Message-Driven ArchitectureEvent-Driven Architecture
강점• 명확한 통신 구조
• 높은 신뢰성
• 트랜잭션 관리 용이
• 디버깅 및 추적 용이
• 높은 확장성
• 느슨한 결합
• 실시간 반응성
• 시스템 진화 용이
약점• 송수신자 간 결합도
• 확장성 제한
• 복잡한 라우팅
• 단일 장애점 위험
• 이벤트 순서 관리 복잡
• 최종 일관성 문제
• 복잡한 디버깅
• 이벤트 스키마 관리

구조 및 아키텍처

Message-Driven Architecture 구조
graph LR
    A[Client] --> B[Message Producer]
    B --> C[Message Queue]
    C --> D[Message Consumer]
    D --> E[Service A]
    
    F[Service B] --> G[Message Queue 2]
    G --> H[Service C]
    
    subgraph "Message Broker"
        C
        G
    end

필수 구성요소:

  • Message Producer: 메시지 생성 및 전송
  • Message Queue: 메시지 저장 및 라우팅
  • Message Consumer: 메시지 수신 및 처리
  • Message Broker: 메시지 중개 및 관리

선택 구성요소:

  • Dead Letter Queue: 처리 실패 메시지 관리
  • Message Router: 조건부 메시지 라우팅
  • Retry Mechanism: 재시도 로직
Event-Driven Architecture
graph TD
    A[Event Producer] --> B[Event Bus]
    B --> C[Event Consumer 1]
    B --> D[Event Consumer 2]
    B --> E[Event Consumer 3]
    
    C --> F[Service A]
    D --> G[Service B]
    E --> H[Analytics Service]
    
    subgraph "Event Broker"
        B
        I[Event Store]
        J[Event Router]
    end

필수 구성요소:

  • Event Producer: 이벤트 발생 및 발행
  • Event Bus/Broker: 이벤트 배포 및 라우팅
  • Event Consumer: 이벤트 수신 및 반응
  • Event Store: 이벤트 영속화 (선택적)

선택 구성요소:

  • Event Sourcing: 이벤트 기반 상태 재구성
  • CQRS: 읽기/쓰기 분리
  • Event Mesh: 분산 이벤트 인프라

주요 원리 및 작동 원리

Message-Driven Architecture (MDA)

주요 원리:

  • 메시지 단위의 통신
  • 송신자 - 수신자 간 느슨한 결합
  • 메시지 큐 (MQ) 또는 메시지 브로커 기반
  • 재시도, ACK, DLQ(Dead Letter Queue) 등 안정성 보장

작동 원리 다이어그램:

  • 메시지를 보내면 브로커 (큐) 에 쌓이고, 컨슈머가 해당 메시지를 받아 처리.
  • 명령 - 응답/작업 요청 등 전통적 통신 대신, 비동기 큐가 중재함.
sequenceDiagram
    participant Client
    participant Producer
    participant Queue
    participant Consumer

    Client->>Producer: 요청 수행
    Producer->>Queue: 메시지 전송
    Consumer->>Queue: 메시지 Pull 또는 Subscribe
    Queue->>Consumer: 메시지 전달
    Consumer->>Queue: 처리 성공 ACK
Event-Driven Architecture (EDA)

주요 원리:

  • 시스템의 상태 변화 = 이벤트
  • 발행/구독 (Pub/Sub) 모델 사용
  • 발행자 (Producer) 는 수신자를 모름
  • 다수의 소비자가 독립적으로 이벤트 처리

작동 원리 다이어그램:

  • 시스템 내 상태 변화 발생 시 이벤트 생성 후 이벤트 버스에 게시.
  • 이를 구독하는 다수의 컨슈머가 해당 이벤트를 감지해 각각 독립적으로 반응.
sequenceDiagram
    participant Producer
    participant EventBus
    participant Consumer1
    participant Consumer2

    Producer->>EventBus: 이벤트 발생
    EventBus->>Consumer1: 이벤트 전달
    EventBus->>Consumer2: 이벤트 전달
    Consumer1-->>EventBus: (선택적으로 ACK)
    Consumer2-->>EventBus: (선택적으로 ACK)

단점과 문제점 및 해결방안

문제 유형원인 및 설명탐지 및 진단예방 전략해결 방안 및 기법
설계 복잡성 증가이벤트 흐름이 분산되고 시스템 구성요소가 많아짐설계 불일치, 변경 시 영향도 분석도메인 중심 이벤트 모델링 EventStorming아키텍처 문서화, 워크플로 자동화
흐름 추적 어려움이벤트가 비동기적으로 다양한 소비자로 전파됨 → 요청 추적 어려움로그 상관관계, 호출 체인 누락Correlation ID 포함, 메시지 라벨링OpenTelemetry, 분산 트레이싱 도구 (Jaeger 등)
중복 메시지재시도, 네트워크 실패, 브로커 재전송 등으로 중복 메시지 소비 발생중복 데이터 탐지, 로그 기반 비교메시지 ID/해시 기반 필터링Idempotent 소비자 설계, 중복 필터, 메시지 해시 적용
메시지 유실브로커 장애, 네트워크 단절, 일시적 장애 시 메시지가 손실될 수 있음브로커 로그, 큐 상태, 처리 누락 검출영속 메시지 설정, 멀티 브로커 구성DLQ 설정, 재처리 큐 운영, 브로커 HA 구성
이벤트 순서 문제파티셔닝된 소비자나 병렬 처리로 인해 순서가 변경될 수 있음이벤트 순서 로그, 재현 시나리오 분석메시지에 Sequence ID 부여, 순서 고정 큐 사용Kafka Offset 기반 처리, 순서 보장 전략 적용
데이터 일관성 불일치Eventual Consistency 모델로 인해 상태가 일시적으로 불일치 가능상태 불일치 알람, 상태 모니터링보상 트랜잭션 기반의 설계 (Saga)Event Sourcing, CDC (Change Data Capture)
디버깅 난이도비동기 처리 로직은 동기 흐름과 달리 재현과 로깅이 복잡로깅 미비, 트레이스 누락테스트용 메시지 시뮬레이터 활용, 전체 흐름 로깅 활성화Logging 강화, DLQ 재현용 시뮬레이터, Trace Replay Tool
테스트 어려움메시지/이벤트 기반 시스템은 단위 테스트보단 통합 테스트 중심 → 시뮬레이션 요구테스트 커버리지 부족, 비결정성 시나리오 발생컨슈머 모킹, 메시지/이벤트 시뮬레이터 구성통합 테스트 환경 구축 (e.g., TestContainers + Kafka)
장애 탐지 지연분산된 구성 요소로 인해 장애의 위치나 원인 파악이 어려움로그 누락, Alert 지연중앙 집중 로깅 + 모니터링통합 모니터링 (Prometheus, Grafana, ELK Stack 등)
메시지 적체컨슈머 처리 속도 < 메시지 유입 속도 시 큐가 적체되어 서비스 지연 발생큐 크기, 처리율, 대기 시간 모니터링Auto-scaling, Load Leveling 설계큐 확장, 컨슈머 수 증가, 백프레셔 제어 적용

도전 과제

카테고리도전 과제원인/상황영향탐지 및 진단 방식예방 및 해결 전략
신뢰성/안정성메시지/이벤트 손실네트워크 장애, 브로커 중단, Ack 누락데이터 유실, 흐름 중단브로커 상태 모니터링, ACK 로그 분석메시지 영속화 (디스크 저장), 재시도 전략, DLQ 구성
중복 처리메시지 재전송 시 중복 처리QoS 1 이상 사용, 재시도 시 중복 허용 구조데이터 중복 기록, 비즈니스 오류이상 상태 탐지 로직, 중복 수신 로그 분석메시지 ID 추적, Idempotency 키 사용, Deduplication 로직 구현
순서 보장이벤트/메시지 순서 왜곡멀티 스레드 처리, 병렬 큐, 복수 브로커 활용상태 불일치, 잘못된 트랜잭션 실행시퀀스 번호 모니터링, 상태 간 불일치 감지파티션 키 활용, 순서 큐 구성, 메시지 헤더 시퀀싱, 이벤트 버퍼링
일관성 보장분산 트랜잭션 처리 어려움서비스 간 독립 처리, 원자성 부족데이터 무결성 깨짐, 불일치 상태 발생상태 불일치 분석, 상태 확인 작업Saga 패턴, 보상 트랜잭션, Outbox 패턴, Eventually Consistent 설계 적용
관찰 가능성이벤트 흐름 추적의 어려움비동기 처리 + 이벤트 확산 → 인과 관계 파악 복잡장애 진단 지연, 성능 병목 추적 어려움분산 추적 시스템, 로그/메트릭 연동 분석Correlation ID 삽입, OpenTelemetry, Jaeger, Zipkin 등 활용
스키마 진화이벤트 구조 변경에 따른 호환성 이슈메시지/이벤트 필드 추가/삭제, 버전 불일치소비자 에러, 통신 오류스키마 유효성 검사, 호환성 테스트Schema Registry 도입, 버전 관리 전략, Avro/Protobuf 기반 호환 설계
보안/컴플라이언스메시지/이벤트 보호 및 인증 문제분산 경계, 브로커 - 컨슈머 간 전송 보안, 인증 미비민감 데이터 노출, 규정 위반감사 로그, 보안 이벤트 추적메시지 암호화, OAuth2 인증, ACL 설정, Zero Trust 설계 적용
팀 간 협업/거버넌스서비스 간 스키마 및 이벤트 관리 문제서비스 단위로 분산 개발, 계약 부재, 의사소통 단절중복 개발, 비호환 이벤트 설계API/이벤트 문서 리뷰, 아키텍처 검토API First, 표준화된 메시지 컨벤션, ADR 관리, 이벤트 명세 중심의 협업 프로세스
모니터링/운영장애 대응 및 지표 부족분산 컴포넌트 모니터링 누락, 지표 비통합장애 징후 탐지 지연, 복구 시간 증가지연/에러율 모니터링, DLQ 확인메트릭 수집 (Prometheus), Alerting Rule 정의, 중앙 집중 로그 수집 (ELK/EFK 등)
테스트/검증이벤트 흐름 테스트의 어려움컨슈머/프로듀서 간 계약 불명확, 테스트 환경 미흡이벤트 누락/불일치로 인한 기능 실패컨트랙트 기반 테스트 도입, 이벤트 시뮬레이션Consumer-Driven Contract Testing, 테스트 더블 (Double) 및 샌드박스 환경 구축

실무 사용 예시

산업 분야시스템 유형활용 아키텍처 유형주요 목적기대 효과주요 기술 구성
전자상거래주문 처리 시스템MDA → EDA 혼합형주문 → 결제 → 배송의 워크플로우 분리 및 상태 변화 후 다중 시스템 동기화확장성, 처리 신뢰성, 실시간 반응RabbitMQ, Kafka, Redis
금융거래 처리 및 분석 시스템MDA (트랜잭션 처리), EDA (사후 분석)거래 승인 → 계좌 업데이트 후 이벤트 기반 분석/리스크 처리신뢰성, 실시간 탐지, 규제 대응JMS, EventStore, Lambda
물류배송 추적/관리 시스템EDA 중심배송 상태 변화에 따른 이벤트를 실시간 처리 및 분석 연계가시성 확보, 사용자 경험 향상Kafka, AWS SNS, Webhook
IoT/제조센서 기반 데이터 처리EDA 중심대규모 센서 이벤트 스트리밍 처리 및 예측 정비운영 효율성, 지능형 유지보수 실현Kafka, MQTT, Flink
게임멀티플레이어 실시간 게임EDA 중심플레이어 이벤트 발생 → 분석, 개인화, 랭킹, 추천 시스템 연동실시간 반응, 사용자 경험 최적화Kafka, Redis Streams, WebSocket
마케팅/알림푸시 알림, 이벤트 기반 마케팅EDA + Pub/Sub알림, 캠페인, 마케팅 자동화 트리거 처리실시간성, 개인화, 운영 자동화AWS SNS/SQS, Firebase, Webhook
스마트 시티환경/교통 데이터 수집EDA + CEP실시간 센서/트래픽 이벤트 분석 및 정책 트리거정책 대응 속도 향상, 비상 상황 탐지Kafka, Apache Flink, Complex Event Processing
엔터프라이즈마이크로서비스 간 통신MDA + EDA 혼합형비즈니스 프로세스 → 메시지 처리 / 상태 변화 → 이벤트 반응유연한 통합, 독립 배포, 확장성 확보Kafka + RabbitMQ, Outbox Pattern

활용 사례

사례 1: 온라인 쇼핑몰의 주문 처리 시스템

아키텍처 적용 방식:

  • 주문 등록 → 결제 승인 → 재고 차감 → 배송 예약 과정을 각각 분리된 서비스로 구성
  • 각 단계는 메시지 기반 (MDA) 으로 연결, 이벤트 발생 시 각 서비스는 이벤트를 수신하여 동작

시스템 구성

graph TD
A[Order Service] -->|Message| B[Payment Service]
B -->|Message| C[Inventory Service]
C -->|Message| D[Delivery Service]
D -->|Event| E[Notification Service]

Workflow 설명:

  1. 사용자가 주문을 생성하면 메시지가 결제 서비스로 전달됨.
  2. 결제 완료 후 재고 차감 요청 메시지 전송.
  3. 재고 확인 후 배송 예약 요청.
  4. 최종 배송 등록 이벤트 발생 → 이벤트 기반 (Notification) 으로 사용자 알림.

Message-Driven 없을 경우 문제점:

  • 상태 전이 실패 시 복구 어려움.
  • 재시도/보장된 순서 처리 불가능.

Event-Driven 없이 Notification 처리 시 문제점:

  • Notification Service 가 상태를 직접 폴링하거나 상태 공유 구조 필요 → 복잡성 증가.

구현 예시:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 주문 생성 → 메시지 브로커 → 결제 서비스
import pika
import json

# 메시지 브로커 연결
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# 큐 생성
channel.queue_declare(queue='payment_queue', durable=True)

# 메시지 전송 함수
def send_order_to_payment(order_id, amount):
    message = json.dumps({
        "order_id": order_id,
        "amount": amount
    })
    channel.basic_publish(
        exchange='',
        routing_key='payment_queue',
        body=message,
        properties=pika.BasicProperties(delivery_mode=2)
    )
    print(f"Sent order {order_id} for payment.")

send_order_to_payment("ORD-1001", 19900)

connection.close()

위 코드는 주문 생성 후 결제 서비스로 메시지를 큐에 넣는 구조. 결제 서비스는 이를 소비하여 처리하게 된다. 확장성과 안정성 확보에 유리하다.

사례 2: 대형 이커머스의 주문 처리 시스템

시스템 구성: 주문 서비스 → 메시지 브로커 (RabbitMQ) → 재고 서비스, 결제 서비스

아키텍처 다이어그램:

sequenceDiagram
    participant User
    participant OrderService
    participant MQ as RabbitMQ
    participant InventoryService
    participant PaymentService

    User->>OrderService: 주문 요청
    OrderService->>MQ: 주문 메시지 발행
    MQ->>InventoryService: 주문 메시지 전달
    MQ->>PaymentService: 주문 메시지 전달
    InventoryService-->>OrderService: 재고 처리 결과(비동기)
    PaymentService-->>OrderService: 결제 결과(비동기)

Workflow: 주문 발생 → 메시지 발행 → 재고/결제 서비스 각각 독립적으로 처리 및 알림

역할: 동시성 보장, 이벤트/비동기 기반 병렬처리, 확장성 확보

유무 차이점: 메시지 기반/이벤트 기반 아키텍처가 없을 경우 주문 폭주 상황에서 서비스 지연, 장애 확산이 잦음.

구현 예시:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 주문 발생 시 메시지 발행과 큐 소비자 예시

import pika

# 프로듀서: 주문 발생시 메시지 발행
def send_order_message(order_id, data):
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='order_queue')
    channel.basic_publish(exchange='',
                          routing_key='order_queue',
                          body=f"{order_id}:{data}")
    connection.close()

# 컨슈머: 주문 메시지 처리(재고 서비스)
def inventory_consumer():
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='order_queue')
    def callback(ch, method, properties, body):
        order_id, data = body.decode().split(":")
        print(f"재고 처리: 주문번호 {order_id}, 데이터 {data}")
        # 재고 차감 처리 코드 위치
    channel.basic_consume(queue='order_queue', on_message_callback=callback, auto_ack=True)
    print('재고 서비스 대기 중…')
    channel.start_consuming()

각 함수에 주석으로 메시지 발행/수신의 위치와 역할, 비동기 큐 소비를 명확하게 구분

사례 3: 전자상거래 주문 처리 시스템

시스템 구성:

  • 전자상거래 플랫폼에서 Message-Driven 과 Event-Driven 아키텍처를 함께 활용한 하이브리드 접근법을 적용했다.
graph TB
    subgraph "프론트엔드"
        A[웹/모바일 앱]
    end
    
    subgraph "API Gateway"
        B[API Gateway]
    end
    
    subgraph "Message-Driven 영역"
        C[주문 서비스]
        D[결제 서비스]
        E[재고 서비스]
        F[배송 서비스]
        Q1[주문 처리 큐]
        Q2[결제 큐]
        Q3[배송 큐]
    end
    
    subgraph "Event-Driven 영역"
        G[이벤트 버스]
        H[알림 서비스]
        I[분석 서비스]
        J[추천 서비스]
        K[재고 추적 서비스]
        L[고객 서비스]
    end
    
    subgraph "데이터 저장소"
        M[(주문 DB)]
        N[(결제 DB)]
        O[(재고 DB)]
        P[(이벤트 스토어)]
    end
    
    A --> B
    B --> C
    C --> Q1
    Q1 --> D
    D --> Q2
    Q2 --> E
    E --> Q3
    Q3 --> F
    
    C --> G
    D --> G
    E --> G
    F --> G
    
    G --> H
    G --> I
    G --> J
    G --> K
    G --> L
    
    C --> M
    D --> N
    E --> O
    G --> P

Workflow:

  • Message-Driven 처리 흐름:
    1. 주문 생성: 고객이 주문을 생성하면 주문 서비스가 주문 정보를 검증
    2. 재고 확인: 주문 서비스가 재고 서비스에 메시지를 보내 재고 확인 요청
    3. 결제 처리: 재고 확인 완료 후 결제 서비스에 결제 처리 메시지 전송
    4. 배송 요청: 결제 완료 후 배송 서비스에 배송 요청 메시지 전송
  • Event-Driven 처리 흐름:
    1. 이벤트 발행: 각 단계 완료 시 이벤트 발행 (주문 생성됨, 결제 완료됨 등)
    2. 병렬 처리: 여러 서비스가 동일 이벤트를 동시 구독하여 처리
    3. 실시간 반응: 알림, 분석, 추천 등이 실시간으로 동작

역할:

  • Message-Driven Architecture 역할:
    • 핵심 비즈니스 로직의 순차적 처리 보장
    • 트랜잭션 일관성 유지
    • 중요한 업무 프로세스의 신뢰성 확보
  • Event-Driven Architecture 역할:
    • 부가 서비스들의 확장성 제공
    • 시스템 간 느슨한 결합 달성
    • 실시간 반응성 확보

아키텍처 유무에 따른 차이점:

  • 적용 전 (단순 REST API):
    • 모든 서비스가 동기적으로 호출되어 응답 시간 증가
    • 부가 서비스 장애가 핵심 주문 프로세스에 영향
    • 새로운 기능 추가 시 기존 코드 수정 필요
  • 적용 후 (하이브리드 아키텍처):
    • 핵심 프로세스와 부가 서비스의 독립적 처리
    • 부가 서비스 장애가 주문 프로세스에 미치는 영향 최소화
    • 새로운 서비스 추가 시 이벤트 구독만으로 연동 가능
    • 평균 주문 처리 시간 40% 단축, 시스템 가용성 99.9% 달성

구현 예시:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
"""
전자상거래 주문 처리 시스템
Message-Driven과 Event-Driven Architecture 하이브리드 구현
"""

import asyncio
import json
import uuid
from datetime import datetime
from typing import Dict, List, Any, Callable
from enum import Enum
from dataclasses import dataclass, asdict
import logging

# ============================================================================
# 1. 기본 데이터 모델
# ============================================================================

class OrderStatus(Enum):
    """주문 상태 열거형"""
    PENDING = "pending"
    PAYMENT_PROCESSING = "payment_processing"
    PAID = "paid"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

@dataclass
class Order:
    """주문 데이터 클래스"""
    id: str
    customer_id: str
    items: List[Dict[str, Any]]
    total_amount: float
    status: OrderStatus
    created_at: datetime

@dataclass
class Message:
    """메시지 데이터 클래스 (Message-Driven Architecture용)"""
    id: str
    type: str
    payload: Dict[str, Any]
    sender: str
    receiver: str
    created_at: datetime
    correlation_id: str = None

@dataclass
class Event:
    """이벤트 데이터 클래스 (Event-Driven Architecture용)"""
    id: str
    type: str
    payload: Dict[str, Any]
    source: str
    created_at: datetime
    version: str = "1.0"

# ============================================================================
# 2. Message-Driven Architecture 구현
# ============================================================================

class MessageQueue:
    """
    메시지 큐 구현
    - 순차적 메시지 처리를 위한 FIFO 큐
    - 메시지 지속성 및 재시도 메커니즘 제공
    """
    def __init__(self, name: str):
        self.name = name
        self.queue: List[Message] = []
        self.handlers: Dict[str, Callable] = {}
        self.dead_letter_queue: List[Message] = []
        
    async def send_message(self, message: Message):
        """메시지를 큐에 전송"""
        self.queue.append(message)
        logging.info(f"Message sent to {self.name}: {message.type}")
        
    async def subscribe(self, message_type: str, handler: Callable):
        """특정 메시지 타입에 대한 핸들러 등록"""
        self.handlers[message_type] = handler
        
    async def process_messages(self):
        """큐의 메시지들을 순차적으로 처리"""
        while self.queue:
            message = self.queue.pop(0)
            try:
                if message.type in self.handlers:
                    await self.handlers[message.type](message)
                    logging.info(f"Message processed: {message.id}")
                else:
                    logging.warning(f"No handler for message type: {message.type}")
            except Exception as e:
                # 실패한 메시지를 Dead Letter Queue로 이동
                self.dead_letter_queue.append(message)
                logging.error(f"Message processing failed: {e}")

class MessageBroker:
    """
    메시지 브로커 - 여러 큐를 관리하고 메시지 라우팅 담당
    """
    def __init__(self):
        self.queues: Dict[str, MessageQueue] = {}
        
    def create_queue(self, name: str) -> MessageQueue:
        """새로운 메시지 큐 생성"""
        self.queues[name] = MessageQueue(name)
        return self.queues[name]
        
    async def route_message(self, queue_name: str, message: Message):
        """메시지를 지정된 큐로 라우팅"""
        if queue_name in self.queues:
            await self.queues[queue_name].send_message(message)
        else:
            logging.error(f"Queue not found: {queue_name}")

# ============================================================================
# 3. Event-Driven Architecture 구현
# ============================================================================

class EventBus:
    """
    이벤트 버스 구현
    - Publish-Subscribe 패턴으로 이벤트 배포
    - 다수의 구독자에게 동시 이벤트 전달
    """
    def __init__(self):
        self.subscribers: Dict[str, List[Callable]] = {}
        self.event_store: List[Event] = []
        
    async def publish(self, event: Event):
        """이벤트 발행 - 모든 구독자에게 브로드캐스트"""
        # 이벤트 저장 (Event Sourcing)
        self.event_store.append(event)
        
        # 구독자들에게 이벤트 전달
        if event.type in self.subscribers:
            tasks = []
            for subscriber in self.subscribers[event.type]:
                tasks.append(subscriber(event))
            await asyncio.gather(*tasks, return_exceptions=True)
            
        logging.info(f"Event published: {event.type} to {len(self.subscribers.get(event.type, []))} subscribers")
        
    async def subscribe(self, event_type: str, handler: Callable):
        """이벤트 타입에 대한 핸들러 구독"""
        if event_type not in self.subscribers:
            self.subscribers[event_type] = []
        self.subscribers[event_type].append(handler)

# ============================================================================
# 4. 핵심 비즈니스 서비스 (Message-Driven)
# ============================================================================

class OrderService:
    """
    주문 서비스 - 핵심 주문 처리 로직
    Message-Driven 방식으로 순차적 처리 보장
    """
    def __init__(self, message_broker: MessageBroker, event_bus: EventBus):
        self.message_broker = message_broker
        self.event_bus = event_bus
        self.orders: Dict[str, Order] = {}
        
    async def create_order(self, customer_id: str, items: List[Dict], total_amount: float) -> str:
        """주문 생성"""
        order_id = str(uuid.uuid4())
        order = Order(
            id=order_id,
            customer_id=customer_id,
            items=items,
            total_amount=total_amount,
            status=OrderStatus.PENDING,
            created_at=datetime.now()
        )
        
        self.orders[order_id] = order
        
        # Message-Driven: 재고 확인 메시지 전송
        inventory_message = Message(
            id=str(uuid.uuid4()),
            type="check_inventory",
            payload={"order_id": order_id, "items": items},
            sender="order_service",
            receiver="inventory_service",
            created_at=datetime.now(),
            correlation_id=order_id
        )
        
        await self.message_broker.route_message("inventory_queue", inventory_message)
        
        # Event-Driven: 주문 생성 이벤트 발행
        await self.event_bus.publish(Event(
            id=str(uuid.uuid4()),
            type="order_created",
            payload=asdict(order),
            source="order_service",
            created_at=datetime.now()
        ))
        
        return order_id
        
    async def handle_inventory_confirmed(self, message: Message):
        """재고 확인 완료 메시지 처리"""
        order_id = message.payload["order_id"]
        if order_id in self.orders:
            # Payment 서비스로 메시지 전송
            payment_message = Message(
                id=str(uuid.uuid4()),
                type="process_payment",
                payload={
                    "order_id": order_id,
                    "amount": self.orders[order_id].total_amount
                },
                sender="order_service",
                receiver="payment_service",
                created_at=datetime.now(),
                correlation_id=order_id
            )
            
            await self.message_broker.route_message("payment_queue", payment_message)

class PaymentService:
    """
    결제 서비스 - 결제 처리 담당
    """
    def __init__(self, message_broker: MessageBroker, event_bus: EventBus):
        self.message_broker = message_broker
        self.event_bus = event_bus
        
    async def handle_payment_request(self, message: Message):
        """결제 처리 메시지 핸들러"""
        order_id = message.payload["order_id"]
        amount = message.payload["amount"]
        
        # 결제 처리 로직 (시뮬레이션)
        await asyncio.sleep(1)  # 결제 처리 시간 시뮬레이션
        
        # 배송 서비스로 메시지 전송
        shipping_message = Message(
            id=str(uuid.uuid4()),
            type="prepare_shipping",
            payload={"order_id": order_id},
            sender="payment_service",
            receiver="shipping_service",
            created_at=datetime.now(),
            correlation_id=order_id
        )
        
        await self.message_broker.route_message("shipping_queue", shipping_message)
        
        # 결제 완료 이벤트 발행
        await self.event_bus.publish(Event(
            id=str(uuid.uuid4()),
            type="payment_completed",
            payload={"order_id": order_id, "amount": amount},
            source="payment_service",
            created_at=datetime.now()
        ))

# ============================================================================
# 5. 부가 서비스들 (Event-Driven)
# ============================================================================

class NotificationService:
    """
    알림 서비스 - 고객에게 실시간 알림 전송
    Event-Driven 방식으로 다양한 이벤트에 반응
    """
    def __init__(self):
        self.sent_notifications: List[Dict] = []
        
    async def handle_order_created(self, event: Event):
        """주문 생성 이벤트 처리"""
        order_data = event.payload
        notification = {
            "type": "order_confirmation",
            "customer_id": order_data["customer_id"],
            "message": f"주문 {order_data['id']}가 접수되었습니다.",
            "timestamp": datetime.now()
        }
        self.sent_notifications.append(notification)
        logging.info(f"Notification sent: {notification['message']}")
        
    async def handle_payment_completed(self, event: Event):
        """결제 완료 이벤트 처리"""
        order_id = event.payload["order_id"]
        notification = {
            "type": "payment_confirmation",
            "order_id": order_id,
            "message": f"주문 {order_id}의 결제가 완료되었습니다.",
            "timestamp": datetime.now()
        }
        self.sent_notifications.append(notification)
        logging.info(f"Notification sent: {notification['message']}")

class AnalyticsService:
    """
    분석 서비스 - 주문 및 결제 데이터 분석
    실시간으로 비즈니스 메트릭 수집
    """
    def __init__(self):
        self.metrics: Dict[str, int] = {
            "total_orders": 0,
            "total_revenue": 0,
            "failed_payments": 0
        }
        
    async def handle_order_created(self, event: Event):
        """주문 생성 분석"""
        self.metrics["total_orders"] += 1
        logging.info(f"Analytics: Total orders = {self.metrics['total_orders']}")
        
    async def handle_payment_completed(self, event: Event):
        """결제 완료 분석"""
        amount = event.payload["amount"]
        self.metrics["total_revenue"] += amount
        logging.info(f"Analytics: Total revenue = {self.metrics['total_revenue']}")

# ============================================================================
# 6. 시스템 통합 및 실행
# ============================================================================

class ECommerceSystem:
    """
    전자상거래 시스템 메인 클래스
    Message-Driven과 Event-Driven 아키텍처를 통합 관리
    """
    def __init__(self):
        # 인프라 컴포넌트 초기화
        self.message_broker = MessageBroker()
        self.event_bus = EventBus()
        
        # 큐 생성
        self.inventory_queue = self.message_broker.create_queue("inventory_queue")
        self.payment_queue = self.message_broker.create_queue("payment_queue")
        self.shipping_queue = self.message_broker.create_queue("shipping_queue")
        
        # 서비스 초기화
        self.order_service = OrderService(self.message_broker, self.event_bus)
        self.payment_service = PaymentService(self.message_broker, self.event_bus)
        self.notification_service = NotificationService()
        self.analytics_service = AnalyticsService()
        
    async def setup(self):
        """시스템 설정 및 핸들러 등록"""
        # Message-Driven 핸들러 등록
        await self.inventory_queue.subscribe("check_inventory", self._handle_inventory_check)
        await self.payment_queue.subscribe("process_payment", self.payment_service.handle_payment_request)
        await self.shipping_queue.subscribe("prepare_shipping", self._handle_shipping_preparation)
        
        # Event-Driven 구독자 등록
        await self.event_bus.subscribe("order_created", self.notification_service.handle_order_created)
        await self.event_bus.subscribe("order_created", self.analytics_service.handle_order_created)
        await self.event_bus.subscribe("payment_completed", self.notification_service.handle_payment_completed)
        await self.event_bus.subscribe("payment_completed", self.analytics_service.handle_payment_completed)
        
    async def _handle_inventory_check(self, message: Message):
        """재고 확인 처리 (시뮬레이션)"""
        order_id = message.payload["order_id"]
        items = message.payload["items"]
        
        # 재고 확인 로직 (시뮬레이션)
        await asyncio.sleep(0.5)
        
        # 재고 확인 완료 메시지를 주문 서비스로 전송
        confirmation_message = Message(
            id=str(uuid.uuid4()),
            type="inventory_confirmed",
            payload={"order_id": order_id, "status": "confirmed"},
            sender="inventory_service",
            receiver="order_service",
            created_at=datetime.now(),
            correlation_id=order_id
        )
        
        # 주문 서비스의 핸들러 직접 호출 (실제로는 큐를 통해 처리)
        await self.order_service.handle_inventory_confirmed(confirmation_message)
        
    async def _handle_shipping_preparation(self, message: Message):
        """배송 준비 처리 (시뮬레이션)"""
        order_id = message.payload["order_id"]
        
        # 배송 준비 로직
        await asyncio.sleep(0.3)
        
        # 배송 준비 완료 이벤트 발행
        await self.event_bus.publish(Event(
            id=str(uuid.uuid4()),
            type="shipping_prepared",
            payload={"order_id": order_id},
            source="shipping_service",
            created_at=datetime.now()
        ))
        
    async def process_all_queues(self):
        """모든 큐의 메시지 처리"""
        tasks = []
        for queue in self.message_broker.queues.values():
            tasks.append(queue.process_messages())
        await asyncio.gather(*tasks)
        
    async def create_order(self, customer_id: str, items: List[Dict], total_amount: float):
        """주문 생성 및 처리"""
        order_id = await self.order_service.create_order(customer_id, items, total_amount)
        await self.process_all_queues()
        return order_id
        
    def get_metrics(self):
        """시스템 메트릭 조회"""
        return {
            "analytics": self.analytics_service.metrics,
            "notifications_sent": len(self.notification_service.sent_notifications),
            "events_stored": len(self.event_bus.event_store)
        }

# ============================================================================
# 7. 사용 예시 및 테스트
# ============================================================================

async def main():
    """시스템 실행 및 테스트"""
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
    
    # 시스템 초기화
    ecommerce = ECommerceSystem()
    await ecommerce.setup()
    
    print("=== 전자상거래 시스템 시작 ===")
    
    # 테스트 주문 생성
    test_items = [
        {"product_id": "P001", "name": "노트북", "quantity": 1, "price": 1500000},
        {"product_id": "P002", "name": "마우스", "quantity": 2, "price": 50000}
    ]
    
    # 첫 번째 주문
    print("\n--- 첫 번째 주문 처리 ---")
    order1 = await ecommerce.create_order("CUST001", test_items, 1600000)
    print(f"주문 생성됨: {order1}")
    
    # 두 번째 주문
    print("\n--- 두 번째 주문 처리 ---")
    order2 = await ecommerce.create_order("CUST002", test_items[:1], 1500000)
    print(f"주문 생성됨: {order2}")
    
    # 시스템 메트릭 출력
    print("\n--- 시스템 메트릭 ---")
    metrics = ecommerce.get_metrics()
    print(f"분석 데이터: {metrics['analytics']}")
    print(f"전송된 알림 수: {metrics['notifications_sent']}")
    print(f"저장된 이벤트 수: {metrics['events_stored']}")
    
    # 이벤트 스토어 내용 출력
    print("\n--- 이벤트 히스토리 ---")
    for event in ecommerce.event_bus.event_store:
        print(f"{event.created_at}: {event.type} from {event.source}")

if __name__ == "__main__":
    asyncio.run(main())

MDA (Message-Driven Architecture) & EDA (Event-Driven Architecture) 혼합 아키텍처

MDA+EDA 혼합 아키텍처 도입 전략

상황이유
명령형 워크플로우 필요주문 → 결제 → 재고 차감 → 배송 등 명확한 순서가 존재
상태 변화 기반 이벤트 발생 필요’ 결제 완료됨 ‘, ’ 배송 시작됨 ’ 등의 이벤트 발생 필요
시스템 간 통합이 복잡함일부 서비스는 명령 기반, 일부는 상태 반응 기반으로 구성됨
확장성과 디커플링 모두 필요서비스 간 동기화 및 반응형 처리의 동시 구현 필요

전략적 구성 원칙

전략설명
Command = Message (MDA)명령적 흐름은 메시지 큐 기반 (e.g. 결제 요청, 배송 예약 등)
State Change = Event (EDA)상태 변화는 이벤트로 브로드캐스트 (e.g. 배송 완료 이벤트)
도메인 경계 기반 메시지/이벤트 분리메시지는 요청 (Command), 이벤트는 상태 (State) 변화 중심으로 설계
비즈니스 로직은 메시지 기반, 리액션은 이벤트 기반주 흐름은 메시지 처리로, 후속 작업 (알림, 분석 등) 은 이벤트 처리로 분리
메시지 우선, 이벤트 보완 구조 적용강한 신뢰성과 제어가 필요한 영역은 메시지, 반응형은 이벤트

도입 시 아키텍처 구성

flowchart TD
    A[Order Service] -->|Command| B["Message Broker (RabbitMQ)"]
    B --> C[Payment Service]
    C -->|Event: PaymentCompleted| D["Event Bus (Kafka)"]
    D --> E[Shipping Service]
    D --> F[Notification Service]
    D --> G[Analytics Service]
  • Message Broker: Command 중심 워크플로우 조율
  • Event Bus: 상태 변화 이벤트 전파
  • 결제 완료 이벤트가 여러 서비스로 전파됨 (Shipping, Notification, Analytics)

적용 시 고려사항

고려항목설명권장 기술/패턴
메시지/이벤트 명확한 구분Command 는 의도 (Intent), Event 는 결과 (Result)CQRS 패턴
메시지 처리 보장중복 처리 방지, 실패 보상 등Idempotent Consumer, DLQ
이벤트 흐름 추적이벤트 경로 추적 가능해야 함OpenTelemetry, Jaeger
트랜잭션 분산 처리원자성 보장 안 되는 경우 처리 방식 명확화Saga Pattern
데이터 일관성 확보Command-Event 순서가 비동기인 경우 주의Outbox Pattern
이벤트 처리 순서순서 중요 시 파티션 키 기반 처리 필요Kafka 파티션 사용
테스트 전략 수립메시지 시뮬레이션, 이벤트 발행 모킹Contract Test, Simulation Broker

혼합 아키텍처의 실무 적용 예시

시스템MDA 역할EDA 역할도입 효과
주문 시스템주문 등록, 결제 요청결제 완료 → 배송 시작, 알림, 분석명확한 흐름 + 반응형 서비스 분리
IoT 시스템디바이스 제어 명령센서 데이터 수신 이벤트명령형 + 대규모 이벤트 수집
금융 시스템대출 심사 요청심사 완료 이벤트 → 상태 변경 및 통보신뢰성 + 실시간 대응
교육 플랫폼시험 시작 명령시험 완료 → 결과 발표, 이메일 발송명확한 트리거 + 다양한 이벤트 소비자

기술 조합 예시

목적메시지 시스템 (MDA)이벤트 시스템 (EDA)
명령/요청 워크플로우RabbitMQ, AWS SQS, ActiveMQ-
상태 변화 이벤트 전파-Apache Kafka, AWS EventBridge
복잡한 트랜잭션 처리Saga Orchestrator with Message BusChoreography 기반 Event Flow
유저 인터페이스 반응성-WebSocket, SSE, Kafka Stream
이벤트 저장-Event Sourcing, Change Data Capture

실제 메시지 / 이벤트 구조 설계 예시 (JSON Schema)

Message-Driven (Command) 메시지 예시
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "CreateInvoiceCommand",
  "type": "object",
  "properties": {
    "command_id": { "type": "string", "format": "uuid" },
    "order_id": { "type": "string" },
    "customer_id": { "type": "string" },
    "amount": { "type": "number" },
    "issued_at": { "type": "string", "format": "date-time" }
  },
  "required": ["command_id", "order_id", "customer_id", "amount", "issued_at"]
}
  • 의도: 송신자가 수신자에게 CreateInvoice 작업을 명령
  • 전송 대상: 특정 컨슈머 (InvoiceService)
Event-Driven (Event) 메시지 예시
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "OrderCreatedEvent",
  "type": "object",
  "properties": {
    "event_id": { "type": "string", "format": "uuid" },
    "event_type": { "type": "string", "enum": ["OrderCreated"] },
    "occurred_at": { "type": "string", "format": "date-time" },
    "payload": {
      "type": "object",
      "properties": {
        "order_id": { "type": "string" },
        "customer_id": { "type": "string" },
        "total_price": { "type": "number" }
      },
      "required": ["order_id", "customer_id", "total_price"]
    }
  },
  "required": ["event_id", "event_type", "occurred_at", "payload"]
}
  • 의도: 주문이 생성되었다는 사실 전달
  • 소비자: InvoiceService, AnalyticsService, NotificationService다수

MDA / EDA 구조 예시 코드 (Python & Node.js)

MDA - Python (RabbitMQ)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# publisher.py
import pika
import json
import uuid
from datetime import datetime

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='invoice_commands')

message = {
    "command_id": str(uuid.uuid4()),
    "order_id": "ORD-1001",
    "customer_id": "CUST-789",
    "amount": 120.5,
    "issued_at": datetime.utcnow().isoformat()
}
channel.basic_publish(
    exchange='',
    routing_key='invoice_commands',
    body=json.dumps(message)
)
print("Message sent.")
connection.close()
1
2
3
4
5
6
7
# consumer.py
def callback(ch, method, properties, body):
    data = json.loads(body)
    print(f"Processing command: {data['command_id']} → Create invoice for order {data['order_id']}")

channel.basic_consume(queue='invoice_commands', on_message_callback=callback, auto_ack=True)
channel.start_consuming()
EDA - JavaScript (Kafka with Node.js)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// producer.js
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ clientId: 'order-app', brokers: ['localhost:9092'] });

const producer = kafka.producer();

async function publishEvent() {
  await producer.connect();
  const event = {
    event_id: 'evt-' + Date.now(),
    event_type: 'OrderCreated',
    occurred_at: new Date().toISOString(),
    payload: {
      order_id: 'ORD-1001',
      customer_id: 'CUST-789',
      total_price: 99.99
    }
  };

  await producer.send({
    topic: 'order.events',
    messages: [{ key: event.event_id, value: JSON.stringify(event) }]
  });

  console.log('Event published.');
  await producer.disconnect();
}

publishEvent();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// consumer.js
const kafka = new Kafka({ clientId: 'invoice-service', brokers: ['localhost:9092'] });
const consumer = kafka.consumer({ groupId: 'invoice-consumer-group' });

async function run() {
  await consumer.connect();
  await consumer.subscribe({ topic: 'order.events', fromBeginning: true });

  await consumer.run({
    eachMessage: async ({ message }) => {
      const event = JSON.parse(message.value.toString());
      console.log(`Received event ${event.event_type} for order ${event.payload.order_id}`);
    }
  });
}
run();

CQRS + Event Sourcing 기반 혼합 아키텍처 흐름도

sequenceDiagram
  actor User
  participant API as API Gateway
  participant CMD as Command Handler
  participant DB as Write DB
  participant EventBus as Event Bus
  participant ReadModel as Read DB (Projection)
  participant Invoice as Invoice Service

  User ->> API: HTTP POST /orders
  API ->> CMD: CreateOrderCommand
  CMD ->> DB: Persist Order (Event Sourced)
  CMD -->> EventBus: Publish OrderCreatedEvent
  EventBus -->> Invoice: OrderCreatedEvent
  EventBus -->> ReadModel: Project OrderView

  Note over ReadModel: Eventually consistent
  1. 사용자가 주문 생성 요청 (Command)
  2. Command Handler가 상태 변경 처리 → 이벤트 저장소에 기록
  3. OrderCreatedEventEvent Bus에 발행
  4. InvoiceService 등은 이벤트를 기반으로 후속 작업
  5. Read Model은 Projection 하여 조회 모델을 구성 (CQRS)

Outbox 패턴 기반 MDA ↔ EDA 브리지 구현

Outbox 패턴은 트랜잭션 경계 내에서 메시지 저장 → 이후 메시지를 브로커에 발행하는 전략으로, MDA 의 안정성과 EDA 의 반응성을 연결하는 주요 패턴이다.

아키텍처 흐름 개요
flowchart TD
  A[Service A: Command Handler] -->|DB 트랜잭션| B[(Order Table + Outbox Table)]
  B --> C["Outbox Poller (CDC or Cron)"]
  C --> D[Event Publisher]
  D --> E[Kafka/EventBridge]
  E --> F["Consumer Services (Invoice, Analytics, Notification)"]
  1. Write 트랜잭션
    • 서비스는 DB 트랜잭션 안에서 도메인 데이터와 함께 Outbox Table 에 이벤트를 기록한다.
  2. Outbox Poller 또는 CDC
    • 별도 프로세스가 outbox 테이블을 주기적으로 스캔하거나 CDC 를 통해 변경 감지.
  3. Event 발행기
    • 폴링된 메시지를 Kafka 나 EventBridge 등에 EDA 이벤트로 게시.
  4. 다수의 Consumer 가 이벤트 구독
Outbox Table 구조 예시
컬럼명설명
idUUID
aggregate_id관련 도메인 ID
type이벤트 타입 (ex: OrderCreated)
payloadJSON 포맷의 이벤트 데이터
statusPENDING, SENT, FAILED
created_at생성 시각
Python 예시 (SQLAlchemy + Kafka)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1. Command 처리 시 Outbox 삽입
def create_order(order_data):
    order = Order(**order_data)
    db.session.add(order)

    event = OutboxEvent(
        type="OrderCreated",
        payload=json.dumps({"order_id": order.id, "total": order.total}),
        status="PENDING"
    )
    db.session.add(event)
    db.session.commit()
1
2
3
4
5
6
7
# 2. Poller: 주기적으로 Kafka에 발행
def publish_events():
    events = db.session.query(OutboxEvent).filter_by(status="PENDING").all()
    for event in events:
        kafka_producer.send("order.events", event.payload)
        event.status = "SENT"
    db.session.commit()

MDA (Message-Driven Architecture) & EDA (Event-Driven Architecture) 혼합 아키텍처 With Kafka

아키텍처적으로 설계 관점과 메시지 사용 방식이 달라야 하며, Kafka 가 본질적으로 Event-Driven 친화적인 플랫폼이라는 점을 이해하고, MDA 스타일에 맞게 보완 설계가 필요하다.

Kafka 하나로 MDA & EDA 처리하는 방식 요약
아키텍처 유형Kafka 적용 방식설명
MDA (명령 기반)Point-to-Point 방식 구현 (Consumer Group 활용)특정 명령을 하나의 서비스가 처리하도록 설정 (1:1 처리 구조)
EDA (이벤트 기반)Pub/Sub 방식 구현 (Consumer Group 없이 독립 구독)동일 이벤트를 여러 서비스가 독립적으로 병렬 반응 (1:N 구조)
MDA with Kafka–구현 방법

Kafka 는 원래 Pub/Sub 구조지만, Consumer Group을 활용하면 MDA 의 Point-to-Point 스타일로 구현할 수 있다.

flowchart LR
  Producer -->|command: createInvoice| KafkaTopic
  KafkaTopic -->|ConsumerGroup: invoice-service| ConsumerA
  • 동일 Consumer Group 내에서는 단일 인스턴스만 메시지를 소비
  • 즉, Kafka 에서도 MDA 스타일의 명령 처리 가능
EDA with Kafka–구현 방법

Kafka 본연의 Event-Driven 처리. 여러 Consumer Group 이 각각 이벤트를 수신하고 처리 가능하다.

flowchart LR
  Producer -->|event: OrderCreated| KafkaTopic
  KafkaTopic -->|ConsumerGroup: invoice| InvoiceService
  KafkaTopic -->|ConsumerGroup: notification| NotificationService
  KafkaTopic -->|ConsumerGroup: analytics| AnalyticsService
  • 이벤트는 모든 ConsumerGroup 에 복제 전파됨
  • 병렬 반응 가능하며, 완전한 Pub/Sub 패턴 구현
Kafka 기반 MDA & EDA 혼합 예시 구조
flowchart TD
  API[User Command API]
  API -->|Command| KafkaCmd[Kafka: order.commands]
  KafkaCmd -->|Group: order-handler| OrderService

  OrderService -->|Event| KafkaEvt[Kafka: order.events]
  KafkaEvt --> InvoiceService
  KafkaEvt --> AnalyticsService
  KafkaEvt --> NotificationService
  • order.commands: Command Topic (MDA 역할)
  • order.events: Event Topic (EDA 역할)
구현 시 주의할 점
항목주의사항
명령 vs 이벤트 토픽 분리MDA 스타일은 command.topic, EDA 스타일은 event.topic 으로 분리하는 것이 좋음
역할 분리 명확히CommandConsumer, EventConsumer 역할 분리해서 구현
Idempotency 필수Kafka 는 중복 수신 가능 → 컨슈머는 반드시 중복 처리 방어
Dead Letter Queue (DLQ)MDA 의 경우 실패 메시지는 DLQ 처리 구조 반드시 설계
Schema Registry 활용Avro/JSON 스키마를 등록해 구조 변경 관리 필요
Kafka 기반 MDA / EDA 분리 구현 예시 (Python)

기본 전제:

  • Kafka 서버 주소: localhost:9092
  • Command Topic: order.commands
  • Event Topic: order.events
  • Group ID:
    • MDA Consumer: order-command-handler
    • EDA Consumers: invoice-service, notification-service
Command Producer (MDA 용)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# send_command.py
from kafka import KafkaProducer
import json
import uuid
from datetime import datetime

producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

command = {
    "command_id": str(uuid.uuid4()),
    "command_type": "CreateOrder",
    "timestamp": datetime.utcnow().isoformat(),
    "payload": {
        "order_id": "ORD-1001",
        "customer_id": "C-123",
        "amount": 99.99
    }
}

producer.send("order.commands", value=command)
producer.flush()
print(f"✅ Command sent: {command}")
Command Consumer (MDA 처리기: 1:1 처리)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# command_consumer.py
from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    "order.commands",
    bootstrap_servers='localhost:9092',
    group_id="order-command-handler",
    value_deserializer=lambda m: json.loads(m.decode('utf-8')),
    auto_offset_reset='earliest',
    enable_auto_commit=True
)

for message in consumer:
    command = message.value
    print(f"📥 Received Command: {command['command_type']} for Order {command['payload']['order_id']}")
    
    # Command 처리 후 이벤트 발행 (EDA 트리거)
    from kafka import KafkaProducer
    producer = KafkaProducer(
        bootstrap_servers='localhost:9092',
        value_serializer=lambda v: json.dumps(v).encode('utf-8')
    )
    event = {
        "event_id": str(uuid.uuid4()),
        "event_type": "OrderCreated",
        "occurred_at": command["timestamp"],
        "payload": command["payload"]
    }
    producer.send("order.events", value=event)
    producer.flush()
    print(f"📤 Published Event: {event['event_type']}")
Event Consumer (EDA: 다수 컨슈머)

Invoice Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# invoice_consumer.py
from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    "order.events",
    bootstrap_servers='localhost:9092',
    group_id="invoice-service",
    value_deserializer=lambda m: json.loads(m.decode('utf-8')),
    auto_offset_reset='earliest',
    enable_auto_commit=True
)

for message in consumer:
    event = message.value
    print(f"🧾 [Invoice Service] 처리 중: {event['event_type']} - Order {event['payload']['order_id']}")

Notification Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# notification_consumer.py
from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    "order.events",
    bootstrap_servers='localhost:9092',
    group_id="notification-service",
    value_deserializer=lambda m: json.loads(m.decode('utf-8')),
    auto_offset_reset='earliest',
    enable_auto_commit=True
)

for message in consumer:
    event = message.value
    print(f"📣 [Notification Service] 사용자에게 알림 전송: {event['payload']['customer_id']}")
실행 순서 요약
  1. command_consumer.py → MDA Consumer 실행
  2. invoice_consumer.py, notification_consumer.py → EDA Consumers 실행
  3. send_command.py → 명령 전송
  4. 흐름:
    • order.commands → Order 처리
    • 처리 후 order.events 로 Event 발행
    • Event 는 여러 서비스에서 병렬로 소비됨
아키텍처 흐름 시각화
sequenceDiagram
  participant A as send_command.py
  participant B as command_consumer.py
  participant Kafka1 as Topic: order.commands
  participant Kafka2 as Topic: order.events
  participant C as invoice_consumer.py
  participant D as notification_consumer.py

  A->>Kafka1: Command: CreateOrder
  Kafka1->>B: MDA Consumer (Command 처리)
  B->>Kafka2: Event: OrderCreated
  Kafka2->>C: Invoice 처리
  Kafka2->>D: Notification 처리

Command vs. Event 구분 기준 및 메시지 라우팅 전략

구분 기준

항목Command (명령)Event (이벤트)
의도행동을 유도 (Do something)사실을 알림 (Something happened)
시간미래 (미실행 상태)과거 (이미 발생함)
수신자단일 수신자 (명확한 담당자)0 개 이상 수신자 (누가 받을지는 모름)
응답 필요 여부일반적으로 Yes (결과 필요)일반적으로 No (단방향 브로드캐스트)
구성 요소Command ID, Target, Payload, Expected ResultEvent ID, Event Type, Timestamp, Payload

라우팅 전략

전략 유형설명예시
명령 라우팅 (Command Routing)큐 이름이나 라우팅 키로 명시적으로 라우팅. 보통 Point-to-Point 방식 사용invoice.create.commandInvoiceService 큐에 직접 전달
이벤트 브로드캐스트주제 기반 토픽에 게시. 관심 있는 소비자가 구독order.events → 여러 서비스가 동시에 반응
EventType 기반 라우팅이벤트의 type 필드 기반으로 동적 라우팅 수행Kafka topic 내부에서 "event_type" == "OrderCreated" 필터
Contextual Routing메시지에 포함된 도메인 정보나 컨텍스트를 기준으로 라우팅“VIP 고객 이벤트만 분석 서비스로 전파 "

메시지 라우팅 예시 (Kafka Stream)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "event_id": "abc-123",
  "event_type": "OrderCreated",
  "occurred_at": "2025-07-17T14:00:00Z",
  "payload": {
    "order_id": "ORD-101",
    "customer_id": "C-999",
    "is_vip": true
  }
}
  • is_vip == true → Analytics 서비스로 전송
  • event_type == OrderCreated → Invoice, Notification, OrderHistory 모두 수신

Kafka Streams, Flink, NiFi 등을 이용하여 내용 기반 라우팅을 손쉽게 구현할 수 있음.

실무에서 효과적 적용을 위한 고려사항

카테고리고려 항목Message-Driven Architecture (MDA)Event-Driven Architecture (EDA)권장 사항 및 전략
1. 메시지/이벤트 설계메시지/이벤트 정의명시적 메시지 타입 (Command/Request/Response 구분)이벤트 의미 중심 정의 (상태 변화, 도메인 사상 중심)변하지 않는 계약 기반 설계 (Contract), 타입 명확화, 이벤트 이름 표현 일관성 유지
스키마 관리명시적 메시지 계약 관리이벤트 스키마의 하위 호환성 및 버전 관리 필요JSON Schema / Protobuf + Schema Registry / AsyncAPI 활용
2. 장애 및 실패 처리실패 메시지 처리DLQ(Dead Letter Queue), Retry, Timeout 처리중복 이벤트 처리, 순서 보장, 보상 트랜잭션 필요Idempotency Key 적용, 메시지 순서 처리 (Partition Key, Session)
장애 전파 제어처리 실패 시 오류 큐 분리 및 재처리 중심이벤트 컨슈머 오류 시에도 전체 시스템 영향 최소화Circuit Breaker, Retry with Backoff, Outbox Pattern 적용
3. 트랜잭션 관리일관성 처리큐 기반 트랜잭션 처리 필요이벤트 흐름 기반 일관성 설계 (Eventually Consistent)Saga 패턴 활용 (로컬 트랜잭션 + 보상 로직 분리), Outbox + Polling 기반 데이터 연동
4. 운영/모니터링지표 수집큐 깊이, 처리 지연, 에러율 중심이벤트 발생률, 처리 지연, 누락 이벤트 탐지Prometheus + Grafana, Alerting Rule, DLQ 모니터링
흐름 추적메시지 ID 및 Correlation ID 기반 흐름 추적이벤트 흐름 추적 및 인과 관계 분석 필요Distributed Tracing (OpenTelemetry, Jaeger, Zipkin 등) 적용
5. 테스트 전략계약 기반 테스트Producer/Consumer 간 명시적 메시지 계약 필요Event Flow 기반 테스트 및 상태 전이 검증Consumer-Driven Contract Test, Schema Validation Test
6. 팀/조직 구조서비스 소유권 및 거버넌스서비스 단위로 메시지 주고받는 명확한 책임 구조이벤트 발행자/구독자 간 소유권 불분명 가능성 있음Conway’s Law 기반 서비스 - 팀 정렬, 이벤트 명세에 대한 거버넌스 체계 수립

최적화를 위한 고려사항

카테고리최적화 대상메시지 기반 아키텍처 (MDA)이벤트 기반 아키텍처 (EDA)공통 권장 전략 및 설명
1. 성능처리량 & 지연 시간배치 처리, 큐 기반 병렬 소비스트림 기반 처리, 이벤트 파이프라인 분산 처리파티셔닝, 병렬 소비자 구성, 비동기화
2. 저장소스토리지 효율성메시지 압축 (Gzip, Snappy), TTL 기반 자동 삭제 설정이벤트 압축 + 장기 아카이빙 (예: S3, Glacier)압축 알고리즘 적용, 저장 주기 분리
3. 네트워크대역폭 효율성메시지 배칭 및 경량 메시지 포맷 사용이벤트 배칭, 미니멀 이벤트 페이로드 설계배치 전송, 프로토콜 최소화 (e.g., Protobuf)
4. 메모리메모리 사용 최적화메시지 캐시 활용 (Redis, LRU 등), 큐 기반 버퍼링스트림 버퍼 조절, 이벤트 리플레이 캐시적응형 버퍼 크기 조정, 백프레셔 적용
5. 확장성수평 확장브로커 클러스터 구성, Consumer 확장이벤트 버스 확장, Auto-scaling 기반 소비자 구성클라우드 네이티브 Auto-scaling, 분산 트래픽 라우팅
6. 정확성중복 방지 & 유실 대응메시지 ID 추적, 오프셋 관리, 재처리 시 idempotent 설계이벤트 중복 필터링, Exactly-once 처리 (Kafka EOS 등)재시도 + 중복 제거 전략, 메시지 추적 ID 기반 처리
7. 지연 최적화지연 최소화경량 메시지 포맷 (JSON → Protobuf), 브로커 처리 최적화실시간 이벤트 전달 + 스트리밍 최적화 (Kafka, Flink 등)경량화, 실시간 처리 우선 순위 설계
8. 운영 관리리소스 활용도큐 대기 시간, DLQ 메시지 수, 처리율 등 메트릭 기반 운영이벤트 처리 실패율, 라그 (Lag), 소비자 상태 추적Prometheus + Grafana 모니터링, Alerting 구성
9. 장애 대응오류 회복력DLQ, Retry Policy, Circuit Breaker 설정이벤트 재처리 및 재전파, 컨슈머 복구 전략자동 Failover, 상태 기반 재처리 설계

주목할 내용

카테고리항목설명관련 아키텍처
** 설계 원칙**Idempotency동일 메시지 반복 처리 시 중복 효과 방지. 정합성과 안전성을 위한 핵심 속성MDA, EDA
순서 보장 (Ordering)메시지 순서대로 처리 보장 필요 시 파티션, 키, 세션 단위로 설계 (e.g., Kafka partition key)MDA
CQRS 패턴Command(쓰기) 와 Query(읽기) 모델 분리로 확장성 및 성능 최적화EDA 중심
** 패턴과 전략**Saga Pattern분산 트랜잭션을 보상 트랜잭션으로 분리 관리하여 롤백 없이 일관성 유지 (비동기 트랜잭션 제어)MDA, EDA
Event Sourcing시스템 상태를 이벤트 로그로 저장, 재생성 가능한 상태 복원 및 감사 추적 가능EDA
** 인프라/플랫폼**Apache Kafka고성능 이벤트 스트리밍 플랫폼, Pub/Sub 및 로그 기반 저장 포함MDA, EDA
RabbitMQ전통적인 메시지 큐, 복잡한 라우팅 및 보증 수준 처리에 적합MDA
AWS SQS/SNS클라우드 기반 메시징 서비스, 확장성과 관리 편의성 제공MDA, EDA
** 운영/모니터링**Dead Letter Queue (DLQ)실패 메시지 격리 보관 큐, 장애 원인 분석 및 재처리 기반MDA, EDA
Distributed Tracing마이크로서비스 및 메시지 기반 시스템 간 흐름 시각화 및 성능 병목 분석 (예: Jaeger, OpenTelemetry)MDA, EDA
메트릭 수집처리량, 지연, 실패율 등의 지표 수집으로 SLA 및 성능 관리MDA, EDA
로그 집계 (Logging)구조화된 메시지 기반 로그를 통해 디버깅 및 이벤트 트레킹 가능MDA, EDA
** 확장성과 복구성**수평 확장성 (Scalability)컨슈머/리스너 개별 확장이 용이하며 메시지 큐 기반 구조가 동적 스케일아웃에 적합MDA, EDA
이벤트 기반 민첩성시스템의 상태 변화에 따라 리액티브하게 동작하여 민첩성 확보EDA
Event Store이벤트의 영속적 저장을 통한 재처리, 복구, 비즈니스 감사 가능EDA

반드시 학습해야할 내용

카테고리주제/영역핵심 항목설명
1. 이론적 기반분산 시스템 이론CAP TheoremConsistency, Availability, Partition Tolerance 간의 트레이드오프 원리
최종 일관성 (Eventually Consistent)분산 환경에서 강한 일관성 대신 허용되는 지연된 동기화 모델
ACID vs BASE전통 트랜잭션 모델과 느슨한 일관성 모델의 비교
2. 메시징 패턴통신 패턴Request-Reply동기적 요청 - 응답 처리 구조 (예: HTTP, RPC)
Publish-Subscribe비동기 이벤트 기반 다중 수신자 구조 (예: Kafka Topics)
Message QueueFIFO 기반의 메시지 큐 구조 (예: RabbitMQ Queue)
3. 이벤트 아키텍처이벤트 중심 설계Event Sourcing모든 상태 변경을 이벤트로 저장해 재구성 가능
CQRS명령 (Command) 과 조회 (Query) 모델 분리
Event Store이벤트 영속화를 위한 저장소 (예: Kafka, EventStoreDB)
4. 트랜잭션 처리분산 트랜잭션 관리Saga Pattern보상 트랜잭션 기반의 분산 트랜잭션 처리
Idempotent Design재시도 시 중복 처리를 방지하는 설계 전략
Retry / DLQ실패 메시지 재처리 및 대체 큐 전략 (Dead Letter Queue)
5. 구현 프레임워크메시징 브로커 구성 요소RabbitMQ / Kafka대표적인 메시징 시스템, 큐/토픽 기반 메시지 처리
Event Bus이벤트 기반 통합 시스템의 중심 통신 채널
Service Mesh메시지 기반 마이크로서비스 간 통신 관리 (예: Istio)
API Gateway클라이언트 요청을 내부 서비스로 라우팅하는 진입 지점
6. 운영 및 관찰성시스템 관찰성 구성분산 트레이싱 (Tracing)서비스 간 호출 관계 추적 (예: OpenTelemetry, Jaeger)
메트릭 수집 (Metrics)처리량, 지연시간 등 시스템 지표 모니터링 (예: Prometheus)
중앙 로그 관리 (Logging)구조화된 로그 수집 및 검색 (예: ELK Stack)

용어 정리

카테고리용어정의 및 설명관련 아키텍처
** 시스템 구성요소**Message Broker생산자 - 소비자 간 메시지를 송수신하고 버퍼링, 라우팅, 전송 보장 처리MDA, EDA
Event Bus이벤트 발행/구독 기반의 전파 중심 메시징 채널 (메시지 라우팅 포함)EDA
Event Store발생한 모든 이벤트를 영속적으로 저장하는 저장소EDA
Dead Letter Queue (DLQ)처리 실패 메시지를 격리 보관하여 후속 처리나 모니터링에 활용하는 큐MDA, EDA
Projection이벤트 로그 (Event Stream) 를 읽기 모델로 변환하는 과정EDA
Correlation ID요청 - 응답/처리 흐름 추적을 위한 메시지 간 고유 식별자MDA, EDA
** 메시지/이벤트 단위**Message명시적 명령 (Command), 요청 (Request), 응답 (Response), 데이터 전달을 포함한 단위MDA
Event시스템에서 발생한 상태 변화나 사상 (Occurrence) 을 의미. 결과 지향적EDA
** 통신/설계 패턴**Publish/Subscribe (Pub/Sub)발행자와 구독자 간 직접적 연결 없이 메시지를 중개 채널을 통해 전달하는 패턴MDA, EDA
Saga Pattern분산 트랜잭션을 보상 트랜잭션 (Compensation) 으로 관리하는 패턴MDA, EDA
Compensating Transaction실패한 작업을 취소하거나 보정하는 트랜잭션MDA, EDA
Circuit Breaker장애 확산을 방지하고 빠른 실패 처리를 유도하는 안정성 제어 패턴MDA, EDA
Event Sourcing상태를 DB 에 저장하는 대신, 발생한 이벤트 시퀀스를 저장하고 이를 재생해 상태를 복원EDA
** 품질 및 운영 특성**Idempotency (멱등성)동일 메시지를 여러 번 처리해도 결과가 동일하게 유지되는 속성MDA, EDA
Eventual Consistency즉시 일관성은 보장하지 않지만 시간이 지나면 일관성을 확보하는 모델EDA
Back Pressure처리량 초과 시 시스템 안정성 유지를 위한 흐름 제어 기법MDA, EDA
Bulkhead장애 격리를 위한 시스템 분할 전략 (마이크로서비스의 독립 실행 보장)MDA, EDA
Distributed Tracing다중 서비스 간 요청 흐름을 추적하기 위한 기법 (예: Jaeger, Zipkin)MDA, EDA

참고 및 출처