Advanced

Claude Code के साथ Designing and Implementing Event-Driven Architecture

Claude Code का उपयोग करके designing and implementing event-driven architecture सीखें। Practical tips और code examples शामिल हैं।

event駆動アーキテクチャ क्या है

event駆動アーキテクチャ(EDA)は、eventの発行と購読 सेシステムを疎結合に設計するpattern है।Claude Code का उपयोग करके、EDAの設計 सेimplementation तकefficiently進められ है।

型safeなeventバス

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);

    // unsubscribefunctionを返す
    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();

eventハンドラの登録

// usercreate時のprocessing
eventBus.on("user.created", async ({ userId, email }) => {
  // ウェルカムメール送信
  await emailQueue.add("send", {
    to: email,
    subject: " तरहこそ!",
    template: "welcome",
    data: { userId },
  });
});

eventBus.on("user.created", async ({ userId }) => {
  // デフォルトsettingsのcreate
  await prisma.userSettings.create({
    data: {
      userId,
      theme: "light",
      language: "ja",
      notifications: true,
    },
  });
});

// 注文完了時のprocessing
eventBus.on("order.completed", async ({ orderId }) => {
  const order = await prisma.order.findUnique({
    where: { id: orderId },
    include: { user: true, items: true },
  });

  if (!order) return;

  // 在庫のupdate
  for (const item of order.items) {
    await prisma.product.update({
      where: { id: item.productId },
      data: { stock: { decrement: item.quantity } },
    });
  }
});

domaineventpattern

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 pattern

// 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> {
    // validation
    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}`);
      }
    }

    // 注文create
    const order = await prisma.order.create({
      data: {
        userId: this.userId,
        status: "pending",
        items: {
          create: this.items.map((item) => ({
            productId: item.productId,
            quantity: item.quantity,
          })),
        },
      },
    });

    // event発行
    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[]> {
    // 読み取り専用のビュー सेfetch
    return prisma.orderView.findMany({
      where: { userId: this.userId },
      orderBy: { createdAt: "desc" },
      take: 20,
      skip: (this.page - 1) * 20,
    });
  }
}

eventストア

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 });

    // eventを発行
    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 सेのutilization

event駆動アーキテクチャのimplementationをClaude Code को requestする例 है।asyncprocessingके बारे मेंはjobqueue・asyncprocessing、बाहर部integrationはWebhookimplementationpatternもदेखें。

event駆動アーキテクチャをintroductionして。
- 型safeなeventバスのimplementation
- domaineventpattern
- CQRS: commandとqueryの分離
- eventストアのimplementation
- 既存のservice層をrefactoringしてintegration

event駆動設計के details के लिएMartin Fowler - Event-Driven Architectureをदेखें。Claude Codeのuse करने का तरीकाはofficial documentationでconfirmでき है।

Summary

event駆動アーキテクチャはシステムの疎結合性とスケーラビリティを高め है।Claude Code का उपयोग करके、型safeなeventバス सेCQRS、eventソーシング तक、段階的にEDAをintroductionでき है।

#Claude Code #event-driven #architecture #CQRS #design patterns