Manejador CQRS (C#) con lógica de dominio y canalizaciones - Explicado en el vídeo de Derek Comartin
Command Query Responsibility Segregation (CQRS) es un potente patrón de diseño en aplicaciones .NET que ayuda a mantener una clara separación entre las operaciones de lectura y escritura. Esta separación permite mejorar la escalabilidad, la capacidad de prueba y el control de la lógica empresarial compleja. Sin embargo, a medida que su aplicación crece, sus manejadores CQRS en C# pueden hincharse de lógica, lo que los hace más difíciles de mantener y probar.
En su perspicaz vídeo sobre "Clean Up Bloated CQRS Handlers with Domain Logic & Pipelines", Derek Comartin explica cómo limpiar dichos gestores de comandos CQRS trasladando la lógica a modelos de dominio y estructurando una canalización para gestionar la lógica de comandos paso a paso. En este artículo, exploraremos el enfoque de Derek en detalle y aprenderemos cómo construir implementaciones CQRS más mantenibles en C#.
El problema de los manejadores hinchados
Derek comienza destacando el escenario común en muchas aplicaciones web: abres un manejador CQRS en C#, y es un desastre. Hay validación de datos, autorización, lógica de negocio, transiciones de estado, publicación de eventos, registro... todo mezclado.
Ilustra esto utilizando un objeto de comando para despachar un envío. El encargado es responsable de:
-
Acceso al almacén de datos para cargar el envío.
-
Comprobación del estado del envío.
-
Actualización del estado (es decir, actualización de datos).
-
Guardar los cambios en la capa de acceso a datos.
-
Enviar un correo electrónico.
- Publicación de un evento de dominio.
Todo ello en un único lugar. Esto viola la intención del patrón CQRS, donde el objetivo es separar las preocupaciones y mejorar el rendimiento y la capacidad de mantenimiento.
Mover la lógica del dominio al modelo de datos
El primer paso de Derek es trasladar la lógica de validación y transición de estados al modelo de datos. Crea un método Dispatch() dentro de la clase Shipment. Aquí es donde vive ahora la lógica del dominio.
En lugar de comprobar manualmente el estado del envío en el controlador, la lógica se encapsula en este método, lo que garantiza la integridad de los datos y un comportamiento coherente dondequiera que se active el envío. Esto es clave para implementar una arquitectura limpia en su aplicación basada en CQRS.
Por ejemplo, cualquier lugar que llame a shipment.Dispatch() realiza automáticamente todas las validaciones y transiciones de estado. Esto se alinea con el patrón de diseño CQRS, ayudando a mantener una clara separación entre el manejador y la lógica de dominio.
El valor de centralizar la lógica
Derek señala que este tipo de cambio no consiste en añadir una abstracción innecesaria. No se trata de una traducción técnica, sino de centralizar la lógica que se utiliza en diferentes partes del código de la aplicación. Si es necesario que varios gestores de comandos realicen un envío, esta lógica personalizada debe estar en un único lugar, dentro del modelo de dominio.
De este modo, su modelo de datos será más robusto y sus implementaciones del controlador CQRS en C# más sencillas y fáciles de mantener.
Presentación del patrón de canalización
Para limpiar aún más el gestor de comandos, Derek introduce un patrón de canalización. Esta estructura procesa un comando como una secuencia de pequeños pasos de propósito único, cada uno de los cuales toma un objeto de contexto y llama al siguiente paso.
Se trata de un middleware conceptualmente similar a ASP.NET Core, y cada paso se centra en una parte específica del flujo:
-
Recuperación del envío (es decir, lectura de datos)
-
Dispatching it (ejecución de operaciones de escritura)
-
Eventos de publicación
- Guardar en el almacén de datos
Estos pasos utilizan un objeto de comando compartido que fluye a través de la canalización. Esto crea una implementación limpia y modular de la segregación de responsabilidades de comandos y consultas.
Ejemplo de implementación de la tubería
En su ejemplo de implementación, Derek estructura el proceso con pasos como:
-
Cargar el envío: extraer los datos de la capa de acceso a datos mediante un repositorio.
-
Despachar el envío - invocar el método Dispatch() para aplicar la lógica de dominio.
-
Añadir un evento de dominio: adjuntar un evento "ShipmentDispatched" al contexto.
-
Publicación de eventos - envío de eventos para notificar a sistemas externos.
- Guardar cambios: persistencia de las actualizaciones en el almacén de datos.
Cada paso representa una parte distinta de la lógica de comandos, lo que mejora la validación de datos y mantiene las responsabilidades separadas.
Derek también señala que las notificaciones por correo electrónico ahora se gestionan por separado reaccionando al evento del dominio. Esto se ajusta a los principios de aprovisionamiento de eventos y promueve la coherencia final.
Beneficios de las pruebas y la mantenibilidad
Una de las mayores ventajas de este patrón es la capacidad de comprobación. Con un gestor de comandos grande, puede tener varias dependencias (por ejemplo, repositorios, servicios de correo, registradores). Pero cuando se divide el gestor en pasos, cada uno de ellos sólo requiere un par de dependencias.
Este enfoque modular permite probar pasos individuales fácilmente con inyección de dependencias, utilizando fakes o mocks cuando sea necesario. Por ejemplo, si está probando un paso que llama a Dispatch(), no necesita simular un servicio de correo electrónico o un editor de eventos.
Esta separación de preocupaciones sigue el patrón CQRS de segregación de responsabilidades, haciendo que sus modelos de lectura y escritura sean más limpios y estén más centrados.
Composibilidad y reutilización
Otra de las ventajas del enfoque de canalización es que es componible. Si utiliza algo como el patrón Outbox, puede asegurarse de que los eventos se publican sólo después de que persistan los modelos de escritura. Este nivel de control es esencial en las implantaciones de CQRS, donde la coherencia y las garantías de entrega son importantes.
También puede compartir pasos entre diferentes gestores de CQRS, por ejemplo, un paso genérico "SaveChanges" o un paso "ValidateRequest".
Utilizando herramientas como la biblioteca MediatR, que admite la gestión de comandos y consultas, puede incluso registrar estos pasos mediante la inyección de dependencias en su aplicación .NET Core utilizando los servicios IServiceCollection.
Para configurar este sistema, puede ejecutar Install-Package MediatR a través de la consola del gestor de paquetes de Visual Studio, un paso habitual al implementar CQRS en C#.
Las ventajas y desventajas
Derek no rehúye la mayor complejidad que conlleva este enfoque. Las canalizaciones introducen indirecciones y, cuando se observa una pila de llamadas, puede parecer que se está navegando por un laberinto.
Sin embargo, en el caso de la lógica empresarial compleja, la compensación suele merecer la pena. Si un manejador tiene más de 10 dependencias y cientos de líneas de lógica, CQRS permite a los desarrolladores estructurar y mantener mejor estos flujos.
Pensamientos finales sobre cuándo refactorizar
Derek concluye recordando a los espectadores que consideren cuidadosamente si su implementación de C# del controlador CQRS está realmente hinchada. No todos los escenarios requieren una canalización. Su objetivo es ilustrar posibilidades, y corresponde a los desarrolladores evaluar su propia implementación de CQRS y determinar si tales patrones serán de ayuda.
Anima a los desarrolladores a buscar áreas en su código en las que la separación de preocupaciones ayudaría a mantener la coherencia, hacer el código más modular y gestionar mejor las operaciones de lectura y escritura, especialmente en aplicaciones web CQRS.
Conclusión
El vídeo de Derek Comartin ofrece una guía práctica para limpiar los gestores de CQRS mediante la encapsulación de lógica de dominio y canalizaciones. Este enfoque ayuda a abordar los problemas de saturación de código, promueve la integridad de los datos y mejora la capacidad de mantenimiento al dividir el código de la aplicación en modelos distintos.
Tanto si gestiona datos de empleados, detalles de productos o un nuevo comando de usuario, la aplicación del patrón CQRS con canalizaciones y diseño basado en dominios hará que su código base sea más escalable, comprobable y robusto.
Al utilizar objetos de transferencia de datos, modelos separados y mantener una separación clara entre la lógica de lectura y la de escritura, su aplicación .NET estará mejor estructurada y será más fácil de evolucionar con el tiempo.


