TechLead
Leccion 27 de 30
5 min de lectura
Diseño de Sistemas

Arquitectura Dirigida por Eventos

Aprende patrones de arquitectura dirigida por eventos incluyendo event sourcing, CQRS, buses de eventos y cuándo usar diseño dirigido por eventos en sistemas distribuidos.

Arquitectura Dirigida por Eventos

La arquitectura dirigida por eventos (EDA) es un paradigma de diseño donde el flujo del programa está determinado por eventos. En lugar de que los servicios se llamen directamente entre sí (solicitud-respuesta), los servicios producen y consumen eventos asincrónicamente. Este desacoplamiento permite escalado independiente, mejor aislamiento de fallos y evolución del sistema más flexible.

¿Qué Es un Evento?

Un evento es un registro inmutable de algo que sucedió en el sistema. A diferencia de un comando (que le dice a un sistema qué hacer), un evento declara lo que ya ocurrió.

Eventos vs. Comandos vs. Consultas

Concepto Intención Dirección Ejemplo
Evento Notificación de algo que sucedió Uno a muchos (broadcast) OrderPlaced, UserRegistered
Comando Solicitud para realizar una acción Uno a uno (dirigido) PlaceOrder, RegisterUser
Consulta Solicitud de información Uno a uno (dirigido) GetOrderStatus, GetUser
// Event structure
interface DomainEvent {
  eventId: string;
  eventType: string;        // e.g., "OrderPlaced"
  aggregateId: string;
  aggregateType: string;    // e.g., "Order"
  timestamp: Date;
  version: number;
  payload: Record<string, unknown>;
  metadata: {
    correlationId: string;
    causationId: string;
    userId?: string;
  };
}

const orderPlacedEvent: DomainEvent = {
  eventId: "evt_abc123",
  eventType: "OrderPlaced",
  aggregateId: "order_456",
  aggregateType: "Order",
  timestamp: new Date(),
  version: 1,
  payload: {
    customerId: "cust_789",
    items: [
      { productId: "prod_001", quantity: 2, price: 29.99 },
    ],
    totalAmount: 59.98,
  },
  metadata: {
    correlationId: "corr_xyz",
    causationId: "cmd_place_order_111",
    userId: "cust_789",
  },
};

Event Sourcing

Event sourcing es un patrón donde el estado de una entidad se deriva de una secuencia de eventos en lugar de almacenarse como un snapshot. En lugar de actualizar una fila en una base de datos, agregas un evento a un event store. El estado actual se reconstruye reproduciendo todos los eventos de esa entidad.

// Event-sourced Order aggregate
class Order {
  private id: string = "";
  private status: string = "";
  private items: OrderItem[] = [];
  private totalAmount: number = 0;

  static fromEvents(events: DomainEvent[]): Order {
    const order = new Order();
    for (const event of events) {
      order.apply(event);
    }
    return order;
  }

  private apply(event: DomainEvent): void {
    switch (event.eventType) {
      case "OrderCreated":
        this.id = event.aggregateId;
        this.status = "CREATED";
        this.items = event.payload.items as OrderItem[];
        this.totalAmount = event.payload.totalAmount as number;
        break;
      case "OrderPaid":
        this.status = "PAID";
        break;
      case "OrderShipped":
        this.status = "SHIPPED";
        break;
      case "OrderCancelled":
        this.status = "CANCELLED";
        break;
    }
  }
}

// Event Store interface
interface EventStore {
  append(aggregateId: string, events: DomainEvent[], expectedVersion: number): Promise<void>;
  getEvents(aggregateId: string, fromVersion?: number): Promise<DomainEvent[]>;
  getEventsByType(eventType: string, fromTimestamp?: Date): Promise<DomainEvent[]>;
}

Beneficios del Event Sourcing

  • Pista de auditoría completa: Cada cambio se registra, habilitando reconstrucción completa del historial
  • Consultas temporales: Responde preguntas como "¿cuál era el estado en el tiempo T?"
  • Depuración: Reproduce eventos para reproducir cualquier estado histórico
  • Flexibilidad: Crea nuevos modelos de lectura reproduciendo eventos a través de nuevas proyecciones
  • Reproducción de eventos: Corrige errores y reconstruye estado reproduciendo handlers de eventos corregidos

