¿Qué hace Diseño Guiado por el Dominio (DDD) — Patrones y Prácticas?
Cuándo Usar DDD
Úsalo cuando:
- Lógica de negocio compleja que va más allá de CRUD
- Múltiples expertos del dominio con diferentes vocabularios
- El modelo de dominio es la ventaja competitiva
- Aplicaciones de nivel empresarial
No lo uses cuando:
- Aplicaciones CRUD simples
- Hackathon/MVP (excesivo)
- No hay experto del dominio disponible
Diseño Estratégico
Contextos Delimitados
El concepto más importante de DDD. Un Contexto Delimitado es un límite dentro del cual un modelo particular se define y es aplicable.
Ejemplo — Comercio Electrónico:
[Contexto de Pedido] [Contexto de Pago] [Contexto de Envío]
- Pedido - Pago - Envío
- ItemPedido - Transacción - NúmeroSeguimiento
- Cliente (ref) - Reembolso - Dirección
- Dirección (valor) - Factura - Transportista
Cliente significa cosas diferentes en cada contexto:
- Contexto de Pedido: nombre, correo electrónico, preferencia de envío
- Contexto de Pago: información de facturación, métodos de pago
- Contexto de Soporte: historial de tickets, puntuación de satisfacción
Mapa de Contexto
[Pedido] ←→ [Pago] # Asociación
[Pedido] → [Envío] # Cliente-Proveedor
[Pedido] → [CRM heredado] # Capa Anticorrupción
Diseño Táctico
Entidades
Objetos con identidad. Dos entidades con los mismos atributos pero IDs diferentes son diferentes.
class Usuario {
readonly id: UsuarioId;
nombre: string;
correo: CorreoElectronico; // Objeto Valor
}
Objetos Valor
Objetos definidos por sus atributos, no por su identidad. Inmutables.
class CorreoElectronico {
constructor(readonly valor: string) {
if (!esValidoCorreo(valor)) throw new ErrorCorreoInvalido(valor);
}
equals(otro: CorreoElectronico): boolean {
return this.valor === otro.valor;
}
}
Agregados
Un grupo de entidades y objetos valor con una única raíz de agregado (Raíz del Agregado). Todo el acceso pasa por la raíz.
class Pedido { // Raíz del Agregado
private items: ItemPedido[] = [];
agregarItem(producto: ReferenciaProducto, cantidad: number): void {
// Lógica de negocio AQUÍ, no en un servicio
if (cantidad <= 0) throw new ErrorCantidadInvalida();
this.items.push(new ItemPedido(producto, cantidad));
}
get total(): Dinero {
return this.items.reduce((suma, item) => suma.sumar(item.subtotal), Dinero.cero());
}
}
Reglas de Agregados:
- Referenciar otros agregados solo por ID
- Un agregado por transacción
- Consistencia eventual entre agregados
Eventos de Dominio
Algo que ocurrió en el dominio que los expertos del dominio consideran importante.
class PedidoRealizado implements EventoDeDominio {
constructor(
readonly idPedido: PedidoId,
readonly idCliente: ClienteId,
readonly total: Dinero,
readonly ocurridoEn: Date
) {}
}
Servicios de Dominio
Lógica de negocio que no pertenece naturalmente a una entidad u objeto valor.
class ServicioDePrecios {
calcularPrecio(pedido: Pedido, cliente: Cliente, promociones: Promocion[]): Dinero {
// Lógica de precios compleja que involucra múltiples agregados
}
}
Repositorios
Abstracción sobre el acceso a datos. Un repositorio por raíz de agregado.
interface RepositorioDePedidos {
buscarPorId(id: PedidoId): Promise<Pedido | null>;
guardar(pedido: Pedido): Promise<void>;
eliminar(id: PedidoId): Promise<void>;
}
Estructura de Directorios (DDD)
src/
├── modulos/
│ └── pedidos/ # Contexto Delimitado
│ ├── dominio/
│ │ ├── entidades/
│ │ │ └── pedido.ts # Raíz del Agregado
│ │ ├── objetos-valor/
│ │ │ └── dinero.ts
│ │ ├── eventos/
│ │ │ └── pedido-realizado.ts
│ │ ├── servicios/
│ │ │ └── precios.ts
│ │ └── repositorios/
│ │ └── repositorio-pedido.ts # Interfaz
│ ├── aplicacion/
│ │ ├── comandos/
│ │ │ └── realizar-pedido.ts
│ │ ├── consultas/
│ │ │ └── obtener-pedido.ts
│ │ └── manejadores/
│ │ └── realizar-pedido.manejador.ts
│ └── infraestructura/
│ ├── persistencia/
│ │ └── repositorio-pedido.impl.ts # Implementación
│ └── api/
│ └── pedidos.controlador.ts
La Regla de Dependencia de la Arquitectura Limpia
En DDD, las dependencias DEBEN apuntar estrictamente hacia adentro:
[ Frameworks & Drivers (Web, DB, UI) ]
└──▶ [ Adaptadores de Interfaz (Controladores, Gateways) ]
└──▶ [ Aplicación (Casos de Uso, Manejadores CQRS) ]
└──▶ [ Dominio (Entidades, Objetos Valor) ]
- La capa de Dominio tiene CERO dependencias de ORMs (Prisma, TypeORM), frameworks HTTP (Express, NestJS) o SDKs externos.
- Los repositorios se definen como interfaces en la capa de dominio/aplicación y se implementan en la capa de infraestructura.
Eventos de Dominio vs Eventos de Integración
- Eventos de Dominio: Representan cambios de estado dentro de un único Contexto Delimitado.
- Se lanzan directamente dentro de la Raíz del Agregado (
pedido.agregarItem(...)lanzaItemAgregadoAlPedido). - Se despachan en proceso antes de la confirmación de la transacción.
- Se lanzan directamente dentro de la Raíz del Agregado (
- Eventos de Integración: Se publican a través de los límites del Contexto Delimitado para comunicarse con otros servicios.
- Se despachan mediante el patrón Buzón de Salida Transaccional (Transactional Outbox) hacia brokers de mensajes.
- Deben usar esquemas compatibles hacia atrás con versionado.
Capa Anticorrupción (ACL)
Al consumir datos de un contexto delimitado externo o de una API de proveedor de terceros (por ejemplo, Stripe, Salesforce):
- NO importes modelos de dominio externos directamente en tu dominio.
- Crea un Traductor / Adaptador ACL en la capa de infraestructura para convertir DTOs externos en tus propios Objetos Valor y Entidades.
Anti-patrones
- [FALLA] Modelo de dominio anémico — entidades con solo getters/setters, toda la lógica en servicios
- [FALLA] Agregado grande — los agregados deben ser pequeños, centrados en invariantes
- [FALLA] Transacciones entre agregados — usa consistencia eventual
- [FALLA] DDD en todas partes — usa DDD solo donde la complejidad lo justifique
- [FALLA] Entidades ORM filtrándose en el Dominio — las entidades de dominio no deben depender de
@Entity()ni de decoradores ORM
Ejemplos de Código
Consulta EXAMPLES.md para ejemplos de código detallados.
Lista de Verificación de Validación
Qué verificar durante la fase de revisión antes de completar la tarea.
Errores Comunes
Anti-patrones y cosas que evitar explícitamente. Consulta TROUBLESHOOTING.md.
Notas de Integración
Cómo interactúa esta habilidad con otras habilidades.