Command Query Responsibility Segregation | CQRS
Tags: eventualconsistency, cleanarchitecture, cleancode, eventsourcing
Published: 2023-09-28 13:23:49

---
title: Command Query Responsibility Segregation | CQRS
published: true
date: 2023-08-04 14:11:41 UTC
tags: eventualconsistency,cleanarchitecture,cleancode,eventsourcing
canonical_url: https://medium.com/all-you-need-is-clean-code/command-query-responsibility-segregation-cqrs-fdb4a58cb945
---

#### Prepara tu código para escalar hasta el infinito

El patrón CQRS fue descrito por Greg Young en 2010 como una forma de separar los modelos de escritura (_commands)_ y de lectura (_queries_) de nuestras aplicaciones, permitiendo de este modo un escalado asimétrico entre los mecanismos de lectura y escritura de nuestra aplicación.

La idea básica es que podemos utilizar un modelo para escribir, distinto al modelo que usamos para ejecutar lecturas, como contrapunto al habitual _CRUD (_ **_C_** _reate,_ **_R_** _ead,_ **_U_** _pdate,_ **_D_** _elete)._ Cabe decir que esta aproximación, la de CQRS, puede añadir una mayor complejidad que un sistema sencillo basado en _CRUD._

### **Implementación de CQRS**

#### Separación de los modelos

Podemos implementar CQRS de distintas maneras en función de nuestras necesidades.

Por ejemplo, podemos optar por mantener una única base de datos y realizar solo una separación a nivel de conexión, es decir, usaremos una conexión diferente para las operaciones de lectura _(queries)_ y las de escritura _(commands)_. Esto nos permiten configurar distintos parámetros como _timeouts_, tamaño de peticiones y otros parámetros más ajustados a la naturaleza de la operación.

