¿Cómo se combinan CQRS y event sourcing en un sistema orientado a eventos real?
CQRS separa las escrituras de las lecturas en modelos distintos, y event sourcing convierte la fuente de verdad del modelo de escritura en un log de eventos en vez del estado actual — juntos permiten reconstruir cualquier vista del pasado y escalar las lecturas de forma independiente.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo se combinan CQRS y event sourcing en un sistema orientado a eventos real?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.CQRS y event sourcing suelen mencionarse juntos casi como si fueran lo mismo, pero responden preguntas distintas, y entender dónde está la línea entre ambos es justo lo que separa una definición memorizada de un dominio real del patrón.
#CQRS: modelos separados para escribir y para leer
Command Query Responsibility Segregation (segregación de responsabilidad de comandos y consultas) dice: deja de usar un mismo modelo tanto para aceptar mutaciones como para servir lecturas. Un comando (PlaceOrder, CancelOrder) expresa una intención, lo maneja un modelo de escritura que aplica las reglas de negocio, y típicamente no devuelve más que un acuse de recibo o un id — nunca una vista completa de los datos. Una consulta (GetOrderSummary, GetOrdersForCustomer) nunca muta nada y la sirve uno o varios modelos de lectura — formas de datos optimizadas puramente para cómo los consume realmente una UI u otro servicio, que pueden estar desnormalizadas, pre-unidas (pre-joined), cacheadas o indexadas de la forma que sea más rápida para esa lectura en particular.
Esta separación ya es valiosa por sí sola, con o sin event sourcing: las cargas de trabajo de escritura y lectura suelen tener necesidades de escalado muy distintas (pocas escrituras, complejas y con mucha validación, frente a muchas lecturas simples y sensibles a la latencia), y forzar ambas a través de un único modelo relacional normalizado es una causa habitual tanto de consultas lentas como de lógica de escritura excesivamente defensiva.
#Event sourcing: el log es la fuente de verdad
Event sourcing cambia lo que persiste un modelo de escritura. En vez de guardar el estado actual y sobrescribirlo en el lugar —una fila de orders cuya columna status se actualiza de pending a shipped—, persistes cada transición de estado como un evento inmutable: OrderPlaced, OrderShipped, OrderCancelled, cada uno con una marca de tiempo y un payload. El estado actual nunca se guarda directamente; se deriva cargando los eventos de una entidad, en orden, y "plegándolos" (fold) en un objeto en memoria. El log de eventos es la autoridad: si un modelo de lectura o una caché alguna vez difiere de él, gana el log, y ese modelo de lectura simplemente puede borrarse y reconstruirse a partir del log.
#Por qué combinan tan naturalmente
Event sourcing ya produce exactamente lo que CQRS necesita del lado de lectura: un flujo durable y ordenado de hechos sobre todo lo que ocurrió. Una vez que cada escritura emite un evento, construir un nuevo modelo de lectura es simplemente escribir un nuevo suscriptor que proyecta esos mismos eventos en una forma conveniente para una consulta — obtienes diversidad de modelos de lectura prácticamente gratis, sin tocar nunca el camino de escritura.
Recorriendo el flujo: un comando PlaceOrder carga el aggregate de la orden reproduciendo sus eventos anteriores (o un snapshot más los eventos posteriores a él), lo cual valida que la orden pueda colocarse legítimamente y, si es así, produce un nuevo evento OrderPlaced. Ese evento se añade al event store — típicamente con una verificación de concurrencia optimista contra la versión esperada del aggregate, de modo que dos comandos concurrentes sobre el mismo aggregate no puedan sobrescribirse silenciosamente los efectos entre sí. Una vez añadido de forma durable, el evento se publica; cada proyección suscrita a él actualiza su propio almacén de forma independiente y a su propio ritmo.
#Qué ganas
- Un historial de auditoría completo, gratis. Cada cambio de estado ya queda registrado con marca de tiempo y contexto completo — sin una tabla de audit-log separada que mantener sincronizada.
- Consultas temporales. Puedes responder "cómo se veía esta orden el martes pasado a las 3pm" reproduciendo los eventos solo hasta ese punto.
- Modelos de lectura retroactivos. ¿Te das cuenta seis meses después de que necesitas un reporte o índice de búsqueda nuevo? Escribe el proyector y reproduce todo el log de eventos en él — sin script de backfill, sin migración de datos, porque los eventos ya contienen todo lo que alguna vez ocurrió.
- Proyecciones recuperables. Si un proyector tiene un bug, lo arreglas y reproduces de nuevo; el modelo de lectura es desechable y reconstruible, a diferencia de una tabla que es la única copia de la verdad.
- Escalado independiente. El throughput de escritura (añadir eventos) y el de lectura (servir consultas desde almacenes construidos a propósito) escalan en ejes completamente distintos.
#Qué cuesta
- Consistencia eventual del lado de lectura. Un cliente que acaba de emitir un comando puede consultar un modelo de lectura que todavía no se puso al día. Los sistemas manejan esto con un estado de UI de "tu acción se está procesando", haciendo que el comando devuelva un número de versión/secuencia que el cliente pueda sondear, o enrutando la lectura inmediata de ese mismo cliente hacia el lado de escritura.
- Evolución del esquema de eventos. Los eventos viven para siempre, así que renombrar un campo o añadir uno nuevo obligatorio necesita un upcaster que transforme las formas antiguas del evento a la actual en el momento de leerlo — no puedes simplemente migrar una columna en el lugar.
- El snapshotting se vuelve necesario. Un aggregate con cientos de miles de eventos sería prohibitivamente lento de reconstruir por reproducción completa cada vez; los sistemas en producción persisten periódicamente un snapshot del estado actual más la versión en la que se tomó, y solo reproducen los eventos posteriores a ese punto.
- Sin consultas ad-hoc contra el lado de escritura. No puedes ejecutar una cláusula
WHEREarbitraria contra un log de eventos como lo harías contra una tabla relacional; cada patrón de consulta necesita su propia proyección, decidida de antemano. - Más piezas en movimiento operativamente. Un event store, un bus y N almacenes de proyección son más cosas que correr, monitorear y razonar que una sola base de datos.