🏗️ 아키텍처

이벤트 드리븐

Event-Driven Architecture

이벤트 드리븐 아키텍처(EDA)는 이벤트의 생성, 감지, 소비를 중심으로 서비스가 느슨하게 결합되는 패턴입니다. Apache Kafka, RabbitMQ, AWS EventBridge 등을 통해 비동기 통신과 확장성을 달성합니다.

📖 상세 설명

이벤트 드리븐 아키텍처(EDA)는 시스템 컴포넌트가 이벤트를 통해 비동기적으로 통신하는 소프트웨어 설계 패턴입니다. 동기 API 호출(Request-Response) 대신 이벤트 발행-구독(Pub/Sub) 모델을 사용하여 서비스 간 느슨한 결합을 달성합니다.

Producer
이벤트 발행
Event Broker
Kafka/RabbitMQ
Consumer
이벤트 소비

핵심 개념

EDA의 장점

💻 코드 예제

Kafka Producer (Node.js)

// services/order/OrderService.ts
import { Kafka } from 'kafkajs';

const kafka = new Kafka({ brokers: ['localhost:9092'] });
const producer = kafka.producer();

export class OrderService {
  async placeOrder(orderData: CreateOrderDTO) {
    // 1. 주문 생성 (도메인 로직)
    const order = Order.create(orderData);
    await this.orderRepository.save(order);

    // 2. 이벤트 발행 (비동기)
    await producer.send({
      topic: 'order-events',
      messages: [{
        key: order.id,
        value: JSON.stringify({
          type: 'OrderPlaced',
          data: {
            orderId: order.id,
            customerId: order.customerId,
            items: order.items,
            totalAmount: order.totalAmount,
          },
          occurredAt: new Date().toISOString(),
        }),
      }],
    });

    return order;
  }
}

Kafka Consumer

// services/notification/NotificationConsumer.ts
import { Kafka } from 'kafkajs';

const kafka = new Kafka({ brokers: ['localhost:9092'] });
const consumer = kafka.consumer({ groupId: 'notification-service' });

async function startConsumer() {
  await consumer.connect();
  await consumer.subscribe({ topic: 'order-events', fromBeginning: false });

  await consumer.run({
    eachMessage: async ({ topic, partition, message }) => {
      const event = JSON.parse(message.value!.toString());

      switch (event.type) {
        case 'OrderPlaced':
          await sendOrderConfirmationEmail(event.data);
          break;
        case 'OrderShipped':
          await sendShippingNotification(event.data);
          break;
      }
    },
  });
}

startConsumer();

AWS EventBridge + Lambda

// 이벤트 발행
import { EventBridge } from '@aws-sdk/client-eventbridge';

const eventBridge = new EventBridge({});

export async function publishOrderEvent(order: Order) {
  await eventBridge.putEvents({
    Entries: [{
      Source: 'com.myapp.orders',
      DetailType: 'OrderPlaced',
      Detail: JSON.stringify({
        orderId: order.id,
        customerId: order.customerId,
        totalAmount: order.totalAmount,
      }),
      EventBusName: 'default',
    }],
  });
}

// Lambda Consumer (이벤트 핸들러)
export const handler = async (event: any) => {
  const orderEvent = event.detail;

  // 재고 차감 처리
  await inventoryService.decreaseStock(orderEvent.items);

  return { statusCode: 200 };
};

이벤트 스키마 정의

// shared/events/OrderEvents.ts
export interface DomainEvent {
  eventId: string;
  eventType: string;
  aggregateId: string;
  occurredAt: string;
  version: number;
}

export interface OrderPlaced extends DomainEvent {
  eventType: 'OrderPlaced';
  data: {
    orderId: string;
    customerId: string;
    items: Array<{
      productId: string;
      quantity: number;
      unitPrice: number;
    }>;
    totalAmount: number;
    shippingAddress: string;
  };
}

export interface OrderCancelled extends DomainEvent {
  eventType: 'OrderCancelled';
  data: {
    orderId: string;
    reason: string;
    cancelledBy: string;
  };
}

💬 현업 대화 예시

👨‍💼 PM

"주문 완료 시 알림, 재고 차감, 포인트 적립이 다 연동되어야 하는데, 한 곳이 느리면 전체가 느려지지 않나요?"

👩‍💻 백엔드 개발자

"이벤트 드리븐으로 비동기 처리하면 됩니다. 주문 서비스는 OrderPlaced 이벤트만 발행하고 즉시 응답해요. 알림, 재고, 포인트 서비스가 각자 이벤트를 구독해서 독립적으로 처리합니다."

👨‍💻 주니어 개발자

"이벤트 처리 중에 실패하면 어떻게 되나요? 주문은 됐는데 포인트가 안 쌓이면요?"

👩‍💻 시니어 개발자

"Dead Letter Queue(DLQ)와 재시도 정책을 설정해요. 실패한 이벤트는 DLQ로 가고, 나중에 재처리하거나 알림을 받아요. 최종적 일관성(Eventual Consistency)을 받아들이는 거죠."

⚠️ 주의사항

최종적 일관성: EDA는 즉각적 일관성을 보장하지 않습니다. 주문 후 재고가 바로 차감되지 않을 수 있어요. 비즈니스 요구사항이 강한 일관성을 요구한다면 동기 호출을 고려하세요.

이벤트 순서: 이벤트 도착 순서가 발행 순서와 다를 수 있습니다. 순서가 중요하다면 Kafka 파티션 키나 순서 보장 메커니즘을 사용하세요.

팁: 이벤트 스키마를 버전 관리하세요. v1, v2처럼 버전을 명시하면 Consumer가 점진적으로 마이그레이션할 수 있습니다.

🔗 관련 용어

📚 더 배우기