Complejidad del negocio con patrones DDD y CQRS – I

Bienvenidos a un nuevo articulo en el que comenzaremos la elaboración de un proyecto de ejemplo, y en el que aplicaremos las técnicas de desarrollo conocidas como DDD (Domain Driven Design), junto con un efoque CQRS (Command and Queries Responsability Segregation). Existe una gran cantidad de libros que hablan sobre el uso de DDD en proyectos donde la complejidad del sistema es tal que si no sigues un orden o no aplicas una estructura rígida y clara en tu código, es muy probable que te estrelles a mitad del camino con un monstruo de proyecto donde sólo los más valientes se atreverían a tocar o aquellos que vieron al monstruo crecer y se conocen todos los aspectos extraños que realiza en su interior.

Con esto vengo a decir que yo no voy a reinventar la rueda ni simplemente a plasmar los conocimientos adquiridos en un libro, si no a mostraros la interpretación que yo he adquirido acerca del uso del patrón DDD y como lo voy aplicando y adaptando en mi día a día en los nuevos proyectos que he ido desarrollando. Puesto que no existe una única forma de aplicar DDD junto con CQRS y es más, puede que el problema con el que te toque enfrentarte no sea necesario su uso o incluso el aplicarlos requeriría un sobre esfuerzo por parte de los desarrolladores cuando en realidad la envergadura del proyecto no es merecedora de tanto empeño.

Además somos nosotros mismos los programadores los que debemos de elegir la herramienta adecuada para el trabajo que le corresponde, no es conveniente aplicar la misma arquitectura en todos los proyectos sin pensar, al igual que un carpintero no utiliza siempre la misma sierra para cortar madera. Con ésto comenzamos esta serie de varios artículos donde culminaremos con un proyecto completo elaborado usando DDD y CQRS.

Nota: DDD y CQRS no son arquitecturas de alto nivel, si no sólo patrones de arquitectura. Microservicios, SOA (Service Oriented Architecture) y EDA (Event Driven Architecture) son ejemplos de arquitecturas de alto nivel.

¿Qué es CQRS?

Comenzamos detallando qué es Command and Query Responsability Segregation, cómo, y para que se utiliza. Pues bien, CQRS fue promovido por Greg Young y parte de la noción de usar un modelo distinto para actualizar la información de tu sistema del modelo que se usa para leer. Como en la gran mayoría de los problemas, y no sólo de la informática, se solucionan dividiendo el problema y agrupando por alguna característica común, esta es la idea base de CQRS.

Antes de que existiera este enfoque los datos de un sistema se gestionaban en base a una metodología CRUD, donde las acciones posibles sobre los datos de un tipo almacenados eran:

  • (Create) Crear nuevos registros.
  • (Read) Leer registros.
  • (Update) Actualizar registros.
  • (Delete) Eliminar registros.

Sin embargo, esta metodología aunque efectiva funciona mejor cuando sólo trabajas con un solo tipo de registro en sus acciones, si miramos desde una perspectiva en la que queremos obtener información de distintos elementos o agrupar estos mismos en uno, poco a poco vemos como esta estructura se diluye y es difícil de gestionar o delimitar los entornos de cada elemento.

Este es el cambio que introduce CQRS, en vez de ver nuestro sistema como un solo modelo donde se realizan todas las acciones, tenemos dos modelos uno para obtener los datos (Queries) y otro modelo para las acciones o actualizaciones de los elementos (Commands). Al tener dos modelos separados se puede incluso hacer que ambos estén en distintos sistemas de almacenamiento.

Ambos modelos pueden ser iguales o totalmente diferentes pero lo que si que deben ser entre ellos es consistentes, y esto quiere decir que los datos mostrados al usuario en la interfaz son gestionados por el modelo de consultas (Queries), pero si este mismo usuario realiza una acción a través de la interfaz para actualizar el modelo, ésta acción sera comunicada al modelo de acciones (Commands), donde a su vez una vez terminada la acción deberá ser comunicada al modelo de consultas y a la interfaz. Como vemos la complejidad de nuestro sistema a la hora de aplicar CQRS se incrementa, y es por ésto por lo que no es recomendable aplicarla en todos los sistemas, si en algún momento diseñando tu modelo observas que se parece a un CRUD, se comporta como un CRUD y es suficiente abordándolo como un CRUD, creo que ya sabes por donde voy…

