Día 25 de 30 Fase 6: Mensajería Asíncrona con Kafka, RabbitMQ, EDA y CQRS

CQRS y Event Sourcing: Modelos de Lectura y Estado como Historia

Separar comandos y consultas, construir proyecciones desde eventos, guardar el estado como secuencia de eventos y saber cuándo no merece la pena

Visión de conjunto. Sales entendiendo cómo funciona y cuándo usarlo; dominarlo pide más práctica en un proyecto real.

  • 4h de estudio
  • 5 conceptos
  • 3 preguntas
  • 2 katas
CQRSEvent SourcingProyeccionesEvent StoreSnapshots
Al terminar este día sabrás 4
  • Separar el modelo de escritura y el de lectura con CQRS cuando aporta valor.
  • Construir proyecciones que se puedan borrar y reconstruir desde los eventos.
  • Reconstruir un agregado a partir de sus eventos y guardarlos con concurrencia optimista.
  • Evolucionar eventos ya guardados con versionado y snapshots, y saber cuándo no usar estos patrones.

CQRS: Command Query Responsibility Segregation

CQRS separa el modelo de datos en dos lados: el lado de Comando (escritura) optimizado para validar y persistir cambios de estado, y el lado de Consulta (lectura) optimizado para devolver datos desnormalizados y rápidos de leer. Los comandos modifican el estado y publican eventos; las consultas leen vistas materializadas actualizadas por esos eventos.

En una línea CommandHandler -> eventStore.save(evento) -> EventListener -> readModelRepository.update(vistaConsulta)

Comando, consulta y proyección actualizada por eventos java
// Lado de escritura: comando con validación de invariantes
public record AsignarTarea(long tareaId, String responsable) {}

@Service
class ComandosTarea {
    @Transactional
    public void manejar(AsignarTarea cmd) {
        var tarea = repo.findById(cmd.tareaId()).orElseThrow();
        tarea.asignarA(cmd.responsable());                    // reglas del dominio
        eventos.publishEvent(new TareaAsignada(cmd.tareaId(), cmd.responsable()));
    }
}

// Lado de lectura: tabla desnormalizada pensada para el panel
public record ResumenResponsable(String responsable, int pendientes, int completadasSemana) {}

@Component
class ProyeccionPanel {
    private final JdbcClient jdbc;
    ProyeccionPanel(JdbcClient jdbc) { this.jdbc = jdbc; }

    @TransactionalEventListener
    void on(TareaAsignada e) {
        jdbc.sql("""
            INSERT INTO panel_responsable (responsable, pendientes) VALUES (:r, 1)
            ON CONFLICT (responsable) DO UPDATE SET pendientes = panel_responsable.pendientes + 1
            """).param("r", e.responsable()).update();
    }

    public List<ResumenResponsable> panel() {                // consulta trivial y rápida
        return jdbc.sql("SELECT * FROM panel_responsable ORDER BY pendientes DESC")
            .query(ResumenResponsable.class).list();
    }
}
Cómo funciona por dentro

CQRS separa las operaciones que cambian el estado (comandos) de las que lo leen (consultas), usando modelos distintos para cada lado. El modelo de escritura se centra en validar reglas de negocio e invariantes; el de lectura, en servir consultas rápidas con la forma exacta que necesita cada pantalla o API, a menudo desnormalizado.

No implica obligatoriamente dos bases de datos. El nivel más sencillo es el mismo almacenamiento con dos modelos en el código (entidades ricas para escribir, proyecciones o vistas SQL para leer). El nivel avanzado usa almacenes separados (PostgreSQL para escribir, Elasticsearch o una tabla desnormalizada para leer) sincronizados mediante eventos, con consistencia eventual.

Tiene sentido cuando lecturas y escrituras son muy asimétricas en volumen o forma: un panel que agrega datos de muchas tablas, búsquedas de texto completo o lecturas que se cuentan por miles frente a pocas escrituras.

Permite optimizar escrituras y lecturas de forma independiente: la base de datos de escritura puede ser relacional normalizada y la de lectura puede ser un Elasticsearch, Redis o vista desnormalizada en PostgreSQL.

