DDD Diseño Táctico

Es el momento de sumergirse de lleno en el concepto de Domain-Driven Design (DDD). Para desarrollarlo, lo abordaremos desde el punto de vista de la arquitectura de software.

En este post, trataremos de sintetizar los conceptos y explicar la esencia de cada uno de ellos, además de ver cómo se relacionan entre sí. Con este enfoque personal, lo que pretendo es allanarte el camino si estás comenzando con DDD. Seguramente, exista una gran cantidad de bibliografía sobre el tema. Pero entendiendo el propósito final de este enfoque de desarrollo, podrás decidir si merece la pena aplicarlo o no.

¿Qué es el Domain-Driven-Design (DDD)?

Antes de todo, debemos comprender de dónde viene este concepto y por qué se creó. La primera vez que escuchamos hablar de DDD es en el libro de Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software. En él, se presenta un enfoque de desarrollo revolucionario para la época (2003). Veníamos de un contexto en el que el desarrollo de software, en los años 90, se entendía como un proceso muy rígido y estructurado. Entonces, empezaron a surgir metodologías ágiles que permitían generar valor de una manera más rápida y flexible. Este es el germen del nacimiento de DDD, que busca precisamente cuidar y maximizar dicho valor.

Cuando desarrollamos software, normalmente buscamos representar un entorno o resolver un problema de negocio a través de lenguajes y metodologías de programación. Por ejemplo, al trabajar en una web o backend, muchas veces lo primero que pensamos es en el lenguaje o framework que utilizaremos.

DDD va más allá de todo eso: en realidad, esas cuestiones no son más que detalles. Son detalles de implementación, aspectos técnicos que, aunque importantes, no son prioritarios desde el punto de vista del negocio. Seguramente, estos aspectos preocupen mucho al desarrollador o arquitecto, quienes, en muchas ocasiones, sienten que los actores del negocio (product owners o stakeholders) no comprenden por su bajo nivel técnico. Esto nos ha pasado a todos y debo admitir que es parte del ego del desarrollador.

Lo que realmente quiere el cliente es proteger la lógica de negocio que interviene en la solución de software que estamos desarrollando. En ella, se concentra lo que realmente le interesa, tanto desde la estabilidad como desde la perspectiva de evolución. Y aquí es donde entra DDD. Esta metodología se centra en elevar a procesos y entidades de la lógica de negocio a ciudadanos de primera clase dentro del desarrollo.

Conceptos principales de DDD

Cuando empezamos a investigar acerca de DDD, uno de los primeros esquemas que encontramos es el famoso grafo de relaciones entre los diferentes conceptos existentes.

Esquema DDD

La verdad es que siempre he pensado que este diagrama resulta más confuso que aclaratorio. Sobre todo, cuando estamos comenzando a trabajar con este enfoque. Esto se debe a que los principales lectores son desarrolladores y su expectativa es encontrar una arquitectura que puedan seguir. Quizás, esta es la parte más confusa al principio. DDD ofrece una metodología, pero no se describiría precisamente como una arquitectura, aunque incluye ciertos aspectos arquitectónicos. De hecho, se integra muy bien con arquitecturas hexagonales que no analizaremos aquí, pero espero poder desarrollar en un post futuro.

Podemos dividir la metodología y sus conceptos en dos bloques. El superior o «bloque táctico» y el inferior o «bloque estratégico».

Este primer post estará enfocado en los desarrolladores, por lo que hablaremos de la parte técnica.

Bloque táctico

Todos los conceptos se relacionan de una forma u otra con el Model-Driven Design. Podríamos decir que este bloque técnico nos proporciona una «receta» para modelar el 100% del contexto del problema, utilizando los diferentes conceptos que aquí aparecen.

Procedamos a revisar cada uno de estos conceptos. Para ello, recurriré al ejemplo de una tienda de comercio electrónico y veremos cómo encaja cada concepto en el modelado.

Entidades

