DDD, el Dominio implica “Crecer fuerte”

“Crecer fuerte” es el lema de la casa de Tyrell. Si eres fan de ‘Juego de Tronos’ conocerás a esta familia, una de las que forman el maravilloso entramado de la aclamada serie.

Si extrapolamos esta premisa al mundo IT, podríamos decir que, a partir de ese lema, se dan las pautas de lo que implica un proyecto desarrollado a partir del paradigma DDD (Domain-Driven Design), o lo que es lo mismo: diseño orientado al dominio.

En este post hablaré de mi experiencia personal con este paradigma o filosofía, y de cómo fue mi proceso de aprendizaje durante un proyecto en el que no tenía ni idea de qué implicaba a nivel de Desarrollo y Negocio.

Mis primeros pasos

Cuando llegué a este proyecto en cuestión, lo primero que hicieron mis compañeros fue darme “el libro gordo de Petete” o “el Libro Rojo”. Antes de comenzar a tirar una línea de código me tocó leerme “Implementing Domain-Driven Design”, de Vaughn Vernon. Un libro (rojo) que habla de la necesidad de DDD y diferentes ideas acerca del diseño y la arquitectura de una aplicación para llevar a cabo este paradigma de trabajo.

Introduce conceptos interesantes como “Qué es el lenguaje ubicuo” o ejemplos de arquitectura compatibles con esta filosofía. Además, se mete a fondo con conceptos como Entidades, Value-Objects, Servicios, Agregados, Factorías, Repositorios…

Se me hizo duro, tardé cerca de dos semanas en leerlo y tener los conceptos más o menos claros. Pero como siempre ocurre, una cosa es la teoría y otra bien distinta la práctica.

Conceptos

Quizás el concepto más interesante y la base de DDD es definir un lenguaje ubicuo. ¿En qué consiste?

Lo primero que hay que tener claro es que el dominio es una abstracción de la realidad, de tal manera que debemos adaptar lo máximo posible nuestro modelo a la misma.

Para que DDD sea efectivo debe existir un lenguaje común (lenguaje ubicuo) entre desarrolladores y los usuarios (y todos los miembros del proyecto, product owners, DBA’s etc.).

Para ello, debemos tener claro el Modelo de Dominio, cuya construcción es un proceso iterativo con continuas reuniones entre los miembros del equipo, incluidos los expertos en el dominio. El modelo, la codificación y el diseño de la aplicación deben ir de la mano, es decir, si cambia un concepto del modelo, también se debe cambiar el diseño y la codificación.

El modelo no tiene que ser la representación exacta de la realidad, sino una representación  fiel de la misma, eliminando redundancias y conceptos que resulten irrelevantes en el producto final.

¿Y qué debe ser imperativo en el lenguaje ubicuo? Que la definición de nombres, variables, funciones, clases… sean autoexplicativas. De hecho, los verbos suelen ser las acciones que se realizan (métodos), y los nombres son los objetos sobre los que se realiza dichas operaciones. Por ejemplo:

getCars (String color)

En este caso, el verbo sería “get” (dame), y el nombre sería “Cars” (objeto).

Bajo mi punto de vista, esto es lo más difícil de comprender de este proceso, ya que, aunque en teoría es algo que deberíamos de entender todos, cada miembro de un proyecto maneja lenguaje muy diferente.

Un perfil de negocio entiende de “lo que tenemos que hacer”, pero no entiende las implicaciones técnicas ni por supuesto domina dicho lenguaje. Por ejemplo, para lo que ellos es una “rescisión” para nosotros, como desarrolladores, puede ser simplemente un “borrado”.

DDD ayuda a tener un diseño más cercano al lenguaje natural que al lenguaje técnico, pero evidentemente, para que esto tenga sentido, debe complementarse con la adecuada decisión de patrones de diseño, inversión de dependencia (especial cuidado con las referencias cíclicas), encapsulación y ACL’s (capas anticorrupción, un patrón de diseño del cual hace referencia Martin Fowler, y que tratan la integración entre dominios).

Con estas premisas complementadas, deberíamos tener como resultado final un producto que debe ser comprensible para todos los miembros del equipo independientemente de su rol.

Todo ello aderezado con técnicas de código limpio, TDD, BDD, etc… Si aplicamos todo esto de forma adecuada, como he dicho al principio, tu proyecto “crece fuerte”, y no solo fuerte, también seguro, robusto, escalable etc…

Dada esta breve, aunque espero, descriptiva definición de lo que es realmente el núcleo de DDD, vamos a dar conceptos que ayudan a trasladar estos conceptos sueltos en un modelo de dominio.

Entities

Son objetos que tienen identidad propia en el sistema y donde sus atributos o propiedades no identifican quién es. Y la mejor manera de explicar esto es con un ejemplo:

Imaginemos un sistema de juego donde, para participar, necesitas ser un usuario. Ese usuario se identifica por un código único, es decir, una clave primaria (mail, nick….), no se identifica por su nombre, apellidos, fecha nacimiento o gustos deportivos (por poner un ejemplo).

