Featured image of post Command Query Responsibility Segregation

Command Query Responsibility Segregation

Contexto

CQRS son las siglas de Command and Query Responsibility Segregation, un patrón que separa las operaciones de lectura y actualización para un almacén de datos. La flexibilidad creada por la migración a CQRS permite que un sistema evolucione mejor con el tiempo y evita que los comandos de actualización causen conflictos de fusión a nivel de dominio. Perfecto para aplicar en aplicaciones con gran carga de rendimiento.

Gracias a CQRS somo capaces de desacoplar la lógica de nuestro sistema por acciones, y en los siguientes puntos veremos como trata de realizarlo.

Command

En CQRS un command no es un comando de CLI aunque la palabra nos lleve a ello

En CQRS, un Command representa la intención de realizar una operación en nuestro sistema que acabe modificando el estado de tal. Mediante un command enviamos información a nuestro sistema para que este sea alterado.

Los commands son DTO (Data Transfer Object) encargados de pedir modificaciones en el sistema mediante operaciones conocidas como Post, Put, Path, Delete. Un command no devuelve nada, por lo que el body de la respuesta estará vacío.

Un command es inmutable, y la razón es que si desde un controlador nos pasan un DTO (command) con una determinada información, y no tendría sentido que alteremos el objeto ya que sería modificar la tarea que nos han pedido.

Podemos tener commands síncronos, para operaciones pocos costosos en tiempo de ejecucción y commands asíncronos para las muy costosas.

El command acaba en un CommandHandler que tratará los datos como pueda ser crear Value Objects para poder realizar validaciones y asegurar que el command cumple con los requisitos del dominio, y seguidamente el CommandHandler enviaría estos datos al caso de uso para que se encargue de ir al repositorio para modificar los registros pertinentes.

Queries

Una query en CQRS no es la tradicional query SQL a la que estamos acostumbrados.

Una Query representa la intención de pedir unos datos a nuestro sistema sin que ello acabe alterando el estado de tal.

Las queries son DTO encargados de pedir información al sistema y que por consiguiente hacen que una respuesta vuelva a nosotros mediante la operación conocida como Get.

La query acaba en un QueryHandler que tratará los datos como pueda ser crear Value Objects para poder realizar validaciones y asegurar que el command cumple con los requisitos del dominio, y seguidamente el QueryHandler enviaría estos datos al caso de uso (servicio de aplicación) para que se encarge de ir al repositorio para recuperar el vídeo, devolverlo y transformarlo en la respuesta esperada.

Commands and Queries Bus

Estos bus son las interfaces que actúan de intermediario entre los commands/queries y los Handler correspondiente. Saben a que Handler tienen que llevar según el command o query que esté llegando.

Su misión principal es quitarle la responsabilidad al controlador de tener que saber a que handler tiene que enviar una command o query determinado.

Para ver que ganamos mediante el uso de los buses vamos a ver la evolución de una arquitectura más tradicional a una que utiliza estos buses.

Etapa 1) Post ➡ Controller: En este caso realizamos una petición http tipo post al controlador, y este será el encargado de realizar toda la lógica. Es el caso tradicional más extremo pero iremos iterando para ver las diferencias. En este caso si quisiéramos añadir otro tipo de punto de entrada a nuestra aplicación distinto al controller (http), tendríamos que copiar la lógica de dicho controlador a el nuevo punto de entrada (ejemplo, un comando de terminal).

Etapa 2) Post ➡ Controller ➡ Caso de uso: En este caso realizamos una petición http tipo post al controlador, y esté conoce a que caso de uso (un servicio de nuestra aplicación) debe dirigirse para que sea ejecutada la lógica. En este caso si quisiéramos añadir otro tipo de punto de entrada a nuestra aplicación distinto al controller (http), tendríamos que apuntar al mismo caso de uso que utilizaba el controller desde el nuevo punto de entrada (ejemplo, un comando de terminal). Gracias a que tenemos la lógica separada del controlador logramos reutilización de código.

Etapa final) Post ➡ Controller ➡ Instancia command (DTO) ➡ CommandBus (interface) ➡ CommandHandler Caso de uso: En este caso realizamos una petición http tipo post al controlador, el cual instancia un command (DTO) que será enviado al CommandBus, que es una interface de dominio que será posteriormente implementada. El CommandBus sabe según que tipo de command está recibiendo a que CommandHandler debe destinarse. En este caso si quisieramos añadir otro tipo de punto de entrada a nuestra aplicación distinto del controller (http), tendríamos instanciar del mismo modo el command deseado, y enviarlo al CommandBus para que este se destine al CommandHandler que toque. Además de ganar en reutilización de código como hacíamos en el paso b, también estamos desacoplando al controlador de conocer a que caso de uso debe dirigirse, simplemente pedirá commands al CommandQuery, hacemos que los controladores que son algo que naturalmente son de infraestructura no conozcan como funciona nuestro dominio. Entonces si el día de mañana queremos cambiar que se debe hacer con un tipo de command determinado, simplemente tendremos que cambiar la lógica en el CommandHandler y así los N puntos de entrada a la aplicación (controladores, comandos de terminal, etc) no necesitarán cambiar nada de su comportamiento. Previamente hemos comentado que el CommandBus es una interfaz que debe ser implementada, y esto nos da la posibilidad de crear una implementación asíncrona del CommandBus para que las peticiones no tengan que ser resultas en el mismo momento en el que se realizan, si no que se irán procesando y se pasará la respuesta cuando esté disponible.

💡 En esta comparación de diseño hemos hablado solo de los Command Bus, pero se aplica exactamente el mismo funcionamiento cuando se trate de una Query y su determinado Query Bus

CQRS + Event Sourcing

CQRS complementa la idea tradicional del Event sourcing de desacoplar nuestro código lanzando eventos y que otro módulos de nuestra aplicación se suscriban a tales eventos para interactuar con ellos. Por lo que vamos a ver como se acoplan estos dos patrones de diseños y nos brindan los mejor de ambos con el siguiente esquema:

Before (Only CQRS)

cqrs
cqrs
After (CQRS + Event Sourcing)
cqrs and eventsourcing
cqrs and eventsourcing

Mitos y otras dudas

Cuando estuve aprendiendo que era el CQRS me hice la pregunta de que si iba a necesitar dos bases de datos, una para el modelo de escritura y otro para el de lectura. En parte los contenidos que leia me llevaban a pensar eso, pero si lo pensamos bien, tendría sentido usar dos bases de datos que se sincronicen entre sí solo si nuestra aplicación lo requiere, es decir, que vayamos a tener que manejar un gran volumen de datos, y/o que sepamos que la consulta y modificación puedan interferir entre sí, pero lo dicho, no es la regla general.

Para más dudas sobre CQRS dejo un enlace que me ha sido de gran ayuda: https://event-driven.io/en/cqrs_facts_and_myths_explained/

Ejemplo práctico

Navegando por Github me encontré un buen ejemplo de la implementación de CQRS + Event Sourcing utilizando Java, ver el siguiente enlace:

Java-CQRS-Introducción

Creado con Hugo
Tema Stack diseñado por Jimmy