2026년 4월 6일

이벤트 기반 아키텍처 (EDA): 느슨한 결합으로 확장성과 유연성을 확보하는 설계 원칙

100
이벤트 기반 아키텍처 (EDA): 느슨한 결합으로 확장성과 유연성을 확보하는 설계 원칙

이벤트 기반 아키텍처 (EDA): 느슨한 결합으로 확장성과 유연성을 확보하는 설계 원칙

이벤트 기반 아키텍처 (EDA): 느슨한 결합으로 확장성과 유연성을 확보하는 설계 원칙

1. 개념 소개: 변화에 반응하는 유연한 시스템

1. 개념 소개: 변화에 반응하는 유연한 시스템

소프트웨어 시스템은 끊임없이 변화합니다. 새로운 기능이 추가되고, 사용자 트래픽은 폭증하며, 기존 서비스는 다른 서비스와 복잡하게 얽히게 됩니다. 이러한 환경에서 시스템을 유연하고 확장 가능하며 장애에 강하게 만드는 것은 모든 개발자의 숙명입니다. 바로 이때 **이벤트 기반 아키텍처(Event-Driven Architecture, EDA)**가 강력한 해답을 제시합니다.

정의

이벤트 기반 아키텍처는 시스템의 각 구성 요소가 **이벤트(Event)**를 발행(publish)하거나 구독(subscribe)하여 서로 통신하는 아키텍처 스타일입니다. 여기서 이벤트는 "시스템에서 발생한 중요한 사실"을 의미합니다. 예를 들어, "사용자가 생성되었다", "주문이 접수되었다", "결제가 완료되었다" 등이 이벤트가 될 수 있습니다.

탄생 배경

전통적인 모놀리식 아키텍처에서는 모든 기능이 하나의 큰 덩어리로 묶여 있었습니다. 이 방식은 개발 초기에는 간단하지만, 시스템이 커질수록 다음과 같은 문제에 직면합니다.

  • 강한 결합(Tight Coupling): 한 서비스의 변경이 다른 서비스에 영향을 미쳐 배포와 유지보수가 어려워집니다.
  • 확장성 한계: 특정 기능에 부하가 집중되어도 전체 시스템을 스케일 아웃해야 하는 비효율이 발생합니다.
  • 단일 장애 지점(Single Point of Failure): 한 부분의 장애가 전체 시스템의 중단으로 이어질 수 있습니다.

마이크로서비스 아키텍처의 등장으로 이러한 문제들이 부분적으로 해결되었지만, 마이크로서비스 간의 효과적인 통신 방식 또한 중요해졌습니다. REST API와 같은 동기식 통신은 여전히 서비스 간의 직접적인 의존성을 만들고, 호출하는 서비스가 응답을 기다려야 하는 비효율성을 야기했습니다.

EDA는 이러한 동기식, 강결합 통신의 한계를 극복하기 위해 탄생했습니다. 이벤트를 중심으로 비동기적으로 통신함으로써, 서비스 간의 의존성을 줄이고 시스템의 유연성과 확장성을 극대화하려는 시도입니다.

왜 중요한가?

2026년 현재, 클라우드 네이티브 환경과 마이크로서비스는 거의 표준처럼 자리 잡았습니다. 이런 환경에서 EDA는 다음과 같은 이유로 더욱 중요해지고 있습니다.

  1. 느슨한 결합(Loose Coupling): 이벤트 발행자는 이벤트를 처리할 구독자가 누구인지, 몇 명인지 알 필요가 없습니다. 이는 서비스 간의 직접적인 의존성을 제거하여, 각 서비스가 독립적으로 개발, 배포, 확장될 수 있게 합니다.
  2. 높은 확장성(Scalability): 특정 이벤트 처리 부하가 증가하면, 해당 이벤트를 처리하는 컨슈머 인스턴스만 늘리면 됩니다. 시스템 전체를 확장할 필요가 없어 효율적입니다.
  3. 향상된 내결함성(Fault Tolerance): 발행자와 구독자가 직접 통신하지 않으므로, 한 서비스에 장애가 발생해도 다른 서비스에 미치는 영향이 최소화됩니다. 이벤트 브로커가 이벤트를 저장하고 있어, 장애 복구 후에도 이벤트를 다시 처리할 수 있습니다.
  4. 유연한 아키텍처 진화: 새로운 기능을 추가할 때 기존 코드를 수정할 필요 없이, 새로운 컨슈머를 추가하여 특정 이벤트를 구독하게 하면 됩니다. 이는 시스템의 변화에 대한 적응력을 높입니다.
  5. 실시간 처리(Real-time Processing): 이벤트 발생 즉시 처리 로직이 실행될 수 있어, 실시간 데이터 처리 및 반응형 시스템 구축에 유리합니다.