Las entidades son objetos que tienen una identidad única que los diferencia de otras instancias, incluso si comparten atributos iguales. Representan objetos con un ciclo de vida y características que pueden cambiar a lo largo del tiempo.

En nuestro ejemplo, podemos modelar como entidades a Cliente y Pedido. Cliente sería una entidad propia. Aunque existieran dos clientes con el mismo nombre e incluso apellidos, cada persona representa un cliente distinto. Por ello, tendrá un “id_cliente” único que lo diferencia.

Objetos de Valor

A diferencia de las entidades, los Objetos de Valor no tienen identidad propia, se definen únicamente por sus atributos. Son inmutables y representan conceptos con valor intrínseco que pueden compartirse entre entidades.

En nuestro ejemplo de tienda, las direcciones de reparto de los pedidos serían un Objeto de Valor. Esto es así porque una dirección es única en sí misma y consideramos que dos objetos con la misma calle y número, hacen referencia al mismo lugar.

Este concepto se entiende muy bien con otros objetos de valor conocidos. Por ejemplo, en el caso de los colores, que se definen por tres valores RGB. Al comparar dos colores, revisamos si sus tres valores coinciden. Si es así, hablamos del mismo color, por lo que no es necesario un “id_color”.

Agregados

En el caso de los agregados, hablamos de un conjunto de entidades y objetos de valor que se comportan como una unidad cohesiva dentro del dominio. Un agregado tiene una «raíz de agregado» que controla el acceso y las modificaciones a los elementos en su interior.

En nuestro ejemplo, un agregado podría ser el Pedido. Dentro de él podríamos incluir entidades como Producto, Detalles de Pago o Cliente, entre otros. Cualquier operación sobre el pedido, como añadir un nuevo producto, se realizaría a través de la raíz del agregado, que en este caso es la entidad Pedido.

Servicios

Este componente encapsula la lógica de negocio que no puede atribuirse a una entidad. Es decir, la lógica de negocio puede encontrarse tanto en las entidades como en los servicios. Las entidades encapsulan cierta lógica de negocio relacionada con un «estado», mientras que los servicios encapsulan comportamientos específicos relacionados con el dominio.

Por ejemplo, podríamos tener un servicio que sea capaz de consultar si es factible enviar un pedido a una dirección.

Repositorios

Los Repositorios son responsables de la persistencia de los agregados. Actúan como una abstracción que encapsula la lógica de acceso a la base de datos, permitiendo la recuperación y almacenamiento de entidades y agregados del dominio. Además, contienen la lógica necesaria para la serialización y deserialización de dichas entidades y agregados.

Los repositorios no siempre tienen que acceder a una base de datos, aunque sí lo hacen en la mayoría de los casos. Un repositorio se encarga de recuperar algún tipo de dato, pero no es necesario que nos importe cómo lo hace. Por ello, podría realizar una petición a un microservicio HTTP o recuperar un archivo del disco.

Deberíamos tener un repositorio para poder recuperar todos nuestros datos persistidos. De esta manera, tendríamos un repositorio para Pedidos, otro para Clientes, etc.

Factorías

Los patrones de diseño son herramientas que nos ayudan a crear objetos de dominio complejos, especialmente agregados, de una manera consistente y lógica.

Por ejemplo, podríamos tener una factoría que nos ayude a crear un Pedido pasándole un Cliente y una serie de Productos. Estos objetos podrán modificarse posteriormente. Sin embargo, si existe alguna lógica de verificación durante el proceso de creación, como comprobar si el cliente tiene pagos pendientes, podríamos encapsular esa lógica en este espacio.

Evento de dominio

Un Evento de Dominio es una notificación sobre un cambio de estado en el dominio que es relevante para otras partes de nuestro sistema. Refleja momentos o acciones que son significativas para el negocio y que, por supuesto, ya han ocurrido. Estos eventos se definen como objetos de primer nivel. Suelen incluir información contextual sobre el cambio como el momento en que ocurrió, el contexto en el que se produjo y otros detalles.

