· nervico-team · desarrollo-software · 11 min read
Domain-Driven Design: guia practica para equipos de producto
Guia practica de Domain-Driven Design para equipos de producto: conceptos fundamentales, bounded contexts, agregados, y como aplicar DDD sin sobreingeniar tu sistema.
Eric Evans publico “Domain-Driven Design: Tackling Complexity in the Heart of Software” en 2003. Mas de veinte anos despues, DDD sigue siendo uno de los enfoques mas citados y menos aplicados correctamente en la industria del software.
El problema no es que DDD sea dificil. Es que la mayoria de los recursos sobre DDD se centran en patrones tacticos (entidades, value objects, repositorios) sin explicar cuando y por que usarlos. El resultado son equipos que implementan una capa de repositorios innecesaria o que crean agregados sin entender el dominio que estan modelando.
Esta guia toma un enfoque diferente. Empieza por lo estrategico (donde DDD aporta mas valor) y baja a lo tactico (los patrones concretos) solo cuando es necesario. Con ejemplos reales, trade-offs honestos, y un framework para decidir si DDD tiene sentido en tu proyecto.
Que es DDD y que no es
El problema que DDD resuelve
Todo software modela un dominio de negocio. Una aplicacion de e-commerce modela procesos de compra, inventario y logistica. Un sistema bancario modela cuentas, transacciones y regulaciones. Un CRM modela relaciones con clientes y ciclos de venta.
El problema aparece cuando el modelo del software diverge del modelo mental del negocio. Los desarrolladores hablan un idioma, los expertos del negocio hablan otro, y las traducciones entre ambos son imprecisas. Cada imprecision se convierte en un bug, una feature mal entendida, o un sistema que no hace lo que el negocio necesita.
DDD propone resolver esto con un principio: el codigo debe reflejar el dominio del negocio de forma directa y explicita. No es solo organizacion del codigo. Es una forma de pensar sobre como el software se alinea con el negocio.
Lo que DDD no es
DDD no es una arquitectura. No te dice si usar microservicios, monolito, hexagonal o CQRS. DDD es compatible con cualquier arquitectura.
DDD no es un conjunto de patrones. Los patrones tacticos (entidades, value objects, repositorios) son herramientas dentro de DDD, no DDD en si mismo.
DDD no es para todos los proyectos. Si estas construyendo un CRUD simple o un sitio web estatico, DDD es sobreingenieria. DDD aporta valor cuando la complejidad del dominio es el principal desafio tecnico.
DDD no requiere microservicios. Puedes aplicar DDD perfectamente en un monolito. De hecho, para la mayoria de equipos es la forma correcta de empezar.
DDD estrategico: donde esta el valor real
Bounded contexts
El concepto mas importante de DDD y el mas ignorado. Un bounded context es un limite explicito donde un modelo de dominio es consistente y tiene significado claro.
Ejemplo practico: En un sistema de e-commerce, la palabra “producto” significa cosas diferentes para diferentes departamentos:
- Para el catalogo: Un producto tiene nombre, descripcion, imagenes, categorias
- Para el inventario: Un producto tiene SKU, ubicacion en almacen, cantidad disponible
- Para el envio: Un producto tiene peso, dimensiones, restricciones de transporte
- Para finanzas: Un producto tiene coste, precio de venta, impuestos aplicables
Si intentas crear una unica clase “Producto” que contenga todo, terminas con un monstruo de cientos de campos que nadie entiende completamente. Cada cambio en un area afecta a las demas.
La solucion DDD: cada area tiene su propio bounded context con su propia definicion de “producto”. El Producto del catalogo no es el mismo objeto que el Producto del inventario. Son modelos diferentes que se comunican a traves de interfaces bien definidas.
Ubiquitous Language
El lenguaje ubicuo es un vocabulario compartido entre desarrolladores y expertos del dominio. No es un glosario tecnico. Es el lenguaje que todos usan, en las reuniones, en los documentos, y en el codigo.
Por que importa:
Si el negocio habla de “pedidos” y el codigo tiene una clase llamada “TransactionRecord”, hay una traduccion mental que cada persona tiene que hacer. Esa traduccion es una fuente constante de errores.
Como implementarlo:
- Identifica los terminos que el negocio usa para sus conceptos clave
- Usa exactamente esos terminos en el codigo (nombres de clases, metodos, variables)
- Si un termino es ambiguo, clarificalo con el negocio antes de escribir codigo
- Documenta el lenguaje ubicuo y mantenlo actualizado
Ejemplo: El negocio habla de “cancelar un pedido”. El codigo deberia tener un metodo order.cancel(), no order.setStatus("CANCELLED") ni orderService.processOrderCancellation(orderId). El lenguaje del codigo debe reflejar el lenguaje del negocio.
Context mapping
Una vez que tienes bounded contexts definidos, necesitas entender como se relacionan entre si. El context mapping documenta estas relaciones.
Patrones de relacion comunes:
Partnership: Dos equipos cooperan como socios. Ambos adaptan sus modelos segun las necesidades del otro. Requiere buena comunicacion.
Customer-Supplier: Un equipo (upstream) provee datos o servicios a otro (downstream). El downstream tiene cierta influencia sobre las prioridades del upstream.
Conformist: El downstream acepta el modelo del upstream sin modificaciones. Comun cuando el upstream es un sistema externo que no puedes cambiar.
Anti-corruption layer: El downstream crea una capa de traduccion que protege su modelo del modelo del upstream. Util cuando el upstream tiene un modelo confuso o inestable.
Open Host Service: Un contexto ofrece una API bien definida que otros pueden consumir. Es el patron mas comun para servicios publicos.
DDD tactico: los patrones concretos
Entidades
Una entidad es un objeto con identidad propia que persiste en el tiempo. Dos entidades con los mismos atributos pero diferente identidad son diferentes.
Ejemplo: Dos usuarios con el mismo nombre y email pero diferente ID son usuarios diferentes. La identidad importa.
Cuando usar entidades:
- El objeto tiene un ciclo de vida (se crea, cambia, puede eliminarse)
- La identidad importa para el negocio
- Necesitas rastrear cambios en el objeto a lo largo del tiempo
Error comun: Hacer todo una entidad. No todo necesita identidad. Un precio de 29.99 euros es un valor, no una entidad.
Value Objects
Un value object es un objeto sin identidad propia que se define por sus atributos. Dos value objects con los mismos atributos son iguales.
Ejemplo: Un Money(29.99, “EUR”) es igual a otro Money(29.99, “EUR”). No importa “cual” de los dos es.
Cuando usar value objects:
- El objeto se define por lo que es, no por quien es
- Es inmutable (no cambia despues de crearse)
- Puede reemplazarse por otro con los mismos valores sin consecuencias
Ventajas de value objects:
- Son inmutables, lo que elimina bugs de estado compartido
- Pueden encapsular validaciones (un Email valida el formato, un Money valida que el importe no sea negativo)
- Hacen el codigo mas expresivo:
calculatePrice(money: Money)es mas claro quecalculatePrice(amount: number, currency: string)
Agregados
Un agregado es un grupo de entidades y value objects que se tratan como una unidad para propositos de cambios de datos. Cada agregado tiene una raiz (aggregate root) que es la unica forma de acceder a los elementos internos.
Por que importan:
Los agregados definen limites de consistencia. Dentro de un agregado, la consistencia esta garantizada. Entre agregados, la consistencia es eventual.
Ejemplo: Un “Pedido” es un agregado que contiene:
- La entidad Pedido (raiz del agregado)
- Las lineas de pedido (entidades dentro del agregado)
- La direccion de envio (value object)
Reglas para disenar agregados:
- Mantenerlos pequenos. Un agregado con 20 entidades es una senal de alarma. Busca formas de dividirlo.
- Referenciar otros agregados por ID, no por referencia directa. El Pedido contiene
customerId, no una referencia al objeto Customer. - Consistencia dentro, eventual entre. Todas las invariantes dentro de un agregado se mantienen en cada transaccion. La consistencia entre agregados se maneja con eventos.
- Una transaccion, un agregado. No modifiques multiples agregados en la misma transaccion.
Repositories
Un repositorio proporciona acceso a agregados como si fueran colecciones en memoria. Oculta los detalles de persistencia.
Interface tipica:
OrderRepository:
findById(id): Order
save(order): void
findByCustomer(customerId): Order[]Cuando implementar repositories:
- Cuando la logica de persistencia es compleja (consultas elaboradas, multiples tablas)
- Cuando quieres desacoplar la logica de negocio de la base de datos
- Cuando necesitas poder cambiar la estrategia de persistencia (raro pero posible)
Cuando no necesitas repositories:
- CRUD simple donde el ORM es suficiente
- Prototipos donde la velocidad de desarrollo importa mas que la arquitectura
- Proyectos donde la base de datos es y sera siempre la misma
Domain Events
Un evento de dominio representa algo que ocurrio en el dominio y que es relevante para el negocio.
Ejemplos:
- OrderPlaced: se ha realizado un pedido
- PaymentReceived: se ha recibido un pago
- ShipmentDispatched: se ha enviado un pedido
Por que usar domain events:
- Desacoplamiento. El modulo de pedidos no necesita conocer el modulo de notificaciones. Solo emite un evento OrderPlaced, y el modulo de notificaciones reacciona.
- Audit trail. Los eventos son un registro natural de lo que ha ocurrido en el sistema.
- Extensibilidad. Puedes anadir nuevos comportamientos (enviar email, actualizar metricas, sincronizar con otro sistema) sin modificar el codigo existente.
Cuando aplicar DDD
Senales de que DDD aporta valor
- El dominio de negocio es complejo y tiene muchas reglas que cambian con frecuencia
- Hay confusion frecuente entre el equipo tecnico y el negocio sobre que deberia hacer el sistema
- El codigo actual tiene logica de negocio dispersa por todas partes (controladores, servicios, utilidades)
- El sistema necesita evolucionar a lo largo de anos
- Multiples equipos trabajan en diferentes areas del mismo sistema
Senales de que DDD es sobreingenieria
- El proyecto es un CRUD simple con pocas reglas de negocio
- El equipo es de 1-3 personas y la comunicacion es fluida
- El proyecto tiene una vida util corta (menos de un ano)
- Las reglas de negocio son simples y estables
- No hay expertos del dominio disponibles para colaborar
El enfoque gradual
No necesitas implementar DDD completo desde el primer dia. Un enfoque gradual:
Nivel 1: Solo lenguaje ubicuo. Empieza usando los terminos del negocio en tu codigo. Cuesta cero y aporta mucho.
Nivel 2: Bounded contexts. Identifica los limites naturales de tu dominio y organiza el codigo en modulos que los respeten.
Nivel 3: Patrones tacticos donde aporten valor. Implementa entidades, value objects y agregados solo en las partes del sistema donde la complejidad del dominio lo justifique.
Nivel 4: Domain events para desacoplamiento. Cuando necesites que diferentes partes del sistema reaccionen a lo que ocurre sin acoplarse directamente.
DDD en proyectos reales: lecciones aprendidas
DDD y bases de datos
Uno de los conflictos mas comunes: el modelo de dominio no tiene por que coincidir con el modelo de base de datos.
En DDD puro, el modelo de dominio dicta la estructura, y la persistencia se adapta. En la practica, esto genera tensiones:
- Los ORMs esperan cierta estructura de objetos que puede no coincidir con tu modelo de dominio
- Las consultas complejas pueden ser mas eficientes con un modelo de datos diferente al modelo de dominio
- Las migraciones de esquema son mas complejas cuando hay discrepancia
Enfoque pragmatico: Empieza con un modelo de dominio que se mapee directamente a la base de datos. Solo introduce capas de abstraccion cuando la discrepancia entre ambos modelos cause problemas reales.
DDD y microservicios
DDD se menciona frecuentemente como guia para definir los limites de microservicios. La idea es que cada bounded context sea un microservicio.
Esto es una simplificacion peligrosa:
- Un bounded context puede contener multiples microservicios
- Un microservicio puede cubrir varios bounded contexts pequenos
- La decision de microservicios involucra factores operativos que DDD no aborda
Regla practica: Usa bounded contexts para organizar tu monolito. Si y cuando necesites microservicios, los bounded contexts te dan buenos candidatos para la division, pero no son la unica consideracion.
DDD y equipos pequenos
DDD se diseno pensando en proyectos grandes con multiples equipos. Pero muchos de sus conceptos son valiosos para equipos pequenos:
- El lenguaje ubicuo mejora la comunicacion sin importar el tamano del equipo
- Los bounded contexts ayudan a organizar el codigo incluso en un monolito de 2 personas
- Los value objects mejoran la calidad del codigo independientemente de la escala
Lo que no necesitas en equipos pequenos: context mapping formal, multiples repositorios con abstraccion completa, o domain events sofisticados.
Framework de implementacion
Paso 1: Event Storming
Event Storming es una tecnica de modelado colaborativo creada por Alberto Brandolini. Reune a desarrolladores y expertos del negocio para mapear el dominio usando eventos.
Como funciona:
- En una pared grande, cada participante escribe eventos del dominio en post-its naranjas: “Pedido Realizado”, “Pago Recibido”, “Envio Despachado”
- Se ordenan cronologicamente
- Se identifican los comandos que causan los eventos (post-its azules): “Realizar Pedido”
- Se identifican los actores que ejecutan los comandos
- Se agrupan los eventos en clusters que sugieren bounded contexts
Duracion: 2-4 horas para un dominio de complejidad media. Es la mejor inversion de tiempo para entender un dominio antes de escribir codigo.
Paso 2: Definir bounded contexts
Con el mapa de eventos, identifica las fronteras naturales:
- Donde cambia el vocabulario (un mismo termino significa cosas diferentes)
- Donde cambia el ritmo de cambio (un area cambia mucho y otra es estable)
- Donde cambian los stakeholders (diferentes personas son responsables de diferentes areas)
Paso 3: Definir el lenguaje ubicuo por contexto
Para cada bounded context, documenta:
- Los terminos clave y sus definiciones
- Las relaciones entre terminos
- Las reglas de negocio principales
Paso 4: Implementar gradualmente
Empieza por el bounded context mas critico o mas complejo. Implementa el modelo de dominio, valida con el negocio, y itera. Luego pasa al siguiente contexto.
Conclusion
DDD es una herramienta poderosa para gestionar la complejidad del dominio, pero no es una bala de plata. Su mayor valor esta en los conceptos estrategicos (bounded contexts, lenguaje ubicuo, context mapping) mas que en los patrones tacticos.
Tres principios para aplicar DDD de forma practica:
- Empieza por el lenguaje. Usa los terminos del negocio en tu codigo. Es gratuito y tiene impacto inmediato.
- Identifica limites antes de patrones. Los bounded contexts son mas importantes que los agregados o los repositorios.
- Aplica patrones tacticos selectivamente. Solo donde la complejidad del dominio lo justifique. No en los CRUDs simples.
Si necesitas ayuda para disenar la arquitectura de dominio de tus proyectos de software a medida, en NERVICO aplicamos DDD donde aporta valor real, no como ejercicio academico.