Marcio Cunha

Modelado de Dominios Complejos con Event Sourcing y Proyecciones Read-Side Optimizadas

Aprenda a construir sistemas resilientes y escalables combinando Event Sourcing y proyecciones Read-Side optimizadas para gestionar reglas de negocio complejas en la práctica.

Marcio Cunha•5 min
También disponible en:PortuguêsEnglish
Resumen
  • El almacenamiento inmutable de eventos preserva cada cambio de estado sin perder historial analítico o de auditoría.
  • La separación estricta entre escritura y lectura elimina los cuellos de botella tradicionales de concurrencia en bases de datos relacionales.
  • Las proyecciones adaptadas aceleran consultas complejas al precomputar vistas listas para el consumo en la interfaz de usuario.
  • La consistencia eventual exige que los equipos diseñen flujos tolerantes a pequeñas ventanas de latencia en la sincronización de datos.
  • La complejidad operacional inicial se compensa sobradamente con la ganancia en trazabilidad y flexibilidad evolutiva a largo plazo.

Comprendiendo el Problema de los Modelos de Datos Tradicionales

En la ingeniería de software convencional, solemos guardar el estado actual de un registro sobrescribiendo datos en tablas relacionales. En la práctica, esto significa que cuando un usuario cambia su dirección de envío, la dirección anterior desaparece para siempre. Este modelo simplificado funciona bien en aplicaciones sencillas, pero falla estrepitosamente cuando necesitamos auditar el pasado, entender el comportamiento del usuario a lo largo del tiempo o explicar por qué se tomó una decisión financiera en un segundo específico. El costo de esta pérdida de contexto histórico crece exponencialmente a medida que el negocio evoluciona y las reglas de cumplimiento se vuelven más estrictas.

Para resolver este dilema de pérdida de información, debemos cambiar nuestra perspectiva fundamental sobre el tiempo y la persistencia. En lugar de registrar solo la instantánea estática del momento, pasamos a registrar cada hecho ocurrido como una verdad inalterable. Este concepto, conocido como persistencia basada en eventos, convierte la base de datos en un diario de a bordo cronológico e inmutable. En la práctica, la aplicación deja de preguntar cuál es el estado actual de la tabla y pasa a sumar o interpretar la secuencia de acontecimientos que condujeron hasta allí, garantizando trazabilidad total de cada transacción de negocio.

El Concepto y Funcionamiento de Event Sourcing

Event Sourcing es un patrón arquitectónico donde el estado de una aplicación está determinado por una secuencia inmutable de eventos de negocio. En la práctica, cada acción importante realizada por un usuario o sistema —como 'CarritoCreado', 'PagoAprobado' o 'PedidoEnviado'— genera un objeto de evento estructurado que se almacena de forma append-only, es decir, sin permitir ediciones ni eliminaciones. Este enfoque garantiza que el sistema posea una pista de auditoría nativa e incansable, ideal para dominios complejos donde la historia importa tanto como el presente.

Cuando necesitamos calcular el estado actual de un objeto de dominio, como el saldo de una cuenta bancaria o los ítems de un pedido, el sistema lee todos los eventos pasados de ese agregado y los aplica secuencialmente en memoria. En la práctica, esto significa que el estado siempre se deriva y nunca se almacena directamente como una fila estática en una tabla común. Para evitar cuellos de botella de rendimiento cuando la lista de eventos se vuelve muy larga, utilizamos el concepto de snapshots, que consisten en guardar puntos de control periódicos del estado acumulado para acelerar la inicialización del agregado.

{
  "eventId": "evt_987654321",
  "aggregateId": "order_123",
  "eventType": "OrderPlaced",
  "timestamp": "2026-03-31T10:00:00Z",
  "data": {
    "customerId": "cust_456",
    "items": [
      {"productId": "prod_789", "quantity": 2, "price": 49.90}
    ]
  }
}

Desafíos de Consulta y la Necesidad de Proyecciones Read-Side

