이벤트 드리븐
Event-Driven Architecture
이벤트 드리븐 아키텍처(EDA)는 이벤트의 생성, 감지, 소비를 중심으로 서비스가 느슨하게 결합되는 패턴입니다. Apache Kafka, RabbitMQ, AWS EventBridge 등을 통해 비동기 통신과 확장성을 달성합니다.
Event-Driven Architecture
이벤트 드리븐 아키텍처(EDA)는 이벤트의 생성, 감지, 소비를 중심으로 서비스가 느슨하게 결합되는 패턴입니다. Apache Kafka, RabbitMQ, AWS EventBridge 등을 통해 비동기 통신과 확장성을 달성합니다.
이벤트 드리븐 아키텍처(EDA)는 시스템 컴포넌트가 이벤트를 통해 비동기적으로 통신하는 소프트웨어 설계 패턴입니다. 동기 API 호출(Request-Response) 대신 이벤트 발행-구독(Pub/Sub) 모델을 사용하여 서비스 간 느슨한 결합을 달성합니다.
// 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;
}
}
// 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();
// 이벤트 발행
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;
};
}
"주문 완료 시 알림, 재고 차감, 포인트 적립이 다 연동되어야 하는데, 한 곳이 느리면 전체가 느려지지 않나요?"
"이벤트 드리븐으로 비동기 처리하면 됩니다. 주문 서비스는 OrderPlaced 이벤트만 발행하고 즉시 응답해요. 알림, 재고, 포인트 서비스가 각자 이벤트를 구독해서 독립적으로 처리합니다."
"이벤트 처리 중에 실패하면 어떻게 되나요? 주문은 됐는데 포인트가 안 쌓이면요?"
"Dead Letter Queue(DLQ)와 재시도 정책을 설정해요. 실패한 이벤트는 DLQ로 가고, 나중에 재처리하거나 알림을 받아요. 최종적 일관성(Eventual Consistency)을 받아들이는 거죠."
최종적 일관성: EDA는 즉각적 일관성을 보장하지 않습니다. 주문 후 재고가 바로 차감되지 않을 수 있어요. 비즈니스 요구사항이 강한 일관성을 요구한다면 동기 호출을 고려하세요.
이벤트 순서: 이벤트 도착 순서가 발행 순서와 다를 수 있습니다. 순서가 중요하다면 Kafka 파티션 키나 순서 보장 메커니즘을 사용하세요.
팁: 이벤트 스키마를 버전 관리하세요. v1, v2처럼 버전을 명시하면 Consumer가 점진적으로 마이그레이션할 수 있습니다.