Artículo facilitado por Caixa Bank Tech
Este artículo explora los principios fundamentales de Domain-Driven Design (DDD) y sus patrones estratégicos clave, como el lenguaje ubicuo y los contextos delimitados, para ayudar a alinear las aplicaciones con las necesidades del negocio, mejorar la comunicación entre equipos y gestionar la complejidad en sistemas de software.
Históricamente, el desarrollo de aplicaciones se había pensado como una actividad separada del negocio, en la que había una toma de requerimientos inicial para conocer necesidades y la entrega de un producto final que no siempre cumplía con las expectativas previstas, ya que con esta forma de trabajar (en cascada) había poca o ninguna flexibilidad para introducir cambios durante el proyecto.
Ya en 2001, con el Agile Manisfesto se empezó a desarrollar pensando en realizar los desarrollos abrazando el cambio. Pero aún había un impedimento a salvar: la diferencia entre el negocio (entendido como las entidades, sus relaciones entre sí, las operaciones que debían hacerse sobre ellas, etc.) y la forma en que este se veía reflejado en la aplicación construida.
Fue a finales del año 2003 cuando apareció el libro Domain-Driven Design: Tackling Complexity in the Heart of Software escrito por Eric Evans, que ofrecía una forma distinta de hacer las cosas. El llamado “libro azul del DDD” no era un libro práctico sobre patrones a aplicar, sino que describía cómo conseguir que la aplicación modelase correctamente el negocio y ofrecía un conjunto de estrategias para conseguir ese objetivo.
El DDD (siglas de Dominio orientado al Dominio en inglés) tiene especial sentido cuando afrontamos el desarrollo de aplicaciones sobre dominios de conocimiento complejos. No es siempre la mejor opción, ya que en dominios relativamente sencillos existen aproximaciones más simples que ofrecen un equilibrio entre la complejidad y el coste de construcción. En cambio, cuando el dominio es complejo, hacer un acercamiento de este tipo permite conseguir una mayor estabilidad de la aplicación a los cambios que aparecen de forma natural al evolucionar el negocio.
A continuación, te damos algunas pautas para aplicar el DDD.
Utilizar un lenguaje ubicuo
En muchas ocasiones, nos encontramos que una aplicación tiene unas tablas, un modelo de datos y unos nombres que, al cabo de unos años, nadie entiende del todo lo que significan; lo que complica el entendimiento entre todas las partes implicadas.
Precisamente, eso es lo que intenta evitar el uso de un lenguaje ubicuo (ubiquitous language en inglés).
El lenguaje ubicuo es un lenguaje común entre todos los equipos involucrados en un proyecto y tiene como objetivo evitar los malentendidos y fomentar la colaboración entre desarrolladores y expertos del dominio.
Un lenguaje ubicuo tiene que:
Con este enfoque, conseguimos que haya una forma de describir los problemas del dominio y que la solución propuesta a los mismos sea comprensible por todos los equipos. Y lo mismo sucede con el código. Los desarrolladores van a ser capaces de hablar con negocio en unos términos comunes.
Pongamos un ejemplo esclarecedor:
Tomando como base este ejemplo, los expertos del dominio ahora podrían pedir un listado mensual con las cuentas en las que no ha habido actividad durante el último mes y, por su parte, el equipo de desarrollo podrá proponer seleccionar las CuentaBancaria’s en que la FechaValor sea anterior al mes en curso.
De esta forma, lo que se pide y lo que se implementa estará perfectamente alineado y todo el mundo entenderá lo que se va a hacer y cómo funciona.
En general el lenguaje ubicuo:
Contexto delimitado
Mediante la división de nuestra aplicación en distintos contextos conseguimos dividir un sistema complejo en piezas más autónomas y más fácilmente mantenibles.
La idea es que cada contexto tenga su propio lenguaje ubicuo y se modele el negocio para resolver una parte específica del problema global. En esa pequeña parcela conseguimos que el dominio tenga un significado específico, que sea coherente y consistente con las reglas de negocio que pueda haber en esa parte del dominio.
A esos distintos contextos los llamamos contextos delimitados (o bounded contexts en inglés).
Una de las fortalezas más importantes de los contextos acotados es que tienen unos límites bien definidos que permiten aislarse del resto de la aplicación y que sus cambios no afecten al resto.
Asimismo, existen patrones con los que favorecer la colaboración entre distintos contextos acotados manteniendo el aislamiento entre ellos (uso de APIs, uso de eventos, uso de capas de anticorrupción, etc.).
Un ejemplo: dentro de una entidad bancaria puede existir el contexto de las cuentas bancarias y el contexto de los préstamos y ambos tienen que poder evolucionar de forma independiente a pesar de poder tener relación.
Además, el concepto de contexto delimitado se puede explotar tanto como sea necesario para reducir la complejidad haciendo que dentro de un mismo contexto haya subcontextos más pequeños que igualmente están cohesionados y en los que se puede implementar una funcionalidad independientemente del resto del contexto.
A nivel práctico, para identificar un contexto delimitado dentro de nuestro dominio tenemos que buscar áreas en que se utilice un conjunto único de términos (el lenguaje ubicuo en ese contexto) o reglas únicas, áreas que tengan unos requisitos funcionales muy diferenciados del resto o áreas que representen una separación natural del flujo de trabajo o los procesos de negocio.
Los contextos delimitados son de vital importancia para impedir que nuestro sistema se convierta en un monolito al dividir de forma clara los distintos modelos que existen dentro del negocio y permitir posibilidades técnicas como el uso de arquitecturas distribuidas.
Mapa de contextos
Como hemos comentado al separar nuestro sistema en distintos contextos delimitados podemos terminar con muchos contextos pequeños y necesitamos tener clara la forma en que van a interactuar entre sí.
Para ello usamos el mapa de contextos (o context map en inglés) que es una representación visual de los contextos delimitados y como interaccionan ya sea de forma jerárquica o mediante colaboración.
Si existe una relación jerárquica entre contextos delimitados, el contexto dominante impone sus términos y los contextos subordinados suelen adaptarse.
En este tipo de relación los patrones de integración más comunes, en este caso, son el patrón Conformista, el patrón Capa de Anticorrupción, el patrón Núcleo Compartido o el patrón Cliente-Proveedor.
Hay ocasiones en que los contextos delimitados colaboran entre sí en plena igualdad de condiciones entonces se tienen que usar otros patrones. Estos son el patrón Asociación, el patrón Núcleo Compartido (en efecto, este patrón se puede usar tanto en interacción jerárquica como en interacción colaborativa), el patrón Lenguaje Publicado y el patrón Vías Separadas.
Como escoger entre este conjunto de patrones forma parte del arte de hacer un diseño guiado por el dominio, pero aquí tienes unas recomendaciones:
Para realizar un mapa de contextos se suelen usar rectángulos o cajas para los contextos y flechas para indicar las relaciones entre ellos. Estas flechas pueden ser unidireccionales si la relación es jerárquica y bidireccionales si la relación es colaborativa. En estas flechas se suele indicar el patrón que queremos aplicar (Conformista, Asociación, etc.). También es común, si se tiene claro en el momento de realizar el mapa, el mecanismo que se utiliza para esa comunicación (API, evento, integración en código, etc.).
A continuación, describiremos brevemente los principales patrones a modo informativo.
> Patrón Conformista
Este patrón (Conformist en inglés) es jerárquico y consiste en que el contexto subordinado acepta completamente el modelo y las reglas del contexto dominante. No intenta modificarlo en absoluto o ni siquiera adaptarse para mantener un modelo propio. La ventaja que ofrece es que es de fácil integración, pero reduce la autonomía del contexto y puede estar afectado por un cambio del contexto dominante.
> Patrón Capa de Anticorrupción
Este patrón (Anticorruption Layer en inglés) es jerárquico y consiste en proteger el modelo propio respecto al modelo del contexto dominante usando una capa de traducción que actúa como mediador. La ventaja es que protege al contexto de los cambios que se puedan producir en el contexto dominante y permite mantener un modelo independiente, pero requiere mayor esfuerzo de implementación y puede introducir problemas de rendimiento por tener que introducir la capa de traducción.
> Patrón Núcleo Compartido
Este patrón (Shared Kernel en inglés) se puede considerar jerárquico o colaborativo. Se fundamente en tener una parte del modelo que se comparte entre ambos contextos, pero poder evolucionar independientemente en el resto del modelo. Su ventaja es que reduce la duplicación de los conceptos comunes y permite consistencia de los datos compartidos, pero los cambios en el núcleo compartido impactan a los dos dominios y requiere una alta colaboración entre los equipos de ambos dominios.
> Patrón Cliente-Proveedor
Este patrón (Customer-Supplier en inglés) es jerárquico y hace que el contexto subordinado actúe como cliente y el contexto dominante actúe como proveedor. En este sentido, el subordinado influye en las decisiones del dominante ya que consume datos o funcionalidades adaptadas a sus necesidades. Su ventaja es que facilita la colaboración y está bien adaptada a las necesidades del subordinado, pero requiere una coordinación continua y activa y si el dominante tiene demasiados subordinados puede generar trabajo adicional.
> Patrón Asociación
Este patrón (Partnership en inglés) es colaborativo y conveniente cuando ambos contextos trabajan estrechamente como socios igualitarios para lograr un objetivo común. La coordinación entre ambos contextos es continua para garantizar la compatibilidad mutua. Su ventaja es que permite una evolución coordinada de ambos contextos y la integración es la justa y necesaria, la desventaja es, como hemos indicado, la coordinación continua y que si ambos equipos no tienen el mismo ritmo de trabajo puede producir problemas.
> Patrón Lenguaje Publicado
Este patrón (Published Language en inglés) es colaborativo y consiste en establecer un protocolo o forma de interaccionar para publicar su modelo de forma clara y consistente a otros contextos. Permite fácilmente relaciones con diversos contextos y no solo uno. El ejemplo técnico de este patrón es la publicación de APIs con mensajerías claramente definidas mediante OpenAPI. Sus ventajas son una alta claridad en la integración y que permite interaccionar con múltiples contextos, pero puede requerir cambios en los consumidores cuando se cambia el lenguaje publicado y requiere que se mantenga actualizado y documentado.
> Patrón Vías Separadas
Este patrón (Separate Ways en inglés) es colaborativo y es conveniente cuando dos contextos no quieren colaborar de ninguna forma y poder resolver sus problemas de forma independiente. Este patrón suele generar duplicidad de datos o de lógica de negocio, pero fomenta la autonomía y la independencia y reduce el riesgo de que un contexto afecte al otro.
Conclusiones
El Domain-Driven Design ofrece una estrategia para abordar la complejidad de un sistema de aplicaciones.
Los contextos delimitados permiten dividir el dominio en partes manejables y los patrones estratégicos facilitan formas de integrarse claras y reconocibles.
Adoptando estas prácticas se promueve la autonomía de los equipos y la flexibilidad en las aplicaciones.
Pero, sobre todo, fomenta la comunicación clara entre los equipos y que el modelo del dominio evolucione junto con las necesidades del negocio.
22a Festibity
13 de maig de 2025 - Teatre Nacional de Catalunya