Command Palette

Search for a command to run...

ES·EN

Nivel 3 · 30 min

CQRS

CQRS (Command Query Responsibility Segregation) separa el modelo de escritura (commands) del modelo de lectura (queries). Esta separación permite optimizar cada lado de forma independiente y es el complemento natural de Event Sourcing.

Separación Command/Query

En CQRS, los Commands mutan el estado y no retornan datos (excepto confirmación). Las Queries leen el estado y no lo mutan. El modelo de escritura puede ser normalizado y enfocado en invariantes; el modelo de lectura puede ser desnormalizado y optimizado para las queries específicas de la UI.

Proyecciones y Modelos de Lectura

Las proyecciones son vistas materializadas del estado que se actualizan en respuesta a eventos. Una proyección puede ser una tabla SQL desnormalizada, un documento en MongoDB, o un índice en Elasticsearch. Diferentes vistas para diferentes casos de uso: detalle de orden, lista paginada, dashboard de analytics.

Consistencia Eventual

En CQRS con event sourcing, las proyecciones se actualizan de forma eventual —hay un lag entre el momento en que un command se procesa y el momento en que la query ve el estado actualizado. Los clientes deben diseñarse para tolerar esto (mostrar ''procesando'', usar polling o websockets). La UI puede actualizar optimistamente el estado local.

Puntos clave

  • CQRS permite optimizar lectura y escritura de forma independiente —el modelo de lectura puede ser radicalmente diferente al de escritura.
  • Las proyecciones son el arma de performance —precalculá y materializá exactamente lo que la UI necesita.
  • La consistencia eventual requiere diseño consciente de la UX —la aplicación debe comunicar el estado de procesamiento al usuario.

Code example

// Command Handler
@CommandHandler
public void handle(PlaceOrderCommand cmd) {
  Order order = new Order(cmd.orderId(), cmd.customerId(), cmd.items());
  eventStore.save(order.domainEvents());
  eventBus.publish(order.domainEvents());
}

// Projection: actualiza el read model
@EventHandler
public void on(OrderPlaced event) {
  orderSummaryRepo.save(new OrderSummary(
    event.orderId(), event.customerId(),
    event.totalAmount(), "PENDING"
  ));
}

// Query Handler
@QueryHandler
public List<OrderSummary> handle(GetUserOrdersQuery query) {
  return orderSummaryRepo.findByCustomerId(query.customerId());
}