CQRS (Command Query Responsibility Segregation)

CQRS separa las operaciones de lectura y escritura en modelos diferentes. El modelo de escritura maneja comandos y produce eventos. El modelo de lectura está optimizado para consultas y se construye consumiendo eventos.

// Write side: handles commands, emits events
class OrderCommandHandler {
  async handle(command: PlaceOrderCommand): Promise<void> {
    const customer = await customerRepo.findById(command.customerId);
    if (!customer.isActive) throw new Error("Customer inactive");

    const event: DomainEvent = {
      eventId: generateId(),
      eventType: "OrderPlaced",
      aggregateId: generateOrderId(),
      aggregateType: "Order",
      timestamp: new Date(),
      version: 1,
      payload: {
        customerId: command.customerId,
        items: command.items,
        totalAmount: command.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
      },
      metadata: { correlationId: command.correlationId, causationId: command.commandId },
    };

    await eventStore.append(event.aggregateId, [event], 0);
    await eventBus.publish(event);
  }
}

// Read side: consumes events, builds query-optimized views
class OrderReadModelProjection {
  async handleEvent(event: DomainEvent): Promise<void> {
    switch (event.eventType) {
      case "OrderPlaced":
        await readDb.orders.insert({
          orderId: event.aggregateId,
          customerId: event.payload.customerId,
          status: "PLACED",
          totalAmount: event.payload.totalAmount,
          itemCount: (event.payload.items as any[]).length,
          createdAt: event.timestamp,
        });
        break;
      case "OrderShipped":
        await readDb.orders.update(
          { orderId: event.aggregateId },
          { status: "SHIPPED", shippedAt: event.timestamp }
        );
        break;
    }
  }
}

Event Bus y Event Store

El event bus (o message broker) es la columna vertebral de un sistema dirigido por eventos. Recibe eventos de productores y los entrega a consumidores.

Tecnología Tipo Mejor Para
Apache Kafka Log distribuido / streaming de eventos Alto rendimiento, reproducción de eventos, procesamiento de streams
RabbitMQ Message broker Enrutamiento complejo, colas de tareas, menor rendimiento
AWS SNS + SQS Pub/sub gestionado + cola Serverless, patrones fan-out, apps nativas de AWS
Redis Streams Stream en memoria Baja latencia, casos de uso más simples
EventStoreDB Event store de propósito específico Event sourcing con proyecciones integradas

Beneficios y Desafíos

Beneficios Desafíos
Acoplamiento débil entre servicios Consistencia eventual (no siempre aceptable)
Escalado independiente de productores y consumidores Depuración es más difícil (rastrear eventos entre servicios)
Ajuste natural para flujos de trabajo asíncronos Garantías de orden de eventos son complejas
Fácil agregar nuevos consumidores sin cambiar productores Evolución de esquemas (cambiar formatos de eventos con el tiempo)
Mejor aislamiento de fallos Idempotencia requerida (eventos pueden entregarse más de una vez)

Cuándo Usar y Cuándo No

Usa Arquitectura Dirigida por Eventos Cuando:

  • Múltiples servicios necesitan reaccionar al mismo evento de negocio
  • Necesitas acoplamiento débil entre contextos delimitados
  • Los flujos de trabajo son naturalmente asíncronos (procesamiento de pedidos, notificaciones)
  • Necesitas una pista de auditoría completa de todos los cambios
  • Quieres escalar independientemente las cargas de lectura y escritura (CQRS)

Evita Arquitectura Dirigida por Eventos Cuando:

  • Necesitas respuestas síncronas, fuertemente consistentes (ej. "¿está disponible este nombre de usuario?")
  • El sistema es lo suficientemente simple para llamadas directas servicio a servicio
  • Tu equipo carece de experiencia con depuración de sistemas distribuidos
  • El dominio es inherentemente solicitud-respuesta (aplicaciones CRUD)
  • Los requisitos de latencia son extremadamente ajustados y cada milisegundo cuenta

Continuar Aprendiendo