2. 핵심 원리 설명: 우체국 시스템 비유

2. 핵심 원리 설명: 우체국 시스템 비유

이벤트 기반 아키텍처의 핵심 원리를 이해하기 위해 우체국 시스템을 비유로 들어봅시다.

일반적인 통신 (강한 결합): 친구에게 직접 전화 걸기 친구에게 무언가를 알려주고 싶을 때, 직접 전화를 걸어서 이야기합니다. 친구가 전화를 받지 않거나 통화 중이면 메시지를 전달할 수 없고, 내가 친구의 전화번호를 정확히 알고 있어야 합니다.

이벤트 기반 아키텍처 (느슨한 결합): 우체국을 통한 편지 발송 내가 편지를 써서 우체통에 넣습니다. 우체국은 이 편지를 받아서, 주소(이벤트 타입)에 따라 편지를 받아볼 사람(구독자)에게 배달합니다.

  • 나 (Event Producer/Publisher): 편지를 쓰는 사람. 내가 편지를 우체통에 넣는 행위가 '이벤트 발행'입니다. 나는 누가 이 편지를 읽을지, 몇 명이 읽을지 알 필요가 없습니다. 그저 편지를 보낼 뿐입니다.
  • 편지 (Event): 시스템에서 '일어난 일'에 대한 정보. "새로운 주문이 발생했습니다"라는 내용의 편지, "사용자 정보가 업데이트되었습니다"라는 내용의 편지 등입니다.
  • 우체국 (Event Broker/Message Broker): 편지를 중개하는 역할. 내가 보낸 편지를 받아서 목적지에 따라 분류하고 배달합니다. 편지를 받는 사람들은 "나는 이 유형의 편지를 받아보고 싶다"고 미리 우체국에 신청(구독)해둡니다. 우체국은 그 신청에 따라 편지를 전달합니다.
  • 편지를 받는 사람 (Event Consumer/Subscriber): 편지를 받아 처리하는 사람. "새로운 주문" 편지를 받은 사람은 재고를 확인하거나 배송을 준비합니다. "사용자 정보 업데이트" 편지를 받은 사람은 고객 데이터베이스를 갱신합니다.

주요 구성 요소

  1. 이벤트 (Event): 시스템에서 발생한 상태 변화를 나타내는 불변의 사실(Immutable Fact). 일반적으로 과거 시제로 명명됩니다. (예: OrderPlaced, UserRegistered, PaymentSucceeded). 이벤트는 단순히 발생한 사실을 알릴 뿐, 특정 액션을 명령하지 않습니다.
  2. 이벤트 발행자 (Event Producer/Publisher): 시스템에서 어떤 이벤트가 발생했을 때, 해당 이벤트를 생성하여 이벤트 브로커에게 전달하는 주체입니다. 이벤트에 대한 정보만 알고 있을 뿐, 누가 이 이벤트를 처리할지는 모릅니다.
  3. 이벤트 구독자 (Event Consumer/Subscriber): 특정 유형의 이벤트를 구독하고, 해당 이벤트가 발생했을 때 이를 수신하여 필요한 비즈니스 로직을 수행하는 주체입니다.
  4. 이벤트 브로커 (Event Broker/Message Broker): 이벤트 발행자와 구독자 사이에서 이벤트를 중개하는 핵심 컴포넌트입니다. 발행된 이벤트를 수신하여 저장하고 있다가, 해당 이벤트를 구독한 모든 구독자에게 전달하는 역할을 합니다. (예: Apache Kafka, RabbitMQ, AWS SQS/SNS, Google Cloud Pub/Sub).

동작 방식 (다이어그램 묘사)

