Diseño orientado al dominio
Una aplicación puede estar perfectamente programada y, sin embargo, representar mal el negocio para el que fue construida. Puede tener una arquitectura ordenada, pruebas automatizadas, una base de datos bien diseñada y un código limpio, pero utilizar conceptos que no coinciden con la realidad que sus usuarios necesitan gestionar. Cuando esto ocurre, el problema no suele encontrarse en una función concreta, sino en algo más profundo: el software y el negocio están utilizando modelos mentales diferentes para describir la misma realidad.
Esta tensión es especialmente visible en sistemas empresariales. Una aplicación para gestionar estudios clínicos, por ejemplo, puede hablar de centros, promotores, estudios, contratos o intermediaciones. Pero que esas palabras aparezcan en las tablas de una base de datos o en las clases de una aplicación no significa necesariamente que el sistema haya comprendido qué representan. ¿Qué hace que algo sea realmente un centro? ¿Puede cambiar su identidad? ¿Qué información forma parte del centro y cuál pertenece a una relación concreta con un estudio? ¿Quién puede modificarla? ¿Qué reglas deben cumplirse cuando cambia su estado?
El diseño orientado al dominio, conocido habitualmente como Domain-Driven Design o DDD, parte precisamente de esta preocupación. Su propuesta fundamental no consiste en utilizar determinadas clases, patrones o estructuras de carpetas, sino en hacer que el diseño del software nazca de una comprensión profunda del dominio empresarial que el sistema intenta representar.
Del código al problema que queremos resolver
Cuando comenzamos un proyecto de software es tentador empezar pensando en tecnología. ¿Qué lenguaje utilizaremos? ¿Qué framework? ¿Qué base de datos? ¿Será una API REST? ¿Utilizaremos microservicios? Son preguntas legítimas, pero existe una pregunta anterior que puede resultar mucho más importante: ¿cómo funciona realmente el negocio?
Imaginemos una empresa que necesita gestionar la relación entre estudios clínicos y centros en los que se realizan determinadas actividades. Desde una perspectiva puramente técnica podríamos empezar diseñando tablas llamadas Study, Center y Contract, crear algunas relaciones entre ellas y posteriormente construir una API.
El problema es que crear estructuras de software a partir de nombres del negocio no significa necesariamente que hayamos modelado el negocio. El verdadero trabajo comienza cuando intentamos comprender qué significan esos conceptos, qué reglas los relacionan y qué comportamientos debe preservar el sistema.
Por ejemplo, un centro puede estar activo o inactivo. Un centro puede participar en determinados estudios, pero no necesariamente en todos. Una relación entre un centro y un estudio puede tener su propio estado y unas fechas determinadas. Un contrato puede condicionar qué operaciones son posibles. El significado de cada concepto aparece cuando empezamos a comprender las reglas que lo rodean.
DDD propone precisamente dedicar una atención especial a esa comprensión. El software deja de ser una traducción directa de tablas o pantallas y pasa a convertirse en una representación explícita de los conceptos y reglas importantes del negocio.
Un lenguaje común para entender el dominio
Para poder construir ese modelo necesitamos primero hablar sobre el problema. Y aquí aparece una de las ideas más importantes de DDD: el lenguaje ubicuo (ubiquitous language).
En cualquier organización existen palabras que tienen un significado concreto para las personas que trabajan en ella. El problema aparece cuando los desarrolladores utilizan unas palabras, los responsables de negocio otras y el sistema termina utilizando una tercera interpretación.
Si un usuario habla de una “intermediación”, por ejemplo, no deberíamos asumir que sabemos exactamente qué significa. Tendríamos que descubrir qué representa, cuándo comienza, cuándo termina, qué estados puede tener y qué condiciones determinan que una intermediación sea válida.
El lenguaje utilizado por el negocio debería convertirse progresivamente en parte del lenguaje utilizado para diseñar y construir el software.
Esto tiene una consecuencia práctica importante. Si durante una conversación con los expertos del dominio aparece una palabra que describe algo importante, esa palabra puede terminar apareciendo en el código. Una clase, un método o una operación pueden utilizar el mismo concepto que utilizan los usuarios.
No se trata de introducir términos empresariales en el código de forma artificial. Se trata de evitar que el código tenga que traducir constantemente entre dos modelos diferentes de la realidad.
El dominio no es necesariamente uno solo
A medida que profundizamos en un negocio aparece otro problema. Una misma palabra puede significar cosas diferentes dependiendo del contexto en el que se utilice.
Pensemos, por ejemplo, en una empresa que trabaja con clientes. Para el departamento comercial, un cliente puede ser una organización con determinados datos fiscales y comerciales. Para facturación, ese mismo cliente puede tener condiciones de pago, facturas y saldos. Para soporte, puede representar una cuenta con incidencias, contactos y acuerdos de servicio.
Todos están hablando de un “cliente”, pero no necesariamente están hablando del mismo concepto.
Aquí aparece el concepto de contexto delimitado, o bounded context. Un contexto delimitado establece el límite dentro del cual un determinado modelo del dominio tiene un significado coherente.
Esto significa que no siempre debemos intentar construir un único modelo empresarial universal que represente toda la organización. Puede ser más útil reconocer que diferentes partes del negocio necesitan modelos diferentes.
Un cliente en el contexto comercial puede no tener exactamente las mismas propiedades, reglas o responsabilidades que un cliente en el contexto de facturación. Aunque ambos conceptos puedan estar relacionados, cada contexto puede tener su propio modelo y su propio lenguaje.
Esta idea resulta especialmente importante cuando los sistemas crecen. DDD no intenta eliminar la complejidad del negocio haciendo que todo comparta el mismo modelo, sino controlar esa complejidad estableciendo límites dentro de los cuales cada modelo puede mantenerse coherente.
Modelar significa decidir qué importa
Una vez delimitado un contexto podemos empezar a construir un modelo. Pero modelar no significa intentar representar absolutamente todo lo que existe en el mundo real.
Un modelo es una simplificación. Selecciona determinados aspectos de la realidad porque son relevantes para el problema que queremos resolver.
Si estamos construyendo un sistema para gestionar centros de estudios clínicos, probablemente no necesitemos representar todo lo que sabemos sobre un centro. Quizá nos interesen su identificador, nombre, ubicación, estado y determinadas capacidades. En otro sistema podrían ser relevantes otros aspectos completamente diferentes.
El modelo de dominio no pretende ser una copia completa de la realidad, sino una representación de aquella parte de la realidad que importa para el problema que estamos resolviendo.
Esta distinción ayuda a entender por qué DDD no consiste simplemente en convertir cada sustantivo del negocio en una clase. Una clase Center no constituye por sí misma un modelo de dominio. El modelo aparece cuando esa estructura expresa conceptos, relaciones, comportamientos y reglas que realmente tienen significado dentro del negocio.
Entidades: objetos cuya identidad importa
Algunos conceptos del dominio necesitan mantener una identidad a lo largo del tiempo. Para ellos DDD utiliza el concepto de entidad.
Imaginemos un centro identificado por C123. Podemos modificar su nombre, dirección o estado, pero seguimos hablando del mismo centro. Sus atributos han cambiado; su identidad permanece.
Esto es lo que diferencia a una entidad de un simple conjunto de datos. Una entidad se reconoce principalmente por quién es, no únicamente por los valores que contiene en un momento determinado.
Esta característica tiene consecuencias en el diseño. Si cambiamos el nombre de un centro, no queremos que el sistema interprete que hemos eliminado un centro y creado otro. La identidad permite comprender que se trata del mismo elemento del dominio a lo largo de su ciclo de vida.
Las entidades, además, suelen tener comportamiento. Si un centro no puede pasar directamente de “inactivo” a determinados estados sin cumplir ciertas condiciones, esa regla pertenece al dominio y puede formar parte del comportamiento de la entidad o de otros elementos del modelo.
La cuestión importante es evitar convertir una entidad en una simple estructura de datos con getters y setters. Una entidad de dominio resulta valiosa cuando ayuda a proteger las reglas que determinan qué estados y comportamientos son válidos.
Objetos de valor: cuando importa el significado, no la identidad
No todo concepto necesita una identidad propia. Hay valores cuyo significado depende exclusivamente de sus propiedades.
Pensemos en una dirección, una cantidad monetaria o un intervalo de fechas. Si dos direcciones contienen exactamente los mismos datos relevantes, normalmente no necesitamos distinguirlas porque sean “la misma dirección” con un identificador independiente.
Estos conceptos pueden representarse mediante objetos de valor, o value objects.
Un objeto de valor se define por los valores que contiene y no por una identidad propia. Una dirección podría estar formada por calle, número, código postal y ciudad. Una cantidad monetaria podría combinar importe y moneda.
Esta distinción parece sencilla, pero puede cambiar considerablemente la calidad de un modelo. En lugar de trabajar con cadenas y números sin significado, podemos representar explícitamente conceptos del dominio.
Por ejemplo, una propiedad llamada PostalCode como simple string permite almacenar cualquier texto. Un objeto de valor CodigoPostal puede encapsular las reglas que determinan qué constituye un código postal válido.
Los objetos de valor permiten que conceptos aparentemente simples adquieran significado dentro del modelo y concentren las reglas que les corresponden.
Además, suelen ser inmutables: en lugar de modificar internamente un objeto de valor, se crea otro que representa el nuevo valor. Esta característica facilita razonar sobre ellos y reduce determinados problemas derivados de compartir estados mutables.
Agregados: establecer límites de consistencia
A medida que nuestro modelo crece aparece una nueva pregunta: si tenemos muchas entidades relacionadas, ¿debemos tratarlas todas como una única unidad?
Supongamos que un estudio tiene varios centros asociados y que cada relación entre estudio y centro posee información y reglas propias. Podríamos crear un modelo en el que todas las entidades estén directamente conectadas entre sí. Pero cuanto más grande sea esa red, más difícil será determinar qué podemos modificar conjuntamente y qué reglas deben cumplirse en cada operación.
Aquí aparece el concepto de agregado, uno de los elementos más importantes del diseño táctico de DDD.
Un agregado es un conjunto de objetos del dominio que se trata como una unidad a efectos de determinadas reglas de consistencia. Dentro de él existe una raíz del agregado (aggregate root) que actúa como punto de entrada para acceder a los elementos que forman parte de esa unidad.
La idea fundamental no es simplemente agrupar clases. Un agregado establece un límite dentro del cual determinadas invariantes del negocio deben mantenerse consistentes.
Imaginemos que un Estudio es la raíz de un agregado que contiene determinada información que debe mantenerse coherente con sus relaciones internas. En lugar de permitir que cualquier parte del sistema modifique directamente cualquiera de sus objetos internos, las operaciones pasan por la raíz del agregado, que puede comprobar las reglas correspondientes.
Esto también ayuda a limitar las transacciones y el acoplamiento. No todo el modelo tiene que cargarse y modificarse como una única estructura gigante.
Un error habitual consiste en interpretar los agregados como simples agrupaciones jerárquicas. Si una entidad contiene otras entidades, eso no significa automáticamente que todas formen un agregado. El límite del agregado debería surgir de las reglas de consistencia que el negocio necesita proteger, no de cómo nos resulte cómodo organizar las clases.
Los intermediarios: coordinar sin convertir todo en una entidad
No todas las operaciones del dominio encajan naturalmente dentro de una entidad o de un objeto de valor.
Imaginemos una operación que necesita coordinar varios conceptos del dominio pero que no representa una propiedad o comportamiento natural de una entidad concreta. Si intentamos introducirla dentro de una entidad únicamente para tener un lugar donde colocarla, podemos terminar creando objetos con responsabilidades que realmente no les pertenecen.
Aquí aparecen los servicios de dominio (domain services), que podemos entender como intermediarios que encapsulan una operación o regla del dominio cuando su responsabilidad no pertenece naturalmente a una única entidad u objeto de valor.
Por ejemplo, podríamos tener una regla que determina si un centro puede ser asociado a un determinado estudio teniendo en cuenta información procedente de varios conceptos. Esa operación podría no pertenecer naturalmente ni al centro ni al estudio.
Un servicio de dominio permite expresar esa regla sin atribuírsela artificialmente a uno de los objetos.
Esto no significa que cualquier lógica de negocio deba convertirse en un servicio. Si una regla pertenece claramente a una entidad, normalmente resulta más coherente mantenerla dentro de ella.
Los servicios de dominio deberían utilizarse cuando existe una operación significativa para el dominio que no encaja naturalmente dentro de una única entidad o un objeto de valor.
La misma idea de intermediación aparece también en otras partes de una arquitectura. Los servicios de aplicación pueden coordinar casos de uso, los repositorios pueden mediar entre el dominio y la persistencia y otros componentes pueden conectar el modelo con sistemas externos. Pero sus responsabilidades no son intercambiables: uno de los objetivos de DDD es mantener claras las fronteras entre el dominio y los mecanismos que lo rodean.
Cómo encajan todas las piezas
Llegados a este punto podemos ver que los conceptos de DDD no son una colección independiente de patrones.
Primero intentamos comprender el negocio y construir un lenguaje común. Después descubrimos que ese lenguaje puede tener diferentes significados según el contexto y establecemos contextos delimitados. Dentro de cada contexto construimos un modelo que representa los conceptos relevantes.
Algunos de esos conceptos necesitan identidad y se convierten en entidades. Otros se definen únicamente por sus valores y pueden representarse como objetos de valor. Cuando varias entidades y objetos necesitan formar una unidad coherente de consistencia, establecemos un agregado. Y cuando una operación de dominio no pertenece naturalmente a una entidad concreta, podemos expresarla mediante un servicio de dominio.
La verdadera fuerza de estos conceptos aparece cuando se utilizan conjuntamente para construir un modelo coherente del negocio, no cuando se aplican como una colección de patrones de programación.
Podemos imaginarlo como diferentes niveles de decisión. El contexto delimitado establece dónde tiene sentido nuestro modelo. Las entidades y objetos de valor representan los conceptos que contiene. Los agregados determinan determinadas fronteras de consistencia. Los servicios permiten expresar operaciones que atraviesan varios conceptos cuando no pertenecen naturalmente a uno solo.
El resultado debería ser un software en el que leer el código permita reconocer, al menos parcialmente, cómo funciona el negocio.
DDD no significa crear muchas clases
Existe una tentación cuando se descubre DDD: transformar inmediatamente cada concepto empresarial en una clase, añadir entidades, objetos de valor, agregados y servicios, y considerar que ya estamos aplicando diseño orientado al dominio.
Pero eso puede producir exactamente el problema contrario al que DDD intenta solucionar. Podemos terminar con un modelo excesivamente complejo, lleno de abstracciones que no aportan significado real.
DDD no consiste en utilizar muchas clases ni en reproducir literalmente todos sus patrones tácticos. La cuestión fundamental es que las estructuras del software representen de forma útil las reglas y conceptos que realmente importan en el dominio.
Un sistema puede tener muchas entidades y, sin embargo, tener un modelo de dominio pobre. También puede tener un modelo relativamente sencillo y representar extraordinariamente bien un problema concreto.
La complejidad debe aparecer cuando el negocio es complejo, no porque la técnica utilizada para modelarlo lo sea.
DDD y la arquitectura del sistema
DDD tampoco determina por sí solo una arquitectura completa. Un sistema diseñado siguiendo DDD puede utilizar una aplicación monolítica, una arquitectura distribuida o diferentes estilos de integración.
Lo que sí aporta es una forma de decidir dónde deben situarse determinadas responsabilidades y qué partes del sistema pertenecen realmente al dominio.
Esto resulta especialmente interesante cuando se combina con arquitecturas que buscan aislar la lógica empresarial de los mecanismos externos. El dominio puede expresar sus reglas sin depender directamente de una base de datos concreta, de un framework web o de un proveedor externo.
La arquitectura deja entonces de ser únicamente una cuestión de organización técnica y empieza a reflejar también las fronteras conceptuales del negocio.
En sistemas grandes, los contextos delimitados pueden incluso convertirse en una herramienta para decidir cómo dividir aplicaciones o servicios. Pero aquí también conviene evitar una asociación automática: un contexto delimitado no equivale necesariamente a un microservicio, porque el límite conceptual del dominio y el límite físico de un despliegue son decisiones diferentes.
El verdadero trabajo está fuera del código
Quizá la consecuencia más importante de DDD sea que desplaza parte del trabajo esencial del ingeniero de software fuera del editor de código.
Comprender un dominio requiere hablar con las personas que conocen el negocio, observar cómo trabajan, cuestionar términos ambiguos, descubrir excepciones y comprender qué reglas utilizan realmente para tomar decisiones.
Esto puede resultar incómodo para quien está acostumbrado a pensar que desarrollar software consiste principalmente en implementar requisitos ya definidos. En dominios complejos, los requisitos escritos rara vez contienen todo el conocimiento necesario.
El modelo de dominio se construye mediante un proceso de aprendizaje compartido entre las personas que conocen el negocio y las personas que construyen el software.
Por eso DDD tiene una dimensión técnica y otra de comunicación. El código es importante, pero también lo son las conversaciones que permiten descubrir qué debe representar ese código.
Cuándo aporta valor y cuándo puede resultar excesivo
DDD resulta especialmente interesante cuando el dominio contiene reglas complejas, conceptos que evolucionan, diferentes áreas de negocio con significados distintos o decisiones empresariales que deben quedar reflejadas con precisión en el software.
No todos los sistemas tienen ese nivel de complejidad. Una aplicación sencilla de gestión de información puede no necesitar un modelo de dominio sofisticado. Introducir agregados, servicios y múltiples abstracciones donde no existe una complejidad real puede hacer que el sistema sea más difícil de entender.
Por eso el objetivo no debería ser “aplicar DDD” como una obligación metodológica. El objetivo debería ser utilizar las herramientas de DDD cuando ayudan a gestionar la complejidad real del dominio.
Esta perspectiva también evita otro error frecuente: confundir DDD con una arquitectura determinada. DDD es, ante todo, una forma de abordar el diseño del software a partir del conocimiento del dominio. Los patrones y las técnicas son herramientas para conseguirlo, no el objetivo final.
Una forma diferente de pensar el software
La principal aportación de DDD no es un nuevo tipo de clase ni una estructura concreta de proyecto. Es un cambio de perspectiva.
En lugar de comenzar preguntando qué tablas necesitamos, qué endpoints debemos crear o qué clases debemos implementar, comenzamos preguntando cómo funciona el negocio, qué conceptos utiliza, qué reglas lo gobiernan y dónde aparecen diferentes interpretaciones de una misma realidad.
A partir de ahí, el software puede empezar a adquirir una estructura que refleje ese conocimiento.
Los contextos delimitados nos ayudan a controlar dónde tiene sentido cada modelo. Las entidades representan conceptos cuya identidad importa. Los objetos de valor permiten modelar valores con significado propio. Los agregados establecen fronteras de consistencia y los servicios de dominio permiten expresar operaciones que no pertenecen naturalmente a una única entidad.
Pero todos ellos son piezas de una idea más amplia. Diseñar orientado al dominio significa intentar que el software y el conocimiento del negocio evolucionen juntos, de manera que el modelo utilizado por el sistema sea una expresión útil de cómo funciona realmente el problema que queremos resolver.
Al final, DDD no consiste en hacer que el código se parezca más al negocio simplemente porque utiliza sus palabras. Consiste en conseguir algo más profundo: que las decisiones importantes del software estén fundamentadas en una comprensión explícita del dominio y que esa comprensión pueda mantenerse, discutirse y evolucionar junto con el propio sistema.