Para terminar cabe destacar en qué tipos de sistemas encaja bien CQRS, como pueden ser modelos donde en su base predomina el Event Sourcing o en sistemas con modelos del dominio complejos donde es recomendable el uso de DDD.

¿Qué es DDD?

Domain Driven Design empezó a ser nombrado por Eric Evans en su libro «Domain Driven Design – Tackling Complexity in the Heart of Software«, y principalmente trata acerca de cómo abordar la complejidad del negocio desde el punto de vista de los desarrolladores junto con la ayuda de los expertos en el dominio que no tienen porqué ser estos programadores. A fin de cuentas, DDD identifica los problemas domo «dominios», y el dominio en si viene a ser el comportamiento de nuestra aplicación fuera de los límites de la tecnología en cuestión, es decir, un dominio es independiente de la tecnología con la que el proyecto sea desarrollado. Si que es verdad que al final el desarrollo se realizará con algún lenguaje específico para poder fabricar la aplicación en cuestión, pero el dominio en sí podría ser plasmado en un simple papel sin necesidad de alguna computadora.

Hablamos de las reglas que dictan como nuestro sistema reaccionará a los distintos datos de entrada que recibirá, y dentro de todas las reglas que nuestro sistema adquiera, un dominio es identificado como una isla de conocimiento común, o un conjunto de reglas acordes a una misma temática y que deben trabajar en común en pos de que nuestro sistema se comporte como es debido. Bajando a un punto más tecnológico un dominio es el conjunto de clases que colaboran entre sí otorgando funcionalidades a través del código.

Bounded Context

Dentro de estas islas del conocimiento en DDD también se identifica lo que sería denominado Contexto Delimitado o Bounded Context, donde en cada dominio o dominios se pueden identificar grupos de clases que colaboran entre sí para llevar a cabo una tarea específica, pero además en otro Bounded Context estas mismas clases o entidades pueden existir pero para cumplir con otro objetivo, por lo que aunque en ambos contextos existan puede que su tarea sea tal que no adquieran tanta relevancia dentro del dominio y se modelen de forma diferente. Para aclarar un poco las ideas pongamos un ejemplo:

  • Imaginemos que un restaurante nos pide una aplicación para gestionar los pedidos de los clientes para envíos a domicilio, pero además esos mismos clientes si quieren pueden seleccionar su comida y decidir ir al restaurante para poder comer allí, donde pueden llegar con la mesa ya puesta y el menú listo.

Ante esta disparidad de opciones nosotros decidimos dividir el dominio en dos Bounded Context, uno para pedidos de envío a domicilio (Orders Context) y otro para reservas de menú con posibilidad de comer en el restaurante (Reservations Context). El modelado podría quedar como el siguiente:

El ejemplo queda muy simple pero la finalidad es comprender que en ambos contextos se han definido las mismas entidades de Customer, Menu y Plate, y que no por duplicar estas clases estamos haciendo algo mal o incumpliendo el principio DRY. Lo que sucede es que según en que contexto se mire su funcionalidad principal es distinta y por lo tanto cada Bounded Context cumple con distintas reglas determinadas, además de que cada contexto es lo suficientemente importante y contiene la cantidad de lógica necesaria como para coexistir como dos entes sin necesidad de que por ello resulte redundante.

En el contexto de Orders Context la entidad Customer tiene una referencia a las clase Address, que como su propio nombre indica hace referencia al concepto de la dirección del usuario, una dirección se compone principalmente del nombre de la calle junto con su número, dirección postal…etc. Datos que seguramente podrían incluirse dentro de Customer, pero sucede que dentro del Bounded Context de pedidos (Orders Context) es lo suficientemente importante este concepto y aporta tal valor al negocio que no estaría de menos que se representase como una entidad en si misma. Por otro lado para el Bounded Context de reservas (Reservations Context) ese concepto no aporta el mismo valor para el negocio puesto que un cliente si que contiene una dirección al final, pero para nuestro objetivo no es lo suficientemente importante como para crear una entidad, por lo que esa información de la dirección puede ser incluida en la entidad Customer.

Espero que con este ejemplo quede algo más claro como el modelado del dominio de un negocio que en principio podría verse de forma similar, puede ser dividido en modelados parecidos pero vistos desde el punto de vista de la finalidad principal de cada contexto o Bounded Context.