Uno de los mayores desafíos prácticos de Event Sourcing es responder a consultas complejas o paginaciones rápidas. Si necesitamos buscar todos los pedidos de un cliente cuyos totales superen cierto valor en un período específico, recalcular esto a partir del flujo bruto de eventos sería computacionalmente inviable en tiempo de ejecución. En la práctica, leer miles de eventos para cada petición HTTP destruiría el rendimiento de la aplicación y saturaría la infraestructura de red y memoria.

Es exactamente aquí donde entran las proyecciones Read-Side, que funcionan como oyentes dedicados a traducir el flujo continuo de eventos en tablas o documentos optimizados exclusivamente para lectura. En la práctica, cada vez que se dispara un evento como 'OrderPlaced', un proyector asíncrono consume este evento y actualiza una base de datos relacional o NoSQL desnormalizada, diseñada a medida para las pantallas de la aplicación. De esta forma, la escritura prioriza la seguridad y la integridad temporal, mientras que la lectura entrega respuestas instantáneas sin esfuerzo computacional redundante.

Implementación Práctica de un Proyector Asíncrono

Construir un proyector eficiente exige entender cómo escuchar el bus de eventos y actualizar la base de lectura de forma idempotente, garantizando que el mismo mensaje procesado dos veces no corrompa el estado final. En la práctica, utilizamos identificadores únicos de eventos o versiones de control para ignorar mensajes duplicados que puedan llegar debido a fallos de red y retransmisiones automáticas. Este cuidado operacional blinda al sistema contra inconsistencias visuales en la interfaz de usuario.

El código a continuación ilustra la lógica fundamental de un proyector en Node.js que escucha eventos de pedidos y actualiza un repositorio optimizado para consultas rápidas en la capa de lectura:

async function handleOrderPlacedEvent(event) {
  const existingRecord = await readRepository.findById(event.aggregateId);
  if (existingRecord) return; // Garantiza idempotencia

  const totalAmount = event.data.items.reduce((sum, item) => sum + (item.quantity * item.price), 0);

  const readModel = {
    id: event.aggregateId,
    customerId: event.data.customerId,
    total: totalAmount,
    status: 'PLACED',
    createdAt: event.timestamp
  };

  await readRepository.save(readModel);
}

Consistencia Eventual y Trade-offs Arquitectónicos

Al desacoplar la escritura (Event Sourcing) de la lectura (Read-Side Projections), adoptamos el modelo de consistencia eventual. En la práctica, esto significa que, inmediatamente después de que el usuario confirme una compra, la pantalla de historial puede tardar una fracción de segundo en mostrar el nuevo pedido, ya que el evento aún necesita ser procesado por el proyector asíncrono. Para la mayoría de las aplicaciones corporativas y de comercio electrónico, esta ventana imperceptible es un precio irrelevante frente a las enormes ganancias de escalabilidad y resiliencia obtenidas.

Otro trade-off importante radica en la complejidad del versionado de esquemas de eventos a lo largo de los años. Como los eventos son inmutables, si la estructura de datos de un evento antiguo necesita cambiar debido a exigencias regulatorias o de negocio, debemos implementar estrategias de upcasting o traductores en tiempo de lectura. Evaluar cuidadosamente estos costos de mantenimiento antes de adoptar la arquitectura evita costosos retrabajos en equipos de ingeniería en etapas avanzadas del producto.

Consideraciones Finales

La adopción combinada de Event Sourcing y proyecciones Read-Side optimizadas representa un salto cualitativo significativo en la ingeniería de dominios complejos. Aunque exige un cambio profundo de mentalidad en el modelado de datos y trae desafíos operacionales inherentes a la distribución asíncrona, los beneficios en términos de auditoría, resiliencia y rendimiento de lectura justifican ampliamente el esfuerzo. Dominar estos patrones capacita a arquitectos y desarrolladores a construir sistemas capaces de crecer de forma sostenible y previsible.

El éxito en el viaje hacia arquitecturas orientadas a eventos depende menos de modas tecnológicas y más de un alineamiento riguroso entre las necesidades reales del negocio y las garantías de consistencia técnica elegidas. Al planificar cuidadosamente cada proyector y mantener el modelo de dominio aislado de preocupaciones de infraestructura, su equipo garantizará un software robusto, flexible y preparado para los retos futuros.