Además, cabe destacar que esto puede ser muy claro en nuestro modelo de dominio, pero en otro contexto esto puede cambiar. Así que, en resumen, podemos decir que una Entidad dentro DDD es según describe Eric Evans:

Una Entidad es algo que tiene continuidad a través de un ciclo de vida y es distinguible independientemente de los atributos que son importantes para la aplicación.

Value Objects

En este caso, al contrario que las Entidades, los Value Objects (o lo que es lo mismo un Data Access Object (DTO) de toda la vida) no tienen identidad ninguna, solo nos interesan sus atributos, ya que complementan la descripción del dominio, pero no se identifican por sí mismos. Al igual que en el caso anterior, y siguiendo con el mismo contexto:

El Usuario puede tener unos datos de contacto (dirección, número teléfono etc). Podríamos tener un Value Object llamando ContactInfo.

Normalmente, y en particular Eric Evans, especifica que dichos objetos deberían ser por lo general inmutables, aunque eso no quiere decir que sea una regla fija. El hecho de que se prefiera como inmutables nos da la ventaja de que podemos usarlos y descartarlos cuando lo necesitemos, ya que no necesitamos preocuparnos por la instancia que se está utilizando.

Servicios de dominio

La base de los servicios en DDD son todos aquellos comportamientos que debemos tener en nuestra aplicación y que no pertenezcan a ninguna entidad, es decir, son operaciones, funciones, métodos, no “cosas”. No tienen estado, y modifican una o varias entidades de dominio, pero que no son propias de la entidad (cargar dinero en la cuenta de un Usuario).

Normalmente estos servicios suelen estar nombrados como verbos, ya que deben darnos una interfaz clara de lo que nos permiten hacer. Tanto sus parámetros de entrada como su objetivo es tratar los objetos de dominio. Por ejemplo, y siguiendo los casos anteriores, una función o servicio de dominio para nuestra entidad Usuario sería el alta o modificación de los datos de dicho usuario.

Estos servicios hay que diferenciarlos de los servicios de aplicación o de infraestructura, ya que estos tratan única y exclusivamente con objetos del modelo.

¿Y qué ocurre cuando tenemos que trabajar con los servicios donde tratamos dos o más dominios diferentes? Aquí entran en juego conceptos de diseño que, a mí personalmente, me resultaron muy útiles, y es el uso de las ACL (capa anticorrupción).

Se suelen utilizar patrones “adapter” o “facade”, para que dos o más dominio se integren entre sí, o para interacciones con terceros (servicios que nos proveen de datos complementarios, por ejemplo).

Por ejemplo, para que un Usuario interactúe con un juego deberíamos crear una ACL que ponga en común ambos dominios (de nuevo lenguaje ubicuo).

Módulos

Cuando un aplicación comienza a ser compleja debemos mantener la máxima de alta cohesión y bajo acoplamiento. Así que para ello, lo que hacemos es agrupar conceptos dentro del modelo de dominio.

Debemos tener muy claros los conceptos, ya que esta división conlleva un gran nivel de abstracción. Traducido a nuestro lenguaje de desarrollador, sería separar los conceptos funcionales en paquetes o subproyectos.

Agregaciones

Los agregados son grupos de entidades que se relacionan entre sí donde se define la dependencia entre ellas. En dichos agregados hay que definir cuál es la Entidad padre (root) y cuál es la frontera, es decir, definir qué está dentro del agregado y qué no (lazy).

Por ejemplo, para un Usuario tenemos un ContactInfo, pero dicho ContactInfo no tiene sentido fuera de la entidad Usuario. Es decir, cuando actualizamos un ContactInfo no actualizamos el ContactInfo en sí, sino que actualizamos el Usuario. Por tanto, en este caso, tanto el Usuario como la ContactInfo forman parte de una misma agregación.

Factorías

Es un patrón usado para encapsular la complejidad de construcción o creación de agregaciones.

Son un recurso para disminuir la complejidad a la hora de esta construcción, de tal manera que podamos hacerlo de forma atómica, y que cuando creamos el objeto raíz también se creen sus objetos “agregados”. Por ejemplo, podríamos tener un factory para que cuando creemos el Usuario también agreguemos a la información del objeto su ContactInfo.

De esta manera, delegamos la construcción de estos agregados en componentes con responsabilidad única (cumplimos uno de los principios SOLID).

Repositorios

Los repositorios son elementos que se encargan de persistir y recuperar los objetos de dominio que necesitamos que pervivan en el tiempo. De esta manera aislamos el dominio de la persistencia y de los distintos elementos necesarios para manejar las transacciones e integridad (aplicación e infraestructura).

Normalmente, se crea un repositorio por Entidad raíz de una Agregación.

Arquitectura de capas

Todo lo que hemos visto hasta ahora hay que concretarlo en una arquitectura que nos permita desacoplar el dominio de otras funciones requeridas en nuestro sistema.

