¿Qué es CQRS (Command Query Responsibility Segregation)?
La abreviatura CQRS que significa Command Query Responsibility Segregation (separación de la responsabilidad de consultas y comandos) es un patrón de diseño de software el cual separa las consultas (recuperar datos) de los comandos (inserción, actualización y borrado de datos).
¡Hola 👋! Últimamente me estoy enfocando en aprender patrones de diseño de software y en mi camino he dado con CQRS. En este tutorial te voy a explicar que es CQRS y más adelante veremos como aplicarlo en un proyecto real.
¿Por qué utilizar CQRS?
CQRS se utiliza en aplicaciones de alto rendimiento. Al separar las consultas de los comandos, podemos por ejemplo utilizar dos bases de datos, una NOSQL, ya que son más rápidas a la hora de recuperar información, y otra SQL para los comandos (inserción, actualización y borrado) que son más veloces que las NOSQL a la hora de realizar estas acciones.
Fomenta el uso del asincronismo en acciones más lentas, como puede ser por ejemplo un envío masivo de emails, en el que podemos lanzar esta acción de forma asíncrona sin esperar el resultado y así no bloquear al usuario hasta que termine este proceso.
En este caso, podemos tener un websocket para recibir una notificación confirmando la finalización del proceso o un panel donde podamos ver el estado de todos los trabajos asíncronos que lancemos.
También fomenta la reutilización de código, por ejemplo en aplicaciones en las que tengas un servicio web, pero que además también se pueda utilizar en consola (CLI). En este caso puedes tener el controlador web y el controlador CLI y al utilizar CQRS, como tendremos separada toda la lógica de negocio fuera los controladores, podremos reutilizarla y aprovecharla cuando la necesitemos. Esto lo veremos más claro en un próximo tutorial ya con un ejemplo práctico.
Traza de la arquitectura CQRS
Antes de meternos a fondo en materia, voy a explicar brevemente un ejemplo de la traza de ejecución para que te vayan sonando los distintos componentes y después explicaré para qué sirven.
Por ejemplo, tenemos una petición POST para crear un usuario en una API:
- El controlador recibirá los parámetros para la creación del usuario
- Se crea un Command el cual será un DTO (objeto de transferencia de datos) con los parámetros de creación del usuario
- El CommandBus recibe por parámetro el Command el cual se encarga de enviarlo al CommandHandler
- El CommnandHandler recibirá el Command y este se encargará de enviarlo al caso de uso
- El caso de uso validará los datos y los persistirá en la base de datos
Importante decir que en el caso de las inserciones, actualizaciones o borrado, no se deberá realizar ningún tipo de retorno, si por ejemplo hay un fallo por la validación, se lanzará una excepción y ya nosotros usando un framework o no, seremos los que nos tendremos que encargar de manejar ese error para mostrarle la información de lo que ha pasado al usuario.
En el caso de una consulta, el ejemplo sería similar, pero utilizaríamos la palabra Query en vez de Command (Query, QueryBus, QueryHandler) y en este caso sí que necesitamos realizar un retorno, ya que es una consulta.
Command
En CQRS, un Command representa la intención de realizar una acción en nuestro sistema que acabe modificando el estado de tal como puede ser crear un registro, modificar uno existe o eliminarlo.
El formato del Command será un DTO (objeto de transferencia de datos) el cual representa la acción que queremos hacer. Como vimos anteriormente en la traza de una ejecución con CQRS, si queremos crear un usuario, debemos crear un DTO que recibirá los parámetros que el usuario ha enviado a nuestra aplicación:
final class CreateUserCommand extends Command
{
public function __construct(
private string $userId,
private string $userName,
private string $userEmail
)
{
}
public function userId(): string
{
return $this->userId;
}
public function userName(): string
{
return $this->userName;
}
public function userEmail(): string
{
return $this->userEmail;
}
}
Este Command sería enviado desde el CommandBus hasta el CommandHandler y como comenté anteriormente, en el caso de uso ya nos encargamos de validar y persistir los datos.
Query
En CQRS, una Query representa la intención de solicitar datos a nuestro sistema sin que ello acabe alterando el estado de tal.
Al igual que en el Command, una Query será un DTO el cual representará la petición de datos que queremos consultar. Por ejemplo, podríamos querer consultar un listado de usuarios por X filtros:
final class GetUsersQuery extends Query
{
public function __construct(
private ?string $orderBy = null,
private ?string $order = null,
private ?array $filters = null,
private ?int $limit = null,
private ?int $offset = null
)
{
}
public function orderBy(): ?string
{
return $this->orderBy;
}
public function order(): ?string
{
return $this->order;
}
public function filters(): ?array
{
return $this->filters;
}
public function limit(): ?int
{
return $this->limit;
}
public function offset(): ?int
{
return $this->offset;
}
}
Al igual que con el Command, esta Query sería enviada desde el QueryBus hasta el QueryHandler y desde el caso de uso se validarían los filtros y se realizaría la consulta.
Command/Query Bus
Aclarado esto, el siguiente paso es ver por donde se mueven estos Command/Query. Estos los crearemos desde el controlador y los lanzaremos al bus correspondiente (QueryBus o CommandBus). Este bus será el encargado de trasladar el DTO a su Handler correspondiente.
Command/Query Handler
En Handler recibirá el DTO y este lo enviará al caso de uso. Gracias a esto, el controlador estará completamente desacoplado del caso de uso y si queremos cambiarlo solo debemos realizar la modificación en el Handler por el nuevo caso de uso y listo.
Caso de uso
Aquí es donde recibimos el DTO y aplicamos la lógica de negocio, validaremos los datos y después, según el tipo, los persistiremos o los recuperaremos.
Fuentes
Para escribir este tutorial me he basado en las siguientes fuentes:
- Curso de Codely.tv de CQRS: Command Query Responsibility Segregation
- Patrón CQRS explicado en 10 minutos
- ¿Qué es un DTO?
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 👋.