El Lenguaje Ubicuo

Este concepto surge de la relación que antes destacaba entre los programadores y los expertos en el dominio o usuarios finales, creando un lenguaje común para que todas las funcionalidades implementadas en el sistema sean interpretables por ambas personas, y no sólo el programador sea capaz de entender que es lo que esta sucediendo entre esas líneas de código. Con esto conseguimos que en las reuniones entre ambas partes todo el mundo hable un mismo idioma y se reduzca al máximo la incoherencia por entendimiento común.

El lenguaje ubicuo se va moldeando poco a poco con cada reunión, hablando con las partes interesadas y descubriendo qué es lo que quieren implementar, sacando de las conversaciones cada verbo, sustantivo o concepto que sea interpretable o aporte un valor en nuestro sistema, para así cuando un cliente diga «El sistema debe de mandar un correo a todos los clientes….«, en código al final exista una función denominada como SendEmailToAllClients(), o conceptos principales o relevantes en nuestro sistema se conviertan en clases o entidades.

Finalmente todos los conceptos extraídos de estas reuniones deberían de documentarse y asegurarnos de que todos los integrantes del equipo e incluidas ambas partes están de acuerdo y lo entienden de la misma forma. Con esto formaremos un Lenguaje Ubicuo consistente y que no de lugar a dudas.

Estructura del Código en Capas

Para mantener un sistema complejo a lo largo del tiempo es indispensable que sigamos una estructura lógica del código distribuida en distintas capas, donde en cada capa deben de mantenerse ciertas funcionalidades relacionadas con aspectos de nuestro sistema. Por lo tanto cada capa son artefactos lógicos, y para nada están relacionadas con el despliegue del servicio.

Y además también se han de seguir unas restricciones en cuando a la comunicación entre las diversas capas, porque aunque tengas tu código dividido por capas si no mantienes un orden en las comunicaciones y todas se relacionan entre sí, al final no sirve de nada esta jerarquía.

A fin de cuentas el objetivo de distribuir el código de esta forma radica en la capacidad de otorgar una alta cohesión y un bajo acoplamiento. Conceptos que bien nos pueden sonar de principios SOLID, al final lo que se está intentando conseguir es que nuestro sistema esté dividido en secciones de un conocimiento específico y que además dichas secciones apenas se conozcan entre ellas. ¿Qué conseguimos con esto? Lograremos un código altamente mantenible, con una mejora de la reusabilidad de los distintos módulos obtenidos y por ende una gran capacidad de pruebas independientes.

Entremos ya en materia, y comenzamos mostrando un listado de las distintas capas involucradas:

Como bien se puede distinguir en el esquema anterior, identificamos los diferentes entes o capas en: Capa de Dominio, Capa de Aplicación, Capa de Infraestructura y Capa de Infraestructura Transversal.

Cabe la posibilidad de poder añadir otras capas opcionales o capas que son incluidas dentro de otras por simplicidad, como puede ser en este ejemplo la Capa de Presentación, encargada de mostrar los datos al cliente, la presentación debería ser totalmente independiente a como el servicio provee los datos incluso con su propia arquitectura lógica de presentación como puede ser MVC, MVVM, MVP…etc, por eso se ha plasmado de dicha forma. O también la capa de Web-Services encargada de exponer los recursos de nuestro servicio como interfaz web, que en éste caso no se muestra en el esquema pero iría incluida dentro de la Capa de Aplicación.

Conclusión

En éste punto terminamos esta primera parte acerca de cómo usar DDD y CQRS a la hora de elaborar un proyecto con una lógica de negocio compleja. Podría continuar pero lo que viene a continuación es totalmente técnico, de puro código, y después de todos los conceptos teóricos anteriores mejor nos conviene asentar conocimientos mientras nos tomamos un café. Por eso y porque considero que se quedaría un artículo demasiado extenso, tanto que parecería un libro más que una publicación Web.

Más adelante entraremos en detalle con un proyecto empezado desde cero, y con código de ejemplo en C#, donde se mostrará realmente el aplicativo de cada uno de los elementos y capas descritos en ésta primera parte introductoria. Y puede que cuando termine toda está serie de publicaciones acabemos convirtiendo el proyecto resultante en un Microservicio, Quien sabe….