+-------------------+       +-----------------+       +-------------------+
| Event Producer A  |------>|                 |------>| Event Consumer X  |
| (주문 서비스)      |       |                 |       | (결제 서비스)      |
+-------------------+       |                 |       +-------------------+
        |                   |                 |
        |  Event:           | Event Broker    |       +-------------------+
        |  "OrderPlaced"    | (카프카, 래빗MQ)  |------>| Event Consumer Y  |
        +------------------>|                 |       | (재고 서비스)      |
                            |                 |       +-------------------+
                            |                 |
                            |                 |       +-------------------+
                            |                 |------>| Event Consumer Z  |
                            |                 |       | (배송 서비스)      |
                            +-----------------+       +-------------------+
  1. **주문 서비스 (Producer A)**에서 고객으로부터 주문을 받습니다.
  2. 주문 처리가 완료되면, "OrderPlaced" 이벤트를 생성하여 이벤트 브로커에 발행합니다.
  3. 이벤트 브로커는 이 이벤트를 받아서 저장하고, "OrderPlaced" 이벤트를 구독하고 있는 모든 서비스에게 이 이벤트를 전달합니다.
  4. **결제 서비스 (Consumer X)**는 "OrderPlaced" 이벤트를 받아 결제를 처리합니다.
  5. **재고 서비스 (Consumer Y)**는 "OrderPlaced" 이벤트를 받아 해당 상품의 재고를 감소시킵니다.
  6. **배송 서비스 (Consumer Z)**는 "OrderPlaced" 이벤트를 받아 배송 준비를 시작합니다.

이 과정에서 주문 서비스는 결제, 재고, 배송 서비스의 존재를 알 필요가 없습니다. 그저 이벤트만 발행할 뿐입니다. 각 컨슈머는 이벤트를 독립적으로 처리하며, 만약 새로운 서비스(예: 마케팅 서비스)가 "OrderPlaced" 이벤트를 기반으로 프로모션을 진행하고 싶다면, 단순히 이 이벤트를 구독하기만 하면 됩니다. 기존 시스템에는 어떤 변경도 필요 없습니다.

3. 코드 예제: 간단한 EDA 시뮬레이션

실제 이벤트 브로커를 사용하는 것은 복잡할 수 있으므로, 여기서는 개념 이해를 돕기 위해 간단한 인메모리 이벤트 브로커를 만들어 시뮬레이션 해보겠습니다.

예제 1: Python - 사용자 등록 이벤트 처리

새로운 사용자가 등록되면, 환영 이메일을 보내고, 사용자 통계를 업데이트하는 시나리오입니다.

import collections
import uuid
import datetime

# 1. Event Broker 클래스 정의 (간단한 인메모리 구현)
class EventBroker:
    def __init__(self):
        # 각 이벤트 타입에 대한 구독자 목록을 저장
        self.subscribers = collections.defaultdict(list)

    def subscribe(self, event_type, handler):
        """특정 이벤트 타입을 구독하는 핸들러를 등록합니다."""
        self.subscribers[event_type].append(handler)
        print(f"DEBUG: '{handler.__name__}'가 '{event_type}' 이벤트를 구독했습니다.")

    def publish(self, event_type, data):
        """이벤트를 발행하고, 해당 이벤트를 구독하는 모든 핸들러에게 전달합니다."""
        event_id = str(uuid.uuid4())
        timestamp = datetime.datetime.now().isoformat()
        event = {
            "id": event_id,
            "type": event_type,
            "timestamp": timestamp,
            "data": data
        }
        print(f"\nINFO: '{event_type}' 이벤트 발행됨 (ID: {event_id})")
        
        if event_type in self.subscribers:
            for handler in self.subscribers[event_type]:
                try:
                    handler(event)
                except Exception as e:
                    print(f"ERROR: '{handler.__name__}'에서 이벤트 처리 중 오류 발생: {e}")
        else:
            print(f"WARNING: '{event_type}' 이벤트를 구독하는 핸들러가 없습니다.")

# 2. Event Producers (이벤트 발행자)
class UserService:
    def __init__(self, broker):
        self.broker = broker

    def register_user(self, username, email):
        user_data = {"username": username, "email": email}
        print(f"UserService: 새 사용자 '{username}' 등록 시도...")
        # 사용자 등록 로직 (DB 저장 등) ...
        print(f"UserService: 사용자 '{username}' 등록 완료.")
        
        # 'UserRegistered' 이벤트 발행
        self.broker.publish("UserRegistered", user_data)

# 3. Event Consumers (이벤트 구독자)
def send_welcome_email(event):
    """사용자 등록 시 환영 이메일을 보내는 핸들러."""
    user_data = event["data"]
    print(f"EmailService: '{user_data['email']}'로 환영 이메일 전송 (이벤트 ID: {event['id']})")

