CQRS y Event Sourcing en Java: Arquitectura que no teme a la complejidad

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Cuando CRUD deja de ser suficiente

\n\n

Imagine que está escribiendo un sistema para el comercio bursátil. Cada segundo: miles de órdenes. Cada orden no es solo "insertar un registro". Es verificación de saldo, recálculo de cartera, auditoría, notificaciones. Ahora imagine que necesita no solo registrar el estado actual, sino también reconstruir lo que sucedió ayer a las 14:35:22.003. El CRUD (Crear, Leer, Actualizar, Eliminar) habitual se rompe aquí. Bienvenido al mundo de CQRS y Event Sourcing en Java.

\n\n

Estos dos patrones no son solo palabras de moda. Son herramientas que permiten construir sistemas con alto rendimiento, auditoría completa y capacidad de "rebobinar el tiempo". En este artículo, analizaremos cómo implementar CQRS (Command Query Responsibility Segregation) y Event Sourcing en Java, utilizando frameworks modernos y código limpio. Sin rodeos, con ejemplos.

\n\n

¿Qué es CQRS en términos simples?

\n\n

CQRS es la separación de responsabilidades entre comandos (escritura) y consultas (lectura). En el CRUD clásico, se utiliza el mismo modelo para leer y escribir. En CQRS, se crean dos modelos diferentes: uno para modificar datos (comandos) y otro para recuperarlos (consultas).

\n\n

¿Por qué es necesario?

\n
    \n
  • Rendimiento: La lectura y la escritura se escalan de forma independiente. Puede tener 10 réplicas para lectura y 2 para escritura.
  • \n
  • Seguridad: Los comandos pueden verificar reglas de negocio, mientras que las consultas simplemente entregan datos.
  • \n
  • Flexibilidad: Para la lectura, se pueden usar vistas desnormalizadas; para la escritura, normalizadas.
  • \n
\n\n

Ejemplo de código: Separación de comandos y consultas

\n\n

Supongamos que tenemos un sistema de gestión de pedidos. En lugar de hacer OrderService.save(order), separamos:

\n\n
// Comando: es una intención de cambiar el estado\npublic class CreateOrderCommand {\n    private final String orderId;\n    private final String customerId;\n    private final List<OrderItem> items;\n\n    // constructor, getters...\n}\n\n// Manejador del comando\n@Component\npublic class CreateOrderCommandHandler {\n    @Autowired\n    private OrderRepository orderRepository;\n\n    public void handle(CreateOrderCommand command) {\n        // Validación de reglas de negocio\n        if (command.getItems().isEmpty()) {\n            throw new IllegalArgumentException("El pedido debe tener al menos un artículo");\n        }\n        Order order = new Order(command.getOrderId(), command.getCustomerId(), command.getItems());\n        orderRepository.save(order);\n    }\n}\n\n// Consulta: es la obtención de datos\npublic class GetOrderQuery {\n    private final String orderId;\n    // constructor, getters...\n}\n\n// Manejador de la consulta\n@Component\npublic class GetOrderQueryHandler {\n    @Autowired\n    private OrderReadRepository orderReadRepository;\n\n    public OrderDTO handle(GetOrderQuery query) {\n        // Lectura directa desde una BD optimizada\n        return orderReadRepository.findById(query.getOrderId());\n    }\n}
\n\n

Observe: OrderRepository (escritura) y OrderReadRepository (lectura) son interfaces diferentes. Pueden apuntar a diferentes bases de datos o diferentes esquemas.

\n\n

Event Sourcing: Almacenamos no el estado, sino la historia

\n\n

Event Sourcing es un patrón en el que guardamos no el estado actual de un objeto, sino la secuencia de eventos que llevaron a ese estado. En lugar de "Pedido nº123: estado = ENTREGADO", almacenamos: "Evento 1: Pedido creado", "Evento 2: Artículo añadido", "Evento 3: Pedido pagado", "Evento 4: Pedido entregado".

\n\n

Cómo funciona en Java

\n\n

Cada evento es un objeto inmutable. Los guardamos en un Event Store (BD especializada). El estado actual del agregado se calcula reproduciendo (replaying) todos los eventos.

\n\n
// Evento base\npublic interface Do                

Blogs

Book Recommendations