Concevoir et implémenter une architecture événementielle avec Claude Code
Découvrez concevoir et implémenter une architecture événementielle avec Claude Code. Conseils pratiques et exemples de code inclus.
イベント駆動アーキテクチャとは
イベント駆動アーキテクチャ(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の使い方は公式ドキュメントで確認できます。
Summary
イベント駆動アーキテクチャはシステムの疎結合性とスケーラビリティを高めます。Claude Codeを使えば、型安全なイベントバスからCQRS、イベントソーシングまで、段階的にEDAを導入できます。
Related Posts
Maîtriser les Hooks de Claude Code : Formatage automatique, tests automatiques et plus encore
Apprenez à configurer le formatage et les tests automatiques avec les hooks de Claude Code. Inclut des exemples de configuration pratiques et des cas d'utilisation concrets.
Configuration des serveurs MCP dans Claude Code et cas d'utilisation pratiques
Un guide complet sur les capacités des serveurs MCP de Claude Code. Apprenez à connecter des outils externes, configurer des serveurs et découvrez des exemples d'intégration concrets.
Le guide complet pour rédiger un CLAUDE.md : Bonnes pratiques de configuration de projet
Un guide approfondi pour rédiger des fichiers CLAUDE.md efficaces. Apprenez à communiquer votre stack technologique, vos conventions et la structure de votre projet pour maximiser la qualité des réponses de Claude Code.