Designing and Implementing Event-Driven Architecture: Claude Code 활용 가이드
designing and implementing event-driven architecture: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.
이벤트駆動아키텍처とは
이벤트駆動아키텍처(EDA)は、이벤트の発行と購読에 의해システムを疎結合に설계するパターンです。Claude Code를 활용하면 EDAの설계から구현まで효율적으로進められます。
타입安全な이벤트バス
type EventMap = {
"user.created": { userId: string; email: string };
"user.updated": { userId: string; changes: Record<string, unknown> };
"order.created": { orderId: string; userId: string; total: number };
"order.completed": { orderId: string; completedAt: Date };
"order.cancelled": { orderId: string; reason: string };
"payment.processed": { paymentId: string; amount: number };
};
type EventName = keyof EventMap;
type EventHandler<T extends EventName> = (payload: EventMap[T]) => Promise<void>;
class EventBus {
private handlers = new Map<string, Set<Function>>();
on<T extends EventName>(event: T, handler: EventHandler<T>) {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
// unsubscribe함수を返す
return () => {
this.handlers.get(event)?.delete(handler);
};
}
async emit<T extends EventName>(event: T, payload: EventMap[T]) {
const handlers = this.handlers.get(event);
if (!handlers) return;
const results = await Promise.allSettled(
Array.from(handlers).map((handler) => handler(payload))
);
const failures = results.filter((r) => r.status === "rejected");
if (failures.length > 0) {
console.error(
`${failures.length} handlers failed for ${event}:`,
failures
);
}
}
}
const eventBus = new EventBus();
이벤트핸들러の등록
// 사용자생성時の処理
eventBus.on("user.created", async ({ userId, email }) => {
// ウェルカムメール전송
await emailQueue.add("send", {
to: email,
subject: "ようこそ!",
template: "welcome",
data: { userId },
});
});
eventBus.on("user.created", async ({ userId }) => {
// デフォルト설정の생성
await prisma.userSettings.create({
data: {
userId,
theme: "light",
language: "ja",
notifications: true,
},
});
});
// 注文완료時の処理
eventBus.on("order.completed", async ({ orderId }) => {
const order = await prisma.order.findUnique({
where: { id: orderId },
include: { user: true, items: true },
});
if (!order) return;
// 在庫の업데이트
for (const item of order.items) {
await prisma.product.update({
where: { id: item.productId },
data: { stock: { decrement: item.quantity } },
});
}
});
ドメイン이벤트パターン
abstract class DomainEvent {
readonly occurredAt: Date;
readonly eventId: string;
constructor() {
this.occurredAt = new Date();
this.eventId = crypto.randomUUID();
}
}
class OrderCreatedEvent extends DomainEvent {
constructor(
public readonly orderId: string,
public readonly userId: string,
public readonly items: Array<{ productId: string; quantity: number }>,
public readonly total: number
) {
super();
}
}
// Aggregate Root
class Order {
private domainEvents: DomainEvent[] = [];
static create(params: {
userId: string;
items: Array<{ productId: string; quantity: number; price: number }>;
}): Order {
const order = new Order();
const orderId = crypto.randomUUID();
const total = params.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
order.domainEvents.push(
new OrderCreatedEvent(orderId, params.userId, params.items, total)
);
return order;
}
pullDomainEvents(): DomainEvent[] {
const events = [...this.domainEvents];
this.domainEvents = [];
return events;
}
}
CQRS パターン
// Command側(쓰기)
interface Command<T = void> {
execute(): Promise<T>;
}
class CreateOrderCommand implements Command<string> {
constructor(
private userId: string,
private items: Array<{ productId: string; quantity: number }>
) {}
async execute(): Promise<string> {
// 유효성 검사
for (const item of this.items) {
const product = await prisma.product.findUnique({
where: { id: item.productId },
});
if (!product || product.stock < item.quantity) {
throw new Error(`Insufficient stock: ${item.productId}`);
}
}
// 注文생성
const order = await prisma.order.create({
data: {
userId: this.userId,
status: "pending",
items: {
create: this.items.map((item) => ({
productId: item.productId,
quantity: item.quantity,
})),
},
},
});
// 이벤트発行
await eventBus.emit("order.created", {
orderId: order.id,
userId: this.userId,
total: 0,
});
return order.id;
}
}
// Query側(読み取り)
interface Query<T> {
execute(): Promise<T>;
}
class GetOrdersQuery implements Query<OrderSummary[]> {
constructor(
private userId: string,
private page: number = 1
) {}
async execute(): Promise<OrderSummary[]> {
// 読み取り専用のビューから취득
return prisma.orderView.findMany({
where: { userId: this.userId },
orderBy: { createdAt: "desc" },
take: 20,
skip: (this.page - 1) * 20,
});
}
}
이벤트ストア
interface StoredEvent {
id: string;
aggregateId: string;
aggregateType: string;
eventType: string;
payload: Record<string, unknown>;
version: number;
occurredAt: Date;
}
class EventStore {
async append(
aggregateId: string,
aggregateType: string,
events: DomainEvent[],
expectedVersion: number
) {
// 楽観的ロック
const currentVersion = await this.getVersion(aggregateId);
if (currentVersion !== expectedVersion) {
throw new Error("Concurrency conflict");
}
const storedEvents = events.map((event, i) => ({
id: event.eventId,
aggregateId,
aggregateType,
eventType: event.constructor.name,
payload: event as any,
version: expectedVersion + i + 1,
occurredAt: event.occurredAt,
}));
await prisma.event.createMany({ data: storedEvents });
// 이벤트を発行
for (const event of events) {
await eventBus.emit(
event.constructor.name as any,
event as any
);
}
}
async getEvents(aggregateId: string): Promise<StoredEvent[]> {
return prisma.event.findMany({
where: { aggregateId },
orderBy: { version: "asc" },
});
}
private async getVersion(aggregateId: string): Promise<number> {
const last = await prisma.event.findFirst({
where: { aggregateId },
orderBy: { version: "desc" },
});
return last?.version ?? 0;
}
}
Claude Code로の활용
이벤트駆動아키텍처の구현をClaude Code에依頼する例です。비동기処理에 대해서는잡큐・비동기処理、外部연동はWebhook구현パターン도 참고하세요.
イベント駆動アーキテクチャを導入して。
- 型安全なイベントバスの実装
- ドメインイベントパターン
- CQRS: コマンドとクエリの分離
- イベントストアの実装
- 既存のサービス層をリファクタリングして統合
이벤트駆動설계의 상세 정보는Martin Fowler - Event-Driven Architecture를 참고하세요.Claude Codeの使い方は공식 문서에서 확인할 수 있습니다.
정리
이벤트駆動아키텍처はシステムの疎結合性と확장성を高めます。Claude Code를 활용하면 타입安全な이벤트バスからCQRS、이벤트ソーシングまで、段階的にEDAを도입할 수 있습니다。
Claude Code 워크플로우를 한 단계 업그레이드하세요
지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code Agent SDK 입문 ― 자율 에이전트를 빠르게 구축하는 방법
Claude Code Agent SDK로 자율형 AI 에이전트를 구축하는 방법을 해설합니다. 설정부터 도구 정의, 멀티스텝 실행까지 실전 코드와 함께 소개합니다.
Claude Code 컨텍스트 관리 테크닉 완전 가이드
Claude Code의 컨텍스트 윈도우를 최대한 활용하는 실전 테크닉을 해설합니다. 토큰 절약, 대화 분할, CLAUDE.md 활용법까지 소개합니다.
Claude Code MCP Server 설정 및 실전 활용 가이드
Claude Code의 MCP Server 기능을 종합적으로 소개합니다. 외부 도구 연결, 서버 설정, 실전 통합 사례까지 한 번에 알아보세요.