def update_user_statistics(event):
    """사용자 등록 시 통계를 업데이트하는 핸들러."""
    user_data = event["data"]
    print(f"AnalyticsService: '{user_data['username']}' 사용자 등록 통계 업데이트 (이벤트 ID: {event['id']})")

def log_event(event):
    """모든 이벤트를 로깅하는 핸들러."""
    print(f"LoggingService: 이벤트 수신 - Type: {event['type']}, Data: {event['data']}")

# 4. 시스템 설정 및 실행
if __name__ == "__main__":
    event_broker = EventBroker()

    # 컨슈머들이 이벤트 구독
    event_broker.subscribe("UserRegistered", send_welcome_email)
    event_broker.subscribe("UserRegistered", update_user_statistics)
    event_broker.subscribe("UserRegistered", log_event) # 모든 이벤트를 로깅할 수도 있지만, 여기서는 UserRegistered만

    # UserService가 이벤트를 발행
    user_service = UserService(event_broker)
    user_service.register_user("Alice", "[email protected]")
    
    user_service.register_user("Bob", "[email protected]")

    # 새로운 이벤트 타입 발행 (구독자가 없는 경우)
    event_broker.publish("ProductViewed", {"product_id": "P123", "user": "Alice"})

코드 설명:

  • EventBroker: subscribe 메서드로 핸들러를 등록하고, publish 메서드로 이벤트를 발행합니다. 발행된 이벤트는 등록된 모든 핸들러에게 전달됩니다.
  • UserService: 사용자를 등록한 후 UserRegistered 이벤트를 브로커에 발행합니다. 이 서비스는 이메일이나 통계 서비스의 존재를 알지 못합니다.
  • send_welcome_email, update_user_statistics, log_event: 각각 UserRegistered 이벤트를 구독하여 처리하는 독립적인 함수(핸들러)입니다.

예제 2: JavaScript (Node.js) - 주문 처리 이벤트

Node.js의 EventEmitter 모듈을 사용하여 간단한 주문 처리 시나리오를 구현해봅니다. EventEmitter는 실제 분산 환경의 브로커는 아니지만, 동일 프로세스 내에서 이벤트 기반 통신을 구현할 때 유용하며, EDA의 발행-구독 개념을 이해하는 데 도움이 됩니다.

const EventEmitter = require('events');

// 1. Event Broker 역할을 하는 EventEmitter 인스턴스
class OrderEventEmitter extends EventEmitter {}
const orderBroker = new OrderEventEmitter();

// 2. Event Producers (이벤트 발행자)
class OrderService {
    constructor(broker) {
        this.broker = broker;
    }

    placeOrder(orderId, userId, amount) {
        const orderData = { orderId, userId, amount, timestamp: new Date().toISOString() };
        console.log(`\nOrderService: 주문 ${orderId} 접수 시도...`);
        // 주문 DB 저장 등 핵심 로직...
        console.log(`OrderService: 주문 ${orderId} 접수 완료.`);
        
        // 'orderPlaced' 이벤트 발행
        this.broker.emit('orderPlaced', orderData);
    }
}

// 3. Event Consumers (이벤트 구독자)
function processPayment(orderEvent) {
    console.log(`PaymentService: 주문 ${orderEvent.orderId} (금액: ${orderEvent.amount}) 결제 처리 시작 (이벤트 발생 시각: ${orderEvent.timestamp})`);
    // 결제 처리 로직...
    console.log(`PaymentService: 주문 ${orderEvent.orderId} 결제 완료.`);
}

function updateInventory(orderEvent) {
    console.log(`InventoryService: 주문 ${orderEvent.orderId} 상품 재고 업데이트 (이벤트 발생 시각: ${orderEvent.timestamp})`);
    // 재고 업데이트 로직...
    console.log(`InventoryService: 주문 ${orderEvent.orderId} 재고 업데이트 완료.`);
}

function notifyCustomer(orderEvent) {
    console.log(`NotificationService: 사용자 ${orderEvent.userId}에게 주문 ${orderEvent.orderId} 접수 알림 전송 (이벤트 발생 시각: ${orderEvent.timestamp})`);
    // 고객 알림 로직...
    console.log(`NotificationService: 사용자 ${orderEvent.userId}에게 알림 전송 완료.`);
}

// 4. 시스템 설정 및 실행
// 컨슈머들이 이벤트 구독
orderBroker.on('orderPlaced', processPayment);
orderBroker.on('orderPlaced', updateInventory);
orderBroker.on('orderPlaced', notifyCustomer);

