1. 이벤트 브로커 스타일이란?
이벤트 기반 아키텍처(EDA)의 개념
- 이벤트 기반 아키텍처(Event-Driven Architecture, EDA)는 시스템의 각 구성 요소가 이벤트(Event)를 중심으로 통신하고 반응하는 구조를 가진다.
- 한 컴포넌트가 이벤트를 발생시키면, 다른 컴포넌트는 해당 이벤트를 수신하여 적절한 동작을 수행한다.
- 컴포넌트 간에는 명시적 호출(Explicit Invocation)이 아닌, 묵시적 호출(Implicit Invocation)로 연결되어있다.
- 느슨한 결합(Loose Coupling)으로 시스템은 서로의 존재를 알 필요 없이 동작한다.
- 이벤트가 발생하면, 이를 필요로 하는 서비스가 알아서 반응하는 비동기적 통신(asynchronous communication)이 이루어진다.
묵시적 호출(Implicit Invocation) 스타일과의 관계
- 프로시저를 직접 호출하는 대신, 이벤트 알림이 다른 모듈의 프로시저를 호출하는 방식이다.
- 컴포넌트는 하나 이상의 이벤트를 브로드캐스트하고, 다른 컴포넌트들은 자신이 관심 있는 이벤트에 등록하여 자동으로 호출된다.
- 컴포넌트 간의 결합도를 낮춰 확장성과 재사용성을 높인다.
이벤트 브로커 스타일의 등장 배경
- 여러 서비스나 모듈 간의 통신을 비동기적으로 처리하면서도 확장성과 내고장성을 보장하기 위해 등장
- 컴포넌트들이 브로커를 통해 간접적으로 통신함으로써 시스템의 복잡도를 줄이고, 장애 전파를 방지한다.
- Apache Kafka, RabbitMQ, Amazon EventBridge, Google Pub/Sub이 대표적인 기술이다.
2. 아키텍처 개요
이벤트 중심 통신 구조의 특징
- 이벤트 브로커 스타일은 시스템의 컴포넌트들이 이벤트를 중심으로 통신하고 반응하는 구조를 가진다.
- 각 컴포넌트는 명시적인 호출 없이 독립적으로 동작하며, 오직 자신이 관심 있는 이벤트에만 반응한다.
- 이벤트가 발생하면(예: 주문 생성, 결제 완료, 재고 변경 등) 브로커가 이를 감지하고 관련된 구독자들에게 전달한다.
- 이벤트를 발행한 서비스는 누가, 언제, 어디서 그 이벤트를 소비하는지 알 필요가 없다.
브로커 스타일과 중재자 스타일의 차이
| 구분 | 이벤트 브로커 스타일 | 이벤트 중재자 스타일 |
|---|
| 이벤트 전달 방식 | 브로커가 단순히 이벤트를 중계 | 중재자가 이벤트의 흐름(워크플로우)을 제어 |
| 구성 요소 간 관계 | 느슨한 결합 (Loose Coupling) | 중재자에 의존 (Tight Coupling) |
| 특징 | 비동기 메시징, 높은 확장성, 단순 구조 | 복잡한 비즈니스 로직 제어 가능 |
| 장점 | 확장성, 성능, 내고장성 우수 | 데이터 일관성, 오류 복구 용이 |
| 단점 | 워크플로우 제어 어려움 | 중재자 의존성, 성능 저하 가능 |
| 대표 기술 | Kafka, RabbitMQ, EventBridge | Apache Camel, Mule, Spring Integration |
- 이벤트 브로커는 이벤트 파이프라인 중심 구조로, 서비스 간 통신을 단순화하고 빠르게 만든다.
- 이벤트 중재자는 중앙 워크플로우 제어자로 이벤트 간 순서를 관리하고 복잡한 비즈니스 로직을 조정한다.
3. 구성 요소
이벤트 브로커 스타일은 이벤트를 매개로 한 비동기 통신 구조로 핵심 구성 요소들이 명확하게 역할을 나누어 시스템의 확장성과 복원력을 높인다.
1) 이벤트 발행자 (Event Publisher)
- 이벤트의 흐름을 시작하는 컴포넌트다.
- 예를 들어 주문 서비스는 주문 생성 이벤트를 발행하고, 결제 서비스는 결제 완료 이벤트를 발행할 수 있다.
- 이벤트는 이벤트 브로커의 채널(토픽)로 전달되어 시스템 전체에 브로드캐스트된다.
- 이벤트 발행 후에는 응답을 기다리지 않고 즉시 반환(비동기 처리)한다.
2) 이벤트 브로커 (Event Broker)
- 이벤트를 수집, 저장, 분배하는 중간 관리자이다.
- 발행자의 이벤트를 수신하여 구독자에게 전달한다.
- 다음과 같은 기능들을 제공한다.
- 토픽/채널 관리
- 메시지 큐 관리 (acknowledgement, retry 등)
- 라우팅 및 필터링 (topic-based routing, fan-out 등)
- 내고장성 및 데이터 영속성 보장
3) 이벤트 구독자 (Event Subscriber)
- 특정 이벤트를 구독(subscribe)하여 이를 비동기적으로 수신하고 처리하는 컴포넌트다.
- 이벤트를 수신하면 비즈니스 로직을 실행하고 필요하다면 새로운 이벤트를 다시 발행하여 체인 형태의 흐름(event chain)을 만든다.
on("OrderCreated", (event) => {
processPayment(event.orderId)
publish("PaymentCompleted", { orderId: event.orderId })
})
4) 이벤트 스트림 프로세서 (Event Stream Processor)
- 실시간으로 발생하는 이벤트 스트림을 분석, 변환, 집계하는 특화된 컴포넌트이다.
- 단순한 브로커 역할을 넘어서, 데이터 흐름 안에서 새로운 이벤트를 만들어내거나 패턴을 감지한다.
- 예를 들어 30초 안에 3건 이상의 결제 실패가 발생하면 FraudAlert 이벤트를 발행과 같은 복합 이벤트 처리를 수행할 수 있다.
5) 커넥터 (Connector)
- 컴포넌트 간 통신을 담당하는 기술적 연결 요소로 JSON, Avro, XML등 데이터 포맷 변환을 지원한다.
- 예를 들어, 주문 서비스가 Kafka 토픽에 JSON 메시지를 발행하면 배송 서비스는 해당 토픽을 Avro 포맷으로 구독해도 브로커가 자동 변환해준다.
요약
| 구성요소 | 역할 | 예시 기술 |
|---|
| Event Publisher | 이벤트 생성 및 발행 | API Gateway, Application Service |
| Event Broker | 이벤트 수집, 저장, 분배 | Kafka, RabbitMQ, EventBridge |
| Event Subscriber | 이벤트 수신 및 처리 | Microservice Consumers |
| Event Stream Processor | 실시간 데이터 처리 | Flink, Spark, Kafka Streams |
| Connector | 통신 및 데이터 포맷 변환 | MQTT, gRPC, AMQP |
4.동작 원리 (Workflow)
이벤트 브로커 스타일은 이벤트를 중심으로 컴포넌트 간의 비동기 통신이 이루어지는 구조이다.
전체적인 흐름 개요
- 이벤트 브로커 스타일은 발행 -> 브로커 -> 구독의 구조로 이루어진다.
- 컴포넌트들은 서로 직접 통신하지 않고, 반드시 브로커를 통해 이벤트를 주고받는다.
-
이벤트 발생 (Event Generation)
- 사용자의 액선, 외부 시스템의 입력, 센서 데이터 등에서 이벤트 발생
-
이벤트 발행 (Publish)
- 이벤트 발행자가 브로커에게 이벤트를 전송
- 이때 이벤트는 특정 토픽이나 채널에 게시
-
이벤트 저장 및 라우팅 (Brokering & Routing)
- 브로커는 이벤트를 큐(queue)에 저장, 해당 이벤트를 구독 중인 모든 구독자에게 라우팅.
- 메시지 포맷 변환, 필터링, 재시도(retry), ack 관리 등도 이 단계에서 수행
-
이벤트 소비 (Consume)
- 구독자는 자신이 관심 있는 이벤트를 수신하고 비즈니스 로직을 실행한다.
- 처리 후, 필요하다면 새로운 이벤트를 발행할 수 있다.
비동기 처리 구조의 특징 (장점)
- 확장성 (Scalability): 구독자 수에 관계없이 시스템이 쉽게 확장됨
- 높은 응답성 (Responsiveness): 발행자는 즉시 반환되어 대기 시간이 없음
- 내고장성 (Resilience): 한 구독자가 다운되더라도 큐에 남은 이벤트로 복구 가능
- 비동기 워크플로우: 여러 이벤트가 병렬로 흐르며 자연스럽게 분산 처리됨
토폴로지
┌──────────────┐
│ Event Broker │
└──────┬───────┘
│
┌───────────┼───────────┐
│ │ │
Publisher A Publisher B Publisher C
│ │ │
▼ ▼ ▼
┌───────────────────────────┐
│ Event Topics │
└───────────────────────────┘
│ │ │
▼ ▼ ▼
Subscriber 1 Subscriber 2 Subscriber 3
- 모든 이벤트는 중앙의 브로커를 통해 분배되며, 서비스 간의 직접 호출이 존재하지 않는다.
- 각 서비스는 독립적 배포(Independent Deployment)와 유연한 확장(Flexible Scaling)이 가능하다.
5. 단점
-
워크플로우 제어의 어려움
- 비동기 이벤트 플로우는 명시적인 순서 제어나 트랜잭션 관리가 어렵다.
- 이벤트가 언제, 어떤 순서로 처리될지 명확하지 않다.
- 전체 흐름의 끝을 추적하기 어렵다.
- 이벤트 간 종속성이 많은 경우 오케스트레이션(orchestration)이 필요.
- 이때 이벤트 브로커 대신 이벤트 중재자 스타일이 더 적합할 수 있다.
-
에러 처리와 복구의 복잡성
- 비동기 처리 특성상 이벤트가 실패했을 때 즉시 롤백하기 어렵다.
- 재시도 로직, DLQ(Dead Letter Queue) 관리 필요
- 메시지 중복 처리나 순서 보장(Ordering)이 어려움
-
데이터 일관성(Consistency) 문제
- 분산된 이벤트 구조에서는 강한 일관성(Strong consistency)대신 최종적 일관성(eventual consistency)을 선택해야 하는 경우가 많다.
- 동일한 이벤트가 여러 서비스에 전달될 때 시점 차이 발생
- 예: 결제가 완료되기 전 배송이 시작되는 상황 방지 필요
- SAGA 패턴, Outbox 패턴 같은 아키텍처적 보완 필요
6. 다른 아키텍처와비교
이벤트 브로커 vs 이벤트 중재자
- 두 스타일 모두 이벤트 기반 구조이지만, 이벤트 브로커는 분산 처리 중심, 이벤트 중재자는 중앙 제어 중심이다.
| 구분 | 이벤트 브로커 스타일 | 이벤트 중재자 스타일 |
|---|
| 구조 | 이벤트 브로커가 중간에서 단순 전달 | 중앙 중재자가 워크플로우 제어 |
| 결합도 | 느슨한 결합 (Loose Coupling) | 상대적으로 높은 결합 (Tight Coupling) |
| 처리 방식 | 비동기적, 병렬 처리 | 순차적, 제어 흐름 기반 |
| 장점 | 확장성, 복원력, 독립 배포 용이 | 흐름 제어 용이, 일관성 유지 |
| 단점 | 전체 프로세스 가시성 낮음 | 단일 실패 지점(Single Point of Failure) 가능 |
| 주요 기술 | Kafka, RabbitMQ, EventBridge | Apache Camel, Mule ESB, Spring Integration |
이벤트 브로커 vs 발행-구독(Pub/Sub) 패턴
- 이벤트 브로커 스타일은 기본적으로 Pub/Sub 패턴을 확장한 형태이다.
- 하지만 단순 Pub/Sub은 중앙 브로커가 없거나, 메시지 전달 보장이 약하다.
| 구분 | Pub/Sub 패턴 | 이벤트 브로커 스타일 |
|---|
| 중개자 | 없음 (직접 통신) | 브로커가 존재 |
| 전달 보장 | 약함 (Best Effort) | 강력함 (At-least-once, Exactly-once) |
| 메시지 관리 | 단순 알림 수준 | 큐잉, 저장, 재시도 등 지원 |
| 확장성 | 소규모 시스템 적합 | 대규모 분산 환경 적합 |
| 예시 | Frontend EventBus, RxJS | Kafka, RabbitMQ, Pub/Sub |
7. 설계 시 고려사항 (Design Consideration)
이벤트 브로커 스타일을 실제 시스템에 적용할 때는 단순한 메시지 전달 구조를 넘어서, 신뢰성과 유지보수성을 고려한 설계가 필요하다.
1) 이벤트 설계 원칙 (Event Design Principles)
- 이벤트는 시스템 간의 계약(Contract) 역할을 하기 떄문에, 명확한 이벤트 이름, 데이터 스키마, 버전 관리가 중요하다.
2) 이벤트 중복 처리 (Idempotency)
- 이벤트는 네트워크나 브로커 장애로 인해 중복 전송될 수 있다.
- 구독자는 동일 이벤트를 여러 번 받아도 결과가 변하지 않도록(idempotent) 설계해야 한다.
- 이벤트 ID를 부여하고, 처리 기록을 저장하여 중복 실행 방지
- 처리된 이벤트 목록 캐시나 DB 테이블 관리
3) 순서 보장 (Ordering)
- 이벤트는 분산 환경에서 병렬로 처리되기 떄문에, 처리 순서가 보장되지 않을 수 있다.
- 동일한 엔티티에 대한 이벤트는 동일한 파티션(Partition Key)으로 보내기
- 순서 의존성이 높을 경우 중재자 (Mediator) 또는 트랜잭션 관리 도입
4) 재시도 및 장애 복구 (Retry & Fault Recovery)
- 비동기 구조에서는 일시적 장애가 발생할 수 있으므로, 재시도 로직(retry) 과 DLQ(Dead Letter Queue) 설계가 필수이다.
- 이벤트 처리 실패 시 -> DLQ로 이동
- 일정 시간 후 자동 재시도 or 수동 복구 *(관련 프로젝트 진행 시 QnA에서 가장 많이 물어보던 질문)
- 장애 원인에 따라 처리 정책 구분
5) 모니터링 및 트레이싱 (Monitoring & Tracing)
- 이벤트 기반 구조는 흐름이 분산되어 있어 디버깅과 장애 추적이 어렵다
- 이벤트 단위로 추적 가능한 모니터링 체계가 필요하다.