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.
Related Posts
Domina los Hooks de Claude Code: Formateo automático, tests automáticos y más
Aprende a configurar formateo y tests automáticos con los hooks de Claude Code. Incluye ejemplos prácticos de configuración y casos de uso reales.
Configuración de servidores MCP en Claude Code y casos de uso prácticos
Guía completa sobre las capacidades de servidores MCP en Claude Code. Aprende a conectar herramientas externas, configurar servidores y explora ejemplos de integración reales.
Guía completa para escribir CLAUDE.md: Mejores prácticas de configuración de proyectos
Una guía exhaustiva para escribir archivos CLAUDE.md efectivos. Aprende a comunicar tu stack tecnológico, convenciones y estructura de proyecto para maximizar la calidad de las respuestas de Claude Code.