Software Architecture
CQRS y Event Sourcing
Explica CQRS y Event Sourcing, sus diferencias, beneficios, costes, proyecciones, consistencia eventual, reconstrucción y requisitos de operación.
- Última actualización
- Actualizada
- Nivel
- Profundización
Software Architecture
Explica CQRS y Event Sourcing, sus diferencias, beneficios, costes, proyecciones, consistencia eventual, reconstrucción y requisitos de operación.
CQRS separa responsabilidades de escritura y lectura. Event Sourcing persiste hechos como fuente de verdad. Pueden combinarse, pero resuelven problemas distintos y añaden costos diferentes.
En un modelo CRUD, la misma representación suele servir para modificar y consultar. Esto funciona bien en muchos sistemas.
CQRS aparece cuando comandos y consultas tienen necesidades muy diferentes.
commands
→ validan intención e invariantes
→ modifican estado
queries
→ leen modelos optimizados
→ no modifican negocioEvent Sourcing cambia la persistencia:
eventos históricos
→ fold/replay
→ estado actualCommand Query Responsibility Segregation puede ser una separación lógica dentro del mismo proceso y base. No exige microservicios ni consistencia eventual.
Expresa una intención:
type ConfirmOrder = {
orderId: string;
actorId: string;
requestId: string;
};Puede rechazarse por reglas, autorización o conflicto.
Devuelve información sin cambiar estado:
type OrderSummary = {
id: string;
customerName: string;
total: string;
statusLabel: string;
};No necesita reconstruir el agregado si una proyección directa es suficiente.
Un modelo de escritura prioriza invariantes y comportamiento. Un modelo de lectura prioriza forma, filtros, joins y rendimiento.
Order aggregate
→ líneas, estado, reglas
OrderDashboardView
→ cliente, sucursal, método de pago, tiemposForzar ambos en una sola clase puede producir queries complejas o dominio contaminado por necesidades de UI.
Commands y queries usan handlers distintos, pero comparten DB.
Escritura normalizada y lectura desnormalizada.
Proyecciones se actualizan por eventos. Introduce consistencia eventual y operación adicional.
Empieza por el nivel mínimo que resuelve el problema.
ConfirmOrder command
→ carga agregado
→ valida transición
→ guarda escritura
→ publica evento
→ projection handler actualiza dashboard
→ query lee proyecciónLa UI puede observar un pequeño retraso. Debe saber si mostrar estado pendiente o leer directamente el resultado de escritura para read-your-writes.
En lugar de guardar solo el estado actual, persiste eventos:
OrderCreated
ItemAdded
ItemAdded
OrderConfirmedEl estado se reconstruye aplicándolos:
function evolve(state: OrderState, event: OrderEvent): OrderState {
switch (event.type) {
case "OrderCreated":
return { id: event.orderId, status: "draft", lines: [] };
case "ItemAdded":
return { ...state, lines: [...state.lines, event.line] };
case "OrderConfirmed":
return { ...state, status: "confirmed" };
}
}El evento persistido se convierte en contrato histórico.
Optimistic concurrency evita que dos writers agreguen sobre la misma versión sin detectar conflicto.
Permite saber cómo se llegó al estado, siempre que eventos representen hechos relevantes y estén protegidos.
Se pueden crear proyecciones nuevas mediante replay.
Es posible observar estado en un punto anterior.
Commands producen eventos explícitos que reflejan decisiones.
Cambiar su significado rompe historia. Necesitas versiones, upcasters o traducción.
Puede tardar y ejecutar efectos accidentales si no separas proyecciones de integraciones.
Datos históricos pueden entrar en conflicto con borrado y minimización. Puede necesitar cifrado por sujeto, tombstones o redacción controlada.
Snapshots, índices, proyecciones, lag y reparación aumentan componentes.
El estado no aparece como una fila simple; requiere entender secuencia y versiones.
Guardan una representación derivada para acelerar carga:
snapshot en versión 100
+ eventos 101–108
→ estado actualNo reemplazan eventos. Deben poder eliminarse y reconstruirse.
Opciones:
Transformar evento antiguo a forma nueva al leer.
Handlers entienden varias versiones.
No reescriben historia; añaden corrección.
Costosa y riesgosa; requiere preservar auditoría y compatibilidad.
No cambies el significado de un tipo existente silenciosamente.
Una proyección debe ser:
OrderConfirmed v8
→ si projectionVersion < 8, aplica
→ si ya es 8, ignora
→ si falta 7, detecta huecoPuedes usar tablas transaccionales y publicar eventos mediante outbox. Las lecturas se desnormalizan sin que eventos sean fuente de verdad.
Esta opción ofrece muchos beneficios con menor complejidad.
Teóricamente posible, pero las consultas sobre streams suelen ser ineficientes. En práctica se crean proyecciones, acercándose a CQRS.
Event Sourcing puede registrar:
StockReceived +20
StockReserved -3
ReservationReleased +1
StockAdjusted -2Aporta auditoría y reconstrucción. Pero el alto volumen, concurrencia y privacidad deben evaluarse. Una tabla de movimientos más estado actual puede ser suficiente sin hacer que eventos sean única fuente.
Define read-your-writes, estado pendiente y SLO de convergencia.
No lo borres sin política. Publica corrección o usa proceso auditado.
Los projectors no deben ejecutar efectos externos; separa integration handlers.
Snapshots, particionamiento y agregados pequeños.
El replay histórico usa hechos, pero ¿aplica reglas antiguas o nuevas? Depende del objetivo. El evento debe conservar suficiente contexto.
Contratos de API y versionado estudia cómo exponer capacidades estables a consumidores sin congelar internals.