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
मुफ़्त

मुफ़्त PDF: 5 मिनट में Claude Code चीटशीट

बस अपना ईमेल दर्ज करें और हम तुरंत A4 एक-पृष्ठ चीटशीट PDF भेज देंगे।

हम आपकी व्यक्तिगत जानकारी की सुरक्षा करते हैं और स्पैम नहीं भेजते।

Masa

लेखक के बारे में

Masa

Claude Code का गहराई से उपयोग करने वाले इंजीनियर। claudecode-lab.com चलाते हैं, जो 10 भाषाओं में 2,000 से अधिक पेजों वाला टेक मीडिया है।