Volviendo a nuestro ejemplo, la creación y el pago de un Pedido podría generar un evento de «pago realizado». Éste podría ser recogido por otra parte de nuestro sistema para enviar un correo electrónico al cliente agradeciendo la compra.

Es importante acotar este concepto, pues es uno de los más interesantes y, sin duda, clave en toda la metodología. Los eventos de dominio permiten desacoplar eficazmente los triggers de los sucesos de los posibles interesados, actuales o futuros en dichos sucesos.

Arquitectura por capas

Por último, el diseño táctico está relacionado con este concepto. No es un instrumento tangible que permita modelar el problema en nuestro desarrollo de software.

Este concepto se refiere a una serie de reglas organizacionales que debemos implementar en nuestro código. No se trata de una arquitectura detallada, sino de una categoría de arquitecturas que podemos denominar “arquitecturas por capas”. Estas arquitecturas nos indican que debemos estructurar nuestro código en capas, promoviendo la independencia de responsabilidad y cambio entre ellas. Como mencionamos anteriormente, una arquitectura por capas es la arquitectura hexagonal, que se adapta muy bien a esta metodología.

Podemos dividir nuestro código en tres capas.

Capa de dominio

Es la capa central y contiene la lógica de negocio pura, los conceptos y reglas específicas del dominio. 

Aquí encontramos entidades, valores, agregados y eventos de dominio.

Esta capa es independiente de la infraestructura y de cualquier implementación de persistencia. La idea es que el dominio sea una representación fiel de la lógica de negocio, sin estar atado a la tecnología.

Capa de aplicación

Esta capa se encarga de coordinar las operaciones de la capa de dominio en respuesta a acciones o comandos específicos que puedan llegarle de fuera.

Contiene los servicios de aplicación, que orquestan la interacción entre los objetos de dominio y aseguran la coherencia de la lógica de negocio. No contiene lógica de negocio en sí, sino lógica de flujo y control.

Capa de infraestructura

Proporciona la capacidad para aspectos de bajo nivel, realizar acciones como persistencia, mensajería y comunicación con otros sistemas.

Aquí podremos encontrar los repositorios que interactúan con la bases de datos para persistir entidades, los que interactúan con otros microservicios, etc.

Estos repositorios contienen los detalles técnicos de cómo se realizan los mapeos a la base de datos, u otras integraciones.

Capa de presentación

Esta capa es la interfaz a través de la cual los usuarios interactúan con el sistema.

Aquí encontramos los controladores, vistas y API que exponen los servicios de aplicación al exterior.

Básicamente, actúa de proxy llamando a los servicios de aplicación para realizar operaciones y responden a las interacciones de los usuarios.

Conclusión

Hemos conocido qué es DDD de una forma sencilla, desarrollando los conceptos fundamentales que encontramos en esta metodología. Más que reglas técnicas, DDD invita a entender y modelar el problema del negocio de forma metódica y precisa.

Domain-Driven Design es una herramienta que nos ayuda a construir software alineado con el negocio, fácil de mantener y que puede crecer y adaptarse en el tiempo. Se trata de un enfoque que permite estructurar y entender mejor tanto el problema como la solución, integrando lógica de negocio y tecnología sin que una dependa de la otra. Aunque es un camino que requiere un cambio de mentalidad, a largo plazo puede marcar una gran diferencia en la calidad y la estabilidad de tus proyectos.

Si quieres ver un ejemplo de construcción de este diseño táctico en Python, te invito a visitar este repositorio de Git en el que intento cumplir con esta metodología + Arquitectura Hexagonal.

Hasta aquí nuestro post de hoy. Si te ha parecido interesante, te animamos a visitar la categoría Software para ver artículos similares y a compartirlo en redes con tus contactos. ¡Hasta pronto!
Óscar García
Óscar García
Artículos: 28