Dentro de la bibliografía, leyendo a autores como Martin Fowler, Eric Evans o Vaughn Vernon, se habla de distintas divisiones en capas, incluso se habla de arquitecturas hexagonales, pero al final, el resultado de este desacoplamiento es un sistema dividido en 4 grandes capas:

  1. Presentación. Se encarga de la presentación de la información y de interpretar las acciones que realiza el usuario.
  2. Aplicación. Esta capa es la que se encarga de crear servicios, instanciar componentes necesarios para interactuar con el dominio, es decir, es el orquestador de todo lo necesario para delegar las acciones de negocio al propio dominio. Esta capa no tiene conocimiento del modelado ni tiene reglas de negocio
  3. Dominio. Es la capa donde reside el dominio y las reglas de negocio.
  4. Infraestructura. Provee todo lo necesario para dar una solución tecnológica a nuestro sistema. Es decir, nos provee, por ejemplo, del sistema de persistencia (Hibernate), Oauth, librerías, pasarelas de pago, etc.

¿Cómo aplicar esto en un sistema real ? Desde mi experiencia, en el único proyecto que he trabajado con este paradigma (proyecto donde, por cierto, también trabajé con TDD y BDD durante dos años), el proceso de modelado del dominio y arquitectura nos llevó bastantes horas de reuniones, discusiones, refactors, marchas atrás, etc.

Como hemos visto no es fácil tener claros los conceptos, al igual que no es fácil modelar, ya que entra en juego muchos factores y perfiles dentro de un mismo proyecto, además de las múltiples interpretaciones y “vueltas de tuerca” que se puede dar a este paradigma para que se adapte lo máximo posible a tus necesidades.

Pero hablando a nivel de arquitectura, nosotros decidimos tener una arquitectura dividida en capas, con servicios REST, Spring, JPA… y salió algo como esto:

  • Core. Era el núcleo de la aplicación, donde se definía el dominio, entidades, value objects, repositorios, etc.
  • Servicios. Era la capa donde se definía el API con 4-5 capas claramente diferenciadas para cumplir entre cosas con los principios SOLID:
    • Facade. Era la entrada/salida del servicio. Lo único que hacía esta capa era recoger los parámetros de entrada y, con dichos parámetros, llamar a un componente @Service.
    • Service. Capa que validaba si los parámetros de entrada eran correctos, es decir, comprobaba lo típico que se comprueba cuando hay una entrada en un servicio: que no haya valores nulos, que si los hay sean controlados, etc.
    • Component. Capa que actuaba como orquestador y donde hubo muchos debates. En principio pensamos en no meter lógica de negocio, ya que se quería delegar todo en las entidades, pero se vio que, debido a los requerimientos en esta capa, había que tener cierta lógica. Al final, esta capa obtenía los objetos de dominio (agregaciones) y los modificaba (un CRUD).
    • Repositorio. Hibernate con JPA.
    • ACL. Cuando tenían que interactuar dos dominios heterogéneos entre sí, se ponía una capa intermedia con un patrón adapter para que ambos dominios hablaran el mismo lenguaje.
  • Infraestructura. Nos proveía de todo lo necesario para tener persistencia, Single sign-on, auditorías, pasarelas de pago, etc.

Conclusiones

En mi opinión, trabajar con DDD no es nada sencillo. Es una afirmación bastante subjetiva porque depende mucho del contexto y de lo que defina negocio, por tanto lo que se da en la bibliografía son pautas que puedes ir adaptando en función de las necesidades de tu proyecto.

Las ventajas que nos va a ofrecer es que su aplicación a proyectos de larga vida y cambios constantes, nos va a ofrecer un modelado constante, iterativo, justificado, consistente y robusto. Además, nos da la oportunidad de que todos los miembros de un proyecto estén alineados, hablando de los mismos conceptos.

Si además, en un proyecto de estas características, aplicamos técnicas de código limpio, TDD, BDD, con las ventajas que nos aportan a nivel de codificación, pruebas y conocimiento de negocio, etc… tu proyecto “Crece fuerte”, y no solo fuerte, también seguro, robusto, escalable…

Actualmente DDD está dando paso a lo que ahora se denomina sistemas Data Centric (al menos, es lo que estoy viendo últimamente), donde el Dato es el centro de todo, al igual que en DDD el dominio es el núcleo. Depende de cómo se interactúe con dichos datos y de quién sea el Gobierno del Dato.

Lo negativo, por decirlo de alguna manera, quizás son las horas y horas de múltiples reuniones, revisiones, rediseños… que conlleva tener un dominio consistente y bien modelado.

Espero que mi experiencia os sirva de ayuda. Y recordad… ¡crece fuerte!

Ingeniero Informático con 11 años de experiencia en el desarrollo de aplicaciones web en entornos J2EE. Después de 8 años en Indra, donde trabajó en proyectos para el Ministerio de Educación, DGT (Dirección General de Tráfico), RFEF (Real Federación Española de Fútbol) y SELAE (Loterías y Apuestas del Estado), vio en Paradigma una oportunidad de seguir creciendo. Con gran experiencia en frameworks Spring (Spring 4, Spring Boot, Spring Webflow, Spring Data, etc.), actualmente inmerso en proyectos de eCommerce con ATG 10.2 para El Corte Inglés.

Ver toda la actividad de Raúl Martínez