![](https://cdn-images-1.medium.com/max/1024/1*qjvXpl9NV4x1nModu1ne8Q.png)
_CQRS con base de datos unificada_

Esta aproximación tiene el inconveniente de que la separación solo existe a un nivel lógico, es decir, seguimos atacando la misma base de datos, por lo que sigue siendo el mismo sistema el que asume la carga de trabajo.

Otra aproximación sería utilizar dos bases de datos separadas para escrituras y lecturas:

![](https://cdn-images-1.medium.com/max/1024/1*OJAXpdregMKLoWqKTUlQrw.png)
_CQRS con bases de datos separadas_

De este modo, la carga de las operaciones de lectura y escritura están más repartidas y no solo a un nivel lógico, sino que son dos sistemas diferentes los que manejan esta carga. El inconveniente de esta aproximación es que se genera una _consistencia eventual_, es decir, los modelos de lectura y escritura no serán consistentes en todo momento, sino que eventualmente existirán diferencias entre ellos, lo cual puede generar un problema en función de la naturaleza de los datos.

#### Implementación de los modelos

Al implementar las operaciones de lectura y escritura vamos a tener 3 nuevas parejas de actores:

- **_Query_** y **_QueryHandler._**   
Son, respectivamente, el modelo de lectura y su manejador. La _query_ no es más que un DTO con toda la información necesaria para realizar la lectura (parámetros, filtros, paginación…). Por su parte el _handler_ o manejador es el servicio encargado de obtener esa información y devolvérsela al controlador.   
Por norma general, las operaciones de lectura son siempre síncronas.

> La correspondencia de queries y handlers es 1:1, es decir, cada query tiene solo un handler, no puede ser manejado por más de uno.

- **_Command_** y **_CommandHandler._**   
Son respectivamente el modelo de escritura y su manejador. El _command_ no es más que un DTO con toda la información necesaria para realizar la escritura (identificador del registro(s) a editar, atributos a modificar…). Por su parte el _handler_ o manejador es el servicio encargado de persistir esa información.   
Las operaciones de escritura pueden ser síncronas o asíncronas, lo cual representa una ventaja en términos de eficiencia, ya que podemos simplemente lanzar la acción de actualización sin esperar a que ésta acabe: el _frontend_ ya tiene los datos actualizados, ya que es él quien nos los ha mandado.   
Debemos de tener, eso sí, mecanismos para reaccionar de cara a problemas de escritura de datos asíncronos (sistema de reintentos, notificaciones al frontend vía server _push_ o _sockets…_).

> _La correspondencia de_ commands _y_ handlers _es 1:1, es decir, cada_ command _tiene solo un_ handler_, no puede ser manejado por más de uno._

- **_Event_** y **_EventHandler._**   
En este caso son, respectivamente, el modelo de _algo que ha sucedido_ en nuestro sistema y su manejador. El _event_ no es más que un DTO con la información de lo que ha sucedido, normalmente se compone de una fecha o _timestamp_ de cuándo ha ocurrido, un nombre descriptivo, siempre en pasado, de lo sucedido (por ejemplo: _user\_deleted)_ y un cuerpo o _payload_ con toda la información relevante para manejar el evento. Este _payload_ puede componerse de datos necesarios para identificar el registro afectado, los atributos que se han modificado… etc.  
Al igual que los _commands_, los eventos pueden manejarse de forma síncrona o asíncrona, asegurándonos de tomar las mismas precauciones para gestionar posibles errores.

> Al contrario que en los casos anteriores, la correspondencia de _eventos_ y _handlers_ (o listeners) es 1:n, es decir, cada event tiene más de un handler o _listener_.

Un caso de uso muy común de los eventos es eliminar la consistencia eventual producida en sistemas con bases de datos separadas: tras realizarse una actualización, eliminación o inserción de datos en la base de datos de escritura, un evento avisa de los cambios para que estos se repliquen en la base de datos de lectura.

![](https://cdn-images-1.medium.com/max/1024/1*ppThCN7nsAHjmVMMLMJTGA.png)
_Actualización del modelo de lectura mediante eventos_

#### Manejo síncrono o asíncrono de los modelos

Para permitir el manejo síncrono o asíncrono de los modelos solemos hacer un uso de un sistema intermedio que se encarga de emparejar los _commands_, las _queries_ y los _events_ con sus respectivos _handlers._ Este sistema intermedio es lo que llamamos un _bus._ El bus es como una cola donde metemos los _commands, queries_ o _events_ y estos son ejecutados por sus _handlers_ asociados. Este _bus_ puede implementarse con sistemas como _Message Queues Systems (RabbitMQ)_ o sistemas como _Redis_ que permiten el encolado de tareas.

![](https://cdn-images-1.medium.com/max/1024/1*lkeVORD99EJ7uf3QTAOfIA.png)
_Flujo síncrono de un endpoint para obtener una entidad de base de datos_

La ventaja del uso de buses en forma de colas de mensajes es que añadimos una capa de [indirección](https://es.wikipedia.org/wiki/Indirecci%C3%B3n) entre la acción y la ejecución. Esto quiere decir que la tarea encolada puede ser consumida por nuestro propio sistema o por un sistema externo que se conecte al servicio de colas, permitiéndonos esto el tener sistemas externos encargados de manejar, por ejemplo, la consistencia eventual, o delegar tareas largas y pesadas a sistemas expertos en realizar estas mismas tareas, que puede que ni siquiera estén escritos en el mismo lenguaje que nuestro sistema principal.

![](https://cdn-images-1.medium.com/max/1024/1*jdgOP9xPifVMrlYZVH-1aA.png)
_Flujo asíncrono de un endpoint de actualización de un registro que incluye el disparo de un evento._

Veamos un ejemplo de como nuestro sistema podría manejar la consistencia eventual (sin el uso de agentes externos):

![](https://cdn-images-1.medium.com/max/1024/1*jrlVgqvE-VN250vxMHozzg.png)
_Manejo de la consistencia eventual con eventos en un sistema de buses._

En el ejemplo de arriba vemos como el _command handler_ dispara un evento al bus de eventos. Este evento puede ser la notificación de que se ha escrito un nuevo registro, o que se ha editado o eliminado uno que ya existía previamente. Puede representar más cosas, como que se ha suscrito a un servicio, que ha hecho _like_ a una foto… lo que sea. El tema es que este evento es capturado por el handler asociado y actúa en consonancia actualizando la base de datos de lectura, tratando de reducir al máximo el tiempo de consistencia eventual.

### Ventajas de CQRS

- **Escalado asimétrico.**   
En entornos de alta disponibilidad, donde se producen miles de interacciones por segundo y hay flujos con más tráfico que otros, el escalado asimétrico de nuestra plataforma es algo realmente deseable, ya que podemos centrar el esfuerzo económico en esa parte únicamente. En el caso de CQRS, el tener separados los dominios de escritura y los de lectura nos permite centrar nuestros esfuerzos económicos en el que requiera mayor inversión.  
¿Tenemos un proyecto tipo red social donde la gente se pasa más tiempo leyendo contenido que produciéndolo? Incrementamos las instancias de lectura, escalamos las instancias… lo que sea necesario, pero sólo centrado en las de lectura.  
¿Nuestro proyecto es como un API en la que sensores a lo largo de cientos de instalaciones escriben registros cada segundo o minuto? Nos centramos en escalar la escritura, ya que, seguramente, haya poca gente consultando el panel de lecturas si lo comparamos con el número de escrituras.
- **Separación de dominios más semántica.**   
Esta ventaja es más a nivel organizativo, pero el hecho de tener separados nuestros casos de uso entre lecturas y escrituras simplifica mucho el entendimiento del mismo. Por ejemplo, sabemos seguro que una lectura no va a tener _side effects_ o que podemos ejecutarla tantas veces como queramos sin repercusiones. Luego, la separación de los _side effects_ que puedan producir las escrituras, al manejarse con eventos nos permite centrar el caso de uso en su propósito único, y delegar estos _side effects_ a otras porciones de código, quedando todo mucho más limpio y organizado.

### Inconvenientes de CQRS

- **Complejidad.**  
El hecho de tener que implementar CQRS incrementa notablemente la complejidad de nuestro sistema, tanto por tener el doble de configuraciones o instancias de base de datos, como por la necesidad del uso de _buses_ (aunque esto es realmente opcional). Si no estamos acostumbrados a trabajar por capas puede ser un cambio realmente brusco, aunque a medio/largo plazo la situación mejora ostensiblemente.  
Un problema derivado de la complejidad es que necesitamos un equipo senior formado para diseñar nuestra estrategia y montar un flow de trabajo que luego puedan seguir nuestros compañeros más juniors, pero un buen diseño puede derivar en un proceso casi mecánico para implantar nuevos casos de uso basados en _commands_ y _queries._
- **Consistencia eventual.**  
Éste es el verdadero gran inconveniente de CQRS, ya que, dependiendo de lo urgente que sea tener nuestros datos actualizados, puede ser una buena opción o no, debido a que tendremos que garantizar un tiempo mínimo para que sean consistentes ambas bases de datos. Luego derivado de esto pueden darse problemas con la entrega de datos de los _buses_, que pueden llegar repetidos o desordenados, y es algo que también tenemos que controlar.

A continuación veremos una técnica para facilitar la gestión de la consistencia eventual llamada _Event Sourcing._

### Consistencia eventual y Event Sourcing

Event Sourcing es un patrón o técnica que podemos utilizar para tratar de simplificar los problemas de consistencia eventual, así como añadir una serie de beneficios a nuestro sistema, tales como:

- **Observabilidad.** Sabemos por qué un registro está en el estado que está.
- **Tolerancia a fallos.** Podemos reconstruir los registros a partir de los eventos guardados.
- **Auditoria.** Estrechamente relacionado con la observabilidad, nos permite auditar las acciones que tienen lugar en nuestro sistema y podemos determinar flujos de datos erróneos.

#### ¿En qué consiste el _Event Sourcing_?

Es un patrón mediante el cual registramos en una base de datos separada o _event store_ todos los eventos que se producen en nuestro sistema, de modo que formen un histórico de cambios asociado a un registro de nuestra base de datos. Esto nos permite reconstruir un registro a cualquier momento del flujo de vida de nuestro sistema.

![](https://cdn-images-1.medium.com/max/1024/1*QuPUKisuzG55wL1DPJbCIg.png)
_Cadena de eventos producida por command handlers y consumidos por la base de datos de lectura para recrear el estado de un registro._

Los eventos, por supuesto, deben de guardar la fecha en la que fueron lanzados, ya que esta es la que determinara su orden en la cadena. También es importante que guarden el identificador del registro de base de datos al que pertenecen, de modo que cuando queramos recrear un registro o desplazarnos hasta un momento en el tiempo, es necesario poder identificar el registro en cuestión.

El _event sourcing_ solo se aplica en los modelos de escritura, ya que las lecturas no afectan al estado de los registros.

### ¿Qué nos aporta CQRS a nosotros?

Para nosotros CQRS ha sido todo un cambio para mejor, ya que nos ha permitido fijar un modo de trabajo común que simplifica mucho el entendimiento de los proyectos, y esto incluye cuando nos movemos entre proyectos en los que no hemos participado nunca.

Aunque de entrada se percibe como un sistema complejo (y lo es), las ventajas que nos aporta a nivel testabilidad, mantenibilidad y escalabilidad de nuestro código (así como la ya mencionada facilidad de rotación entre proyectos), compensan con creces los inconvenientes.

Cabe decir que en proyectos menores hemos tomado decisiones que simplifican todo o que anulan parte de los inconvenientes, como por ejemplo usar comandos síncronos o directamente eliminar los _buses_ de _queries_ y de _commands_, que borran de un plumazo gran parte de los problemas.

Adaptar los patrones a la necesidad de cada proyecto también es una decisión estratégica que hay que tomar, ya que debemos de estar seguros de sus implicaciones y su facilidad de revertir esas adaptaciones, ya que si un proyecto crece, puede que necesitemos esos extras que nos aporta el patrón original.

### Bibliografía

- [**CQRS Documents.** Greg Young (2010)](https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf)
- [**CQRS.** Martin Fowler (2011)](https://martinfowler.com/bliki/CQRS.html)
- [**CQRS By Example.** Carlos Buenosvinos y Christian Soronellas (2022)](https://leanpub.com/cqrs-by-example)
- [**Domain-Driven Design in PHP.** Carlos Buenosvinos y Christian Soronellas (2016)](https://amzn.to/3t0NKmJ)
- [**Event Sourcing explicado fácil.**](https://www.netmentor.es/entrada/event-sourcing-explicado-facil)

* * *