Event-Driven Pattern
이 패턴은 시스템의 상태 변화를 이벤트로 표현하고, 이를 기반으로 서비스 간 통신을 구현하는 방식이다.
Event-Driven Pattern은 시스템에서 발생하는 중요한 변화나 행동을 이벤트로 정의하고, 이를 중심으로 시스템을 설계하는 아키텍처 패턴이다.
이 패턴에서는 이벤트의 생성, 전파, 처리가 시스템의 핵심 동작이 된다.
주요 특징:
- 비동기 통신을 기반으로 함
- 서비스 간 느슨한 결합 제공
- 실시간 데이터 처리와 반응성 향상
- 확장성과 유연성 증대
주요 구성 요소
Event-Driven Pattern의 주요 구성 요소는 다음과 같다:
이벤트 생성자(Event Producer):
- 이벤트를 생성하고 발행하는 역할을 하는 컴포넌트.
- 사용자 액션, 시스템 상태의 변화, 외부 시스템의 입력 등에 의해 이벤트를 생성한다.
이벤트 소비자(Event Consumer):
- 이벤트를 수신하고 이에 대한 반응으로 특정 작업을 수행하는 컴포넌트.
- 하나 이상의 이벤트에 대해 반응할 수 있으며, 이벤트의 내용에 따라 다양한 처리를 실행한다.
이벤트 채널(Event Channel):
- 이벤트 생성자와 소비자를 연결하는 매개체 역할을 한다.
- 메시지 큐나 이벤트 버스 등이 이에 해당된다.
이벤트 브로커(Event Broker):
- 이벤트 생성자와 소비자 사이에서 이벤트를 중개하는 역할을 한다.
- 이벤트를 수신, 저장하고 적절한 이벤트 소비자에게 전달한다.
이벤트(Event):
- 시스템이나 서비스의 상태 변화를 나타내는 정보나 신호를 의미한다.
- 일반적으로 헤더(메타데이터)와 바디(실제 데이터)로 구성된다.
이벤트의 구조와 특성
이벤트는 보통 다음과 같은 구조를 가진다:
- 이벤트 ID
- 이벤트 유형
- 이벤트 발생 시간
- 관련 정보
- 이벤트 데이터
- 이벤트 스키마 버전
작동 방식
- 이벤트 생성자가 상태 변화를 감지하고 이벤트를 생성
- 생성된 이벤트는 이벤트 브로커로 전송
- 이벤트 브로커는 해당 이벤트를 구독하고 있는 소비자들에게 이벤트를 전달
- 이벤트 소비자는 수신한 이벤트를 처리하고 필요한 작업 수행
flowchart LR OS[주문 서비스]-->|주문생성 이벤트|EB[이벤트 버스] EB-->|이벤트 수신|PS[결제 서비스] EB-->|이벤트 수신|IS[재고 서비스] EB-->|이벤트 수신|NS[알림 서비스] style EB fill:#f9f,stroke:#333,stroke-width:4px style OS fill:#bbf style PS fill:#bfb style IS fill:#bfb style NS fill:#bfb
장점
- 높은 확장성: 새로운 기능이나 서비스를 쉽게 추가할 수 있다.
- 유연성: 서비스 간 느슨한 결합으로 변경에 대한 영향을 최소화한다.
- 실시간 처리: 이벤트 발생 즉시 관련 서비스에서 처리할 수 있다.
- 복잡한 비즈니스 프로세스 모델링: 현실 세계의 많은 프로세스가 이벤트 중심적이기 때문에 자연스럽게 구현할 수 있다.
단점
- 복잡성 증가: 이벤트의 흐름을 추적하고 관리하는 것이 어려울 수 있다.
- 데이터 일관성 관리: 분산된 환경에서 데이터 일관성을 유지하기 어려울 수 있다.
- 디버깅과 테스트의 어려움: 비동기적이고 분산된 특성으로 인해 문제 해결이 복잡할 수 있다.
적용 사례
- 금융 서비스: 실시간 거래 처리와 사기 탐지에 활용된다.
- e커머스: 주문 처리, 재고 관리 등에 사용된다.
- IoT 시스템: 센서 데이터 처리와 장치 제어에 활용된다.
구현 시 고려사항
- 이벤트 설계와 모델링에 주의를 기울여야 한다.
- 이벤트 처리와 관련한 베스트 프랙티스와 패턴을 적용해야 한다.
- 시스템 전체의 관점에서 모니터링 및 로깅 전략을 수립해야 한다.
- 이벤트의 순서 보장
- 이벤트 스키마 관리와 버전 관리
- 이벤트 저장소 설계
- 장애 복구 전략
- 확장성 계획
이벤트 전송 메커니즘
비교 항목 | 메시지 브로커 전송 (Message Broker) | 이벤트 버스 전송 (Event Bus) |
---|---|---|
기본 개념 | 메시지의 라우팅, 변환, 저장을 담당하는 중개자 역할 | 이벤트를 발행자로부터 구독자로 전달하는 채널 역할 |
아키텍처 특성 | - 독립적인 미들웨어 시스템으로 동작 - 분산 시스템 간 통신 지원 - 높은 확장성과 신뢰성 제공 - 서비스 간 완전한 분리 가능 | - 애플리케이션 내부 컴포넌트로 동작 - 단일 애플리케이션 내 통신 지원 - 간단한 구현과 빠른 처리 - 메모리 내 직접 전달 방식 |
통신 모델 | 점대점(Point-to-Point) 및 발행/구독(Pub/Sub) 지원 | 주로 발행/구독(Pub/Sub) 모델 사용 |
데이터 지속성 | - 디스크 기반의 영구 저장 지원 - 메시지 손실 방지를 위한 저장 메커니즘 - 장애 복구를 위한 백업 지원 - 메시지 이력 추적 가능 | - 메모리 기반의 일시적 저장 - 애플리케이션 재시작 시 데이터 손실 - 영구 저장 필요시 별도 구현 필요 - 실시간 처리 중심 |
확장성 | - 수평적/수직적 확장 용이 - 클러스터링 지원 - 대규모 트래픽 처리 가능 - 파티셔닝을 통한 부하 분산 | - 단일 애플리케이션 범위 내 제한 - 메모리 한계로 인한 제약 - 로컬 처리에 최적화 - 단순한 확장 모델 |
성능 특성 | - 네트워크 통신 오버헤드 발생 - 상대적으로 높은 지연시간 - 대량 메시지 처리에 최적화 - 안정적인 처리량 보장 | - 메모리 내 직접 전달로 빠른 속도 - 최소한의 지연시간 - 로컬 처리 성능 우수 - 단일 프로세스 내 효율적 |
구현 복잡도 | - 복잡한 설정과 관리 필요 - 클러스터 구성 및 운영 - 모니터링 도구 필요 - 장애 처리 메커니즘 필요 | - 단순한 구현과 설정 - 최소한의 관리 부담 - 기본적인 모니터링만 필요 - 단순한 오류 처리 |
사용 사례 | - 마이크로서비스 간 통신 - 분산 시스템 이벤트 처리 - 크로스 플랫폼 통합 - 대규모 이벤트 처리 | - 단일 애플리케이션 내 통신 UI 컴포넌트 간 이벤트 - 모듈 간 느슨한 결합 - 로컬 이벤트 처리 |
오류 처리 | - 자동 재시도 메커니즘 Dead Letter Queue 지원 - 장애 복구 기능 - 메시지 추적 기능 | - 기본적인 예외 처리 - 수동 재시도 구현 필요 - 단순한 오류 로깅 - 메모리 내 상태 관리 |
운영 관리 | - 전문적인 운영 팀 필요 - 복잡한 모니터링 도구 - 백업/복구 계획 필요 - 성능 튜닝 필요 | - 최소한의 운영 관리 - 단순한 모니터링 - 애플리케이션 수준 관리 - 간단한 설정 관리 |
비용 측면 | - 라이선스 비용 발생 가능 - 인프라 운영 비용 - 전문 인력 비용 - 확장 시 추가 비용 | - 최소한의 추가 비용 - 개발 리소스만 필요 - 운영 비용 거의 없음 - 단순한 유지보수 비용 |
보안 | - 강력한 인증/인가 기능 - 전송 구간 암호화 - 접근 제어 정책 - 감사 로깅 지원 | - 애플리케이션 수준 보안 - 단순한 접근 제어 - 내부 통신 중심 - 기본적인 보안 기능 |
메시지 브로커와 이벤트 버스는 분산 시스템에서 통신을 위해 사용되는 중요한 컴포넌트이지만, 몇 가지 주요한 차이점이 있다:
메시지 처리 방식:
- 메시지 브로커:
- 메시지를 받아서 처리한 후 일반적으로 삭제한다.
- 메시지의 순서를 보장하고 장애 발생 시 메시지 손실을 방지하는 데 중점을 둔다.
- 이벤트 버스:
- 이벤트를 영구적으로 저장하고 필요한 시간 동안 보존한다.
- 대용량의 이벤트 데이터를 실시간으로 처리하는 데 중점을 둔다.
- 메시지 브로커:
데이터 지속성
- 메시지 브로커: 메시지는 소비된 후 일반적으로 삭제된다.
- 이벤트 버스: 이벤트는 장기간 저장되어 나중에 재생하거나 분석할 수 있다.
확장성
- 메시지 브로커: 상대적으로 제한된 확장성을 가진다.
- 이벤트 버스: 대규모 데이터 처리에 적합한 높은 확장성을 제공한다.
사용 사례
- 메시지 브로커: 주로 서비스 간 메시지 전달과 작업 분배에 사용된다.
- 이벤트 버스: 실시간 데이터 스트리밍, 이벤트 소싱, 복잡한 이벤트 처리에 적합하다.
구현 예시
- 메시지 브로커: RabbitMQ, Apache ActiveMQ
- 이벤트 버스: Apache Kafka, Amazon Kinesis
이 두 전송 메커니즘은 각각의 장단점이 있으며, 시스템의 요구사항과 규모에 따라 적절한 선택이 필요하다.
이러한 차이점들로 인해 메시지 브로커는 즉각적인 메시지 처리와 작업 분배에 적합하고, 이벤트 버스는 대규모 실시간 데이터 처리와 장기적인 데이터 분석에 더 적합하다.
오류 처리와 복원력
이벤트 재처리:
실패한 이벤트를 재처리하기 위한 메커니즘이 필요하다:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
@Component public class EventProcessor { private final DeadLetterQueue dlq; public void processEvent(Event event) { try { processEventLogic(event); } catch (Exception e) { dlq.send(event); log.error("Failed to process event: {}", event.getId(), e); } } @Scheduled(fixedRate = 5000) public void retryFailedEvents() { List<Event> failedEvents = dlq.readEvents(); failedEvents.forEach(this::processEvent); } }
멱등성 보장:
같은 이벤트가 여러 번 처리되더라도 안전하도록 설계해야 한다:1 2 3 4 5 6 7 8 9 10 11 12 13
@Service public class IdempotentEventHandler { private final ProcessedEventRepository processedEvents; public void handleEvent(Event event) { if (processedEvents.exists(event.getId())) { return; // 이미 처리된 이벤트는 무시 } processEventLogic(event); processedEvents.save(event.getId()); } }
모니터링과 추적
- 이벤트 로깅:
모든 이벤트의 흐름을 추적할 수 있어야 한다:
|
|
- 메트릭 수집:
시스템의 건강 상태를 모니터링하기 위한 메트릭을 수집한다:- 이벤트 처리율
- 실패율
- 처리 지연 시간
- 큐 길이