// OrderService가 이벤트를 발행
const orderService = new OrderService(orderBroker);
orderService.placeOrder('ORD-001', 'user123', 50000);
orderService.placeOrder('ORD-002', 'user456', 25000);

// 다른 이벤트 타입 발행 (구독자가 없는 경우)
orderBroker.emit('productAddedToCart', { productId: 'P001', userId: 'user123' });

코드 설명:

  • OrderEventEmitter: EventEmitter를 상속받아 이벤트 브로커 역할을 합니다.
  • OrderService: 주문을 접수하고 orderPlaced 이벤트를 발행합니다.
  • processPayment, updateInventory, notifyCustomer: 각각 orderPlaced 이벤트를 구독하여 결제, 재고 업데이트, 고객 알림을 처리합니다.

두 예제 모두 발행자와 구독자가 서로의 존재를 모르고 오직 이벤트 브로커를 통해서만 통신하는 EDA의 핵심 원리를 보여줍니다.

4. 실무 적용 사례

EDA는 현대의 다양한 IT 시스템에서 활발하게 활용되고 있습니다.

  • 전자상거래 플랫폼:
    • 주문 처리: 고객이 주문하면 OrderPlaced 이벤트가 발행됩니다. 이벤트를 구독하는 서비스들은 결제, 재고 감소, 배송 준비, 고객 알림, 마케팅 캠페인 트리거 등의 작업을 비동기적으로 동시에 수행합니다.
    • 사용자 활동 추적: ProductViewed, ItemAddedToCart 등의 이벤트를 발행하여 사용자 행동 데이터를 수집하고, 이를 기반으로 개인화 추천, 광고, 분석 대시보드를 구축합니다.
  • IoT (사물 인터넷) 시스템:
    • 수많은 센서 디바이스에서 발생하는 온도, 습도, 위치 정보 등의 데이터를 이벤트로 발행합니다. 이 이벤트들을 구독하는 서비스들은 데이터 저장, 이상 감지, 알림 발송, 대시보드 업데이트 등을 실시간으로 처리합니다.
  • 데이터 파이프라인 및 스트리밍 분석:
    • 다양한 소스(DB 변경 로그, 웹 서버 로그 등)에서 발생하는 데이터를 이벤트 스트림으로 통합합니다. 이를 Apache Kafka와 같은 이벤트 브로커에 저장하고, 실시간 분석 엔진, 데이터 웨어하우스 적재 시스템 등이 이벤트를 구독하여 데이터를 처리합니다.
  • 마이크로서비스 간 통신:
    • 서비스 간의 직접적인 API 호출 대신 이벤트를 통해 통신하여 서비스 간의 의존성을 최소화합니다. 예를 들어, UserManagement 서비스에서 UserUpdated 이벤트를 발행하면, ProfileService, NotificationService 등이 이를 구독하여 각자의 데이터를 갱신합니다.
  • 뱅킹 및 금융 서비스:
    • 거래 발생, 계좌 이체, 결제 승인 등의 이벤트를 실시간으로 처리하여 사기 탐지, 규제 준수, 고객 알림 등을 신속하게 수행합니다.

5. 자주 하는 실수와 해결법