Se conecta con Event SourcingVistas MaterializadasEscalabilidad Asimétrica

Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.

  1. Bloque 1: CQRS y Proyecciones (0 - 25 min)

    Qué estudiar. Separación entre comandos y consultas, y proyecciones alimentadas por eventos.

    Pregunta a responder. ¿Qué consulta de tu proyecto se beneficiaría de un modelo de lectura propio?

    Resultado esperado. Diagrama del flujo comando → evento → proyección → consulta para el panel de pedidos.

  2. Bloque 2: Event Sourcing (25 - 50 min)

    Qué estudiar. Agregado reconstruido a partir de eventos y método aplicar con switch exhaustivo.

    Pregunta a responder. ¿Qué información pierdes si solo guardas el estado actual?

    Resultado esperado. Agregado Pedido que se reconstruye desde una lista de eventos, con tests.

  3. Bloque 3: Event Store, Concurrencia y Versionado (50 - 80 min)

    Qué estudiar. Versión esperada al añadir eventos, snapshots y upcasting.

    Pregunta a responder. ¿Qué pasa si dos comandos añaden eventos al mismo agregado a la vez?

    Resultado esperado. Tabla de eventos con clave (agregado_id, version) y un test que provoca el conflicto.

  4. Cierre de Consolidación (80 - 90 min)

    Qué estudiar. Repaso de la rúbrica del tutor.

    Pregunta a responder. Explicar cuándo NO usarías CQRS ni Event Sourcing.

    Resultado esperado. Checklist de decisión para CQRS y Event Sourcing en tus notas.

Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.

Explícalo desde cero

Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.

Event Sourcing explicado con un extracto bancario

Lo que debes entender

El banco no guarda solo tu saldo: guarda cada movimiento, y el saldo se calcula sumándolos. Event Sourcing hace lo mismo con cualquier entidad: el estado actual es el resultado de aplicar todos sus eventos en orden.

Analogía para contarlo

Si el banco detecta un cargo erróneo, no borra la línea del extracto: añade una devolución. Así se corrige también un evento.

Lo que suele explicarse mal

Decir que Event Sourcing es «guardar un log de auditoría». En un log, el estado vive en otra tabla y el log es un añadido; en Event Sourcing, los eventos son la única fuente de verdad.

Señal de que aún no lo dominas

Si tu solución para un error en los datos es editar un evento ya guardado.

Preguntas trampa

Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.

Nivel 1 (Básico) ¿Qué se gana al separar el modelo de escritura y el de lectura en CQRS? Ver respuesta

Respuesta razonada

Porque escribir y leer tienen necesidades opuestas. El lado de escritura protege invariantes: valida, aplica reglas y guarda en un modelo normalizado pensado para que los datos no se contradigan. El lado de lectura quiere devolver rápido exactamente lo que pide una pantalla, y eso suele significar datos duplicados y desnormalizados.

Al separarlos, cada lado se diseña, se optimiza y se escala por su cuenta: puedes tener muchas réplicas de lectura, varias vistas para usos distintos y un modelo de escritura pequeño y estricto. El coste es la consistencia eventual entre ambos y más piezas que mantener, así que solo compensa cuando lecturas y escrituras divergen de verdad.

Qué está evaluando la pregunta

Cada lado se optimiza para lo suyo: el de escritura valida reglas y protege invariantes con un modelo normalizado; el de lectura sirve consultas rápidas con vistas desnormalizadas a medida de cada pantalla, y puede escalar por separado.

Nivel 2 (Intermedio) ¿Cuándo NO se debe aplicar CQRS? Ver respuesta

Respuesta razonada

Cuando el dominio es un CRUD sencillo donde leer y escribir tienen la misma forma, cuando el sistema es pequeño, cuando el equipo no tiene experiencia con consistencia eventual o cuando no hay una asimetría real entre lecturas y escrituras. En esos casos CQRS duplica modelos, mapeos y código de sincronización sin beneficio.

Si además separas almacenes, introduces consistencia eventual: el usuario guarda algo y no lo ve en la lista inmediatamente, con la complejidad de interfaz que eso conlleva. CQRS se aplica por partes, solo en el contexto del sistema donde hace falta, nunca como arquitectura por defecto de toda la aplicación.

Qué está evaluando la pregunta

