Advanced

Diseno e implementacion de arquitectura orientada a eventos con Claude Code

Aprenda sobre el diseno e implementacion de arquitectura orientada a eventos usando Claude Code. Incluye consejos practicos y ejemplos de codigo.

Que es la arquitectura orientada a eventos

La arquitectura orientada a eventos (EDA) es un patron que disena sistemas de forma desacoplada mediante la publicacion y suscripcion de eventos. Con Claude Code, puede avanzar eficientemente desde el diseno hasta la implementacion de EDA.

Event bus con seguridad de tipos

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

    // Devuelve funcion de desuscripcion
    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 fallaron para ${event}:`,
        failures
      );
    }
  }
}

const eventBus = new EventBus();

Registro de event handlers

// Procesamiento al crear un usuario
eventBus.on("user.created", async ({ userId, email }) => {
  // Enviar correo de bienvenida
  await emailQueue.add("send", {
    to: email,
    subject: "Bienvenido!",
    template: "welcome",
    data: { userId },
  });
});

eventBus.on("user.created", async ({ userId }) => {
  // Crear configuracion por defecto
  await prisma.userSettings.create({
    data: {
      userId,
      theme: "light",
      language: "es",
      notifications: true,
    },
  });
});

// Procesamiento al completar un pedido
eventBus.on("order.completed", async ({ orderId }) => {
  const order = await prisma.order.findUnique({
    where: { id: orderId },
    include: { user: true, items: true },
  });

  if (!order) return;

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

Patron de eventos de dominio

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

Patron CQRS

// Lado del Command (escritura)
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> {
    // Validacion
    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(`Stock insuficiente: ${item.productId}`);
      }
    }

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

    // Emitir evento
    await eventBus.emit("order.created", {
      orderId: order.id,
      userId: this.userId,
      total: 0,
    });

    return order.id;
  }
}

// Lado del Query (lectura)
interface Query<T> {
  execute(): Promise<T>;
}

class GetOrdersQuery implements Query<OrderSummary[]> {
  constructor(
    private userId: string,
    private page: number = 1
  ) {}

  async execute(): Promise<OrderSummary[]> {
    // Obtener de la vista de solo lectura
    return prisma.orderView.findMany({
      where: { userId: this.userId },
      orderBy: { createdAt: "desc" },
      take: 20,
      skip: (this.page - 1) * 20,
    });
  }
}

Event Store

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
  ) {
    // Bloqueo optimista
    const currentVersion = await this.getVersion(aggregateId);
    if (currentVersion !== expectedVersion) {
      throw new Error("Conflicto de concurrencia");
    }

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

    // Emitir eventos
    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;
  }
}

Uso con Claude Code

Ejemplo de como solicitar a Claude Code la implementacion de arquitectura orientada a eventos. Para procesamiento asincrono, consulte Colas de trabajo y procesamiento asincrono. Para integracion externa, consulte Patrones de implementacion de Webhooks.

Introduce arquitectura orientada a eventos.
- Implementacion de event bus con seguridad de tipos
- Patron de eventos de dominio
- CQRS: separacion de comandos y consultas
- Implementacion de event store
- Refactorizar la capa de servicios existente e integrar

Para mas detalles sobre diseno orientado a eventos, consulte Martin Fowler - Event-Driven Architecture. Para el uso de Claude Code, consulte la documentacion oficial.

Resumen

La arquitectura orientada a eventos mejora el desacoplamiento y la escalabilidad del sistema. Con Claude Code, puede introducir EDA gradualmente, desde un event bus con seguridad de tipos hasta CQRS y event sourcing.

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