EDA는 많은 장점을 제공하지만, 잘못 설계하면 오히려 복잡성을 증가시킬 수 있습니다.

  1. 이벤트 정의의 모호성 또는 잘못된 이벤트 사용:
    • 실수: 이벤트를 '일어난 일'이 아니라 '해야 할 일'로 정의하는 경우 (예: ProcessOrder 대신 OrderProcessed). 또는 이벤트에 너무 많은 정보가 포함되거나 너무 적은 정보가 포함되는 경우.
    • 해결법: 이벤트는 항상 과거 시제로, 시스템의 상태 변화를 나타내는 불변의 사실이어야 합니다. 특정 액션을 명령하는 대신, 발생한 사실만을 전달해야 합니다. 이벤트는 관련 컨슈머가 작업을 수행하기에 충분한 최소한의 정보를 포함해야 합니다.
  2. 과도한 이벤트 분리 또는 너무 적은 이벤트 분리:
    • 실수: 모든 작은 상태 변화를 이벤트로 발행하여 시스템이 너무 복잡해지거나, 반대로 너무 큰 이벤트를 발행하여 서비스 간의 결합도가 높아지는 경우.
    • 해결법: 비즈니스 도메인의 중요 경계에 맞춰 이벤트를 정의합니다. 특정 비즈니스 프로세스의 중요한 단계들을 이벤트로 정의하는 것이 좋습니다. 도메인 주도 설계(DDD)의 개념을 활용하여 이벤트의 granularity를 결정할 수 있습니다.
  3. 이벤트 스키마 관리의 부재:
    • 실수: 이벤트의 구조(스키마)가 변경되었을 때, 이를 인식하지 못하는 컨슈머들이 오류를 발생시키는 경우.
    • 해결법: 이벤트 스키마를 명확히 정의하고 버전 관리합니다. Avro, Protobuf 같은 스키마 정의 언어를 사용하고, 스키마 레지스트리를 통해 관리하면 좋습니다. 하위 호환성을 유지하는 방식으로 스키마를 발전시키는 전략이 중요합니다.
  4. 이벤트 순서 보장 문제:
    • 실수: 특정 이벤트의 처리 순서가 중요한데, 브로커가 순서를 보장하지 않아 데이터 불일치가 발생하는 경우. (예: 사용자 계좌 잔액 변경 이벤트를 잘못된 순서로 처리)
    • 해결법: 대부분의 이벤트 브로커는 파티션(Partition) 내에서는 순서를 보장합니다. 따라서 순서가 중요한 이벤트들은 동일한 파티션으로 라우팅되도록 설계해야 합니다 (예: 사용자 ID 기반으로 파티셔닝). 컨슈머 그룹 내에서 단일 컨슈머 인스턴스가 특정 파티션을 전담하도록 하거나, 컨슈머 자체에서 멱등성(Idempotency)을 고려하여 여러 번 처리되어도 문제가 없도록 설계해야 합니다. (멱등성에 대한 자세한 내용은 기존 게시글 "멱등성(Idempotency): 분산 시스템의 신뢰성을 지키는 견고한 방패"를 참고해주세요.)
  5. 에러 처리 및 재시도 전략 부재:
    • 실수: 컨슈머가 이벤트를 처리하다가 오류가 발생했을 때, 해당 이벤트를 놓치거나 무한 루프에 빠지는 경우.
    • 해결법: 컨슈머는 이벤트 처리 실패 시 재시도 메커니즘을 갖춰야 합니다 (지수 백오프 전략 등). 특정 횟수 이상 재시도해도 실패하는 이벤트는 데드 레터 큐(Dead Letter Queue, DLQ)로 보내 별도로 분석하고 처리할 수 있도록 해야 합니다.

6. 더 공부할 리소스 추천

이벤트 기반 아키텍처는 깊이 있는 개념이므로, 꾸준한 학습이 필요합니다.

  • 서적:
    • "Designing Event-Driven Systems" by Ben Stopford: Apache Kafka를 중심으로 이벤트 기반 시스템 설계에 대한 심도 있는 내용을 다룹니다.
    • "Kafka The Definitive Guide" by Gwen Shapira et al.: 이벤트 스트리밍 플랫폼인 Kafka를 통해 EDA의 실제 구현을 이해하는 데 도움이 됩니다.
  • 온라인 아티클 및 블로그:
    • Martin Fowler의 블로그: "Event-Driven Architecture" 검색 시, EDA의 개념과 다양한 패턴에 대한 명확한 설명을 찾을 수 있습니다.
    • Confluent 블로그: Kafka 개발사에서 운영하는 블로그로, 이벤트 스트리밍 및 EDA 관련 최신 트렌드와 기술 자료가 풍부합니다.
  • 주요 이벤트 브로커 공식 문서:
    • Apache Kafka: 대용량 실시간 이벤트 스트리밍 처리에 특화된 오픈소스 플랫폼.
    • RabbitMQ: 다양한 메시징 패턴을 지원하는 범용 메시지 브로커.
    • AWS SQS (Simple Queue Service) & SNS (Simple Notification Service): 클라우드 기반의 메시지 큐 및 발행-구독 서비스.
    • Google Cloud Pub/Sub: 구글 클라우드의 확장 가능한 실시간 메시징 서비스.

이벤트 기반 아키텍처는 단순히 기술 스택을 넘어선 사고방식의 변화를 요구합니다. 이벤트 중심으로 시스템을 바라보고 설계하는 연습을 통해, 더욱 유연하고 견고한 시스템을 구축하는 데 기여할 수 있을 것입니다.