CQRS añade complejidad de sincronización entre modelos de escritura y lectura, introduce consistencia eventual, y duplica la lógica de persistencia. En un CRUD simple con pocas lecturas/escrituras simétricas, la complejidad añadida no se justifica.

Nivel 3 (Entrevista Senior) Tienes millones de eventos PedidoCreado guardados y ahora el evento necesita un campo moneda obligatorio. ¿Cómo lo cambias? Ver respuesta

Respuesta razonada

No se tocan los eventos guardados: son la historia, y las proyecciones dependen de ellos. Se define una nueva versión del evento (PedidoCreado v2 con moneda), los eventos nuevos se escriben ya en v2 y, al leer, un upcaster convierte los v1 en v2 con un valor por defecto acordado con negocio (por ejemplo, EUR si la tienda solo vendía en euros).

El dominio solo maneja la versión actual, y si alguna proyección necesita el dato nuevo para los eventos antiguos, se reconstruye. Si el valor no se puede deducir de forma segura, se publican eventos de corrección para los casos afectados en lugar de inventar el dato.

Qué está evaluando la pregunta

Sin tocar los eventos guardados: se crea una versión 2 del evento, los nuevos se escriben en v2 y al leer los antiguos un upcaster los convierte con un valor por defecto acordado. Si alguna proyección necesita el dato, se reconstruye.

Reto del tutor

Un arquitecto propone implementar Event Sourcing para el módulo de gestión de usuarios de la empresa (CRUD de altas, bajas y modificaciones de perfil). El equipo tiene 2 desarrolladores juniors. ¿Es una buena decisión? Argumenta como un CTO técnico.

Lo que una respuesta suele tener bien

Reconoce que Event Sourcing tiene valor en ciertos contextos.

Lo que suele quedarse en la superficie

Decir que "Event Sourcing es bueno porque es moderno".

El concepto de fondo que suele faltar

No evaluar el coste de complejidad frente al beneficio real. La gestión de usuarios es un CRUD puro con pocas escrituras, sin necesidad de audit trail complejo ni reproducción de estados.

Cómo lo respondería alguien senior en una entrevista

No es una decisión acertada para ese módulo: (1) La gestión de usuarios es un CRUD simple con pocas escrituras concurrentes y sin necesidad de reconstruir estados históricos; (2) Event Sourcing añade complejidad significativa (event store, snapshots, proyecciones, consistencia eventual) que un equipo de 2 juniors no dominará y generará bugs difíciles de diagnosticar; (3) El audit trail se puede lograr con una simple tabla de auditoría (Hibernate Envers o triggers de BD) con una fracción de la complejidad. Event Sourcing brilla en dominios con alta concurrencia, reglas de negocio complejas con muchas transiciones de estado (carritos de compra, reservas de vuelo, transacciones financieras) y necesidad real de reproducción temporal del estado.

Proyecto «Pedidos» · paso 25 de 30

Panel de la tienda con CQRS

Separar las lecturas del panel de las escrituras de pedidos con un modelo de lectura propio.

De qué va el proyecto

El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.

Qué hacer

  1. Crea la tabla resumen_pedidos (por día y por cliente) que solo actualiza una proyección a partir de los eventos.
  2. Haz que la proyección recuerde el último evento procesado y que reprocesar sea seguro.
  3. Añade un comando de administración que borre la proyección y la reconstruya desde los eventos.

Está listo cuando

  • El panel lee solo de resumen_pedidos, nunca de las tablas de escritura.
  • Reconstruir la proyección desde cero da exactamente los mismos números que la actualización incremental.

Katas: ejercicios cortos

Reglas de oro

  • No apliques CQRS ni Event Sourcing a CRUDs simples; reserva estos patrones para dominios con alta complejidad de estado.
  • Los eventos guardados no se editan: los errores se corrigen con un evento compensatorio.
  • Las proyecciones se pueden borrar y reconstruir desde los eventos: diseña para poder hacerlo.
  • Al guardar eventos, comprueba siempre la versión esperada del agregado.

Java 17 frente a Java 21

CaracterísticaJava 17 LTSJava 21 LTS
Aplicar eventos al agregado instanceof en cadena o patrón visitante switch exhaustivo con record patterns
Se guarda automáticamente

Fuentes oficiales

Revisado el . Este día en Markdown