Volver al blog
• 9 min de lectura

DDD no se trata de entidades

El error más común al adoptar Domain-Driven Design es creer que se trata de anotar clases con @Entity y modelar tablas en objetos. DDD es una disciplina de diseño estratégico, y las entidades son apenas una pieza menor del rompecabezas.

DDD Arquitectura Backend Diseño de Software Domain-Driven Design
DDD no se trata de entidades
Foto por Michael Nowarra

Hay un patrón que se repite con frecuencia en equipos que dicen adoptar Domain-Driven Design:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    private Long id;

    @Column
    private String status;

    @OneToMany
    private List<OrderItem> items;

    // getters y setters
}

Y cuando les preguntas “¿están haciendo DDD?”, responden: “sí, tenemos entidades de dominio”.

No están haciendo DDD. Están haciendo modelado relacional en objetos, que es una cosa muy distinta.

DDD no se trata de entidades. Las entidades son un patrón táctico menor — uno de los bloques de construcción más básicos — dentro de una disciplina que es fundamentalmente estratégica. Confundir DDD con “crear clases que mapean tablas” es como confundir arquitectura de software con “elegir un framework”.


El error de empezar por lo táctico

El libro azul de Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software — tiene dos partes principales: diseño estratégico y diseño táctico.

La mayoría de los equipos leen los capítulos de patrones tácticos (Entidades, Objetos de Valor, Agregados, Repositorios, Servicios de Dominio) y los aplican como si fueran el destino. Ignoran o leen por encima los capítulos de diseño estratégico, que son el fundamento de todo.

El resultado es predecible: clases con anotaciones de JPA, modelos anémicos con getters y setters, y ningún cambio real en cómo el equipo entiende y modela el negocio.

El diseño táctico sin diseño estratégico es decoración.


Lo que DDD es realmente

DDD es una manera de afrontar la complejidad del negocio poniendo el modelo del dominio en el centro del sistema. Su premisa fundamental:

El software debe reflejar la comprensión profunda del dominio que tiene el equipo, y esa comprensión debe estar en el código, no solo en las cabezas de las personas.

Esto requiere tres cosas que no tienen nada que ver con entidades:

1. Lenguaje Ubicuo

El patrón más importante de DDD no está en los capítulos tácticos. Es el Lenguaje Ubicuo (Ubiquitous Language).

El lenguaje ubicuo es el vocabulario compartido entre desarrolladores y expertos del dominio, que se usa en conversaciones, documentos, código, tests y UI. No es una capa de traducción — es el idioma del modelo, y el modelo vive en el código.

Cuando el experto de negocio habla de “publicar una orden” y el código tiene un método setStatus("PUBLISHED"), hay una brecha. Cuando el código tiene order.publish(), el modelo y el dominio hablan el mismo idioma.

// Sin lenguaje ubicuo
order.setStatus("CONFIRMED");
order.setConfirmedAt(LocalDateTime.now());

// Con lenguaje ubicuo
order.confirm();  // el método encapsula el concepto completo

La diferencia no es estética. Es la diferencia entre un modelo que refleja el dominio y uno que refleja la base de datos.

2. Contextos Delimitados

El patrón estratégico más importante de DDD es el Contexto Delimitado (Bounded Context).

Un Contexto Delimitado es un límite explícito dentro del cual un modelo de dominio es consistente y tiene un significado preciso. Fuera de ese límite, los mismos términos pueden significar cosas distintas.

Cliente en el contexto de Ventas tiene atributos como historial de compras, segmento, canal de adquisición. Cliente en el contexto de Soporte tiene tickets, nivel de SLA, historial de incidentes. Cliente en el contexto de Facturación tiene datos fiscales, métodos de pago, ciclo de facturación.

No es el mismo objeto. Son tres modelos distintos, cada uno completo y coherente dentro de su contexto.

El error clásico es intentar tener una entidad Cliente que sirva a todos los contextos. El resultado es un objeto con decenas de atributos, la mayoría opcionales según el contexto, imposible de mantener y que nadie entiende completamente.

❌ Un modelo unificado para todo:
   Cliente (id, nombre, email, RFC, CURP, historial_compras,
            segmento, ticket_activo, sla_level, metodo_pago,
            ciclo_facturacion, canal_adquisicion, ...)

✅ Modelos separados por contexto:
   Ventas:      Prospect (id, nombre, email, segmento, canal)
   Soporte:     Suscriptor (id, nombre, nivel_sla, tickets[])
   Facturación: CuentaFacturable (id, rfc, metodo_pago, ciclo)

Cada modelo es más simple, más cohesivo, y refleja exactamente lo que ese contexto necesita saber.

3. Mapa de Contextos

Los Contextos Delimitados no existen en aislamiento. Se relacionan entre sí, y esas relaciones tienen patrones con nombres: Shared Kernel, Customer-Supplier, Conformist, Anti-Corruption Layer, Open-Host Service, Published Language.

El Mapa de Contextos (Context Map) es un diagrama que muestra cómo se relacionan los contextos y qué tipo de relación tienen. No es un diagrama de clases — es un mapa político del sistema.

Sin este mapa, los equipos no saben quién depende de quién, dónde están las fronteras de responsabilidad, o cómo se propagan los cambios. Con el mapa, tienen una vista estratégica del sistema que informa decisiones de equipo, de despliegue y de evolución.


Los patrones tácticos en su lugar correcto

Una vez que tienes el diseño estratégico — lenguaje ubicuo, contextos delimitados, mapa de contextos — los patrones tácticos tienen sentido y un lugar donde aplicarse.

Agregados, no Entidades

El patrón táctico central de DDD no es la Entidad — es el Agregado.

Un Agregado es un clúster de objetos del dominio que se tratan como una unidad para las operaciones de escritura. Tiene una Raíz de Agregado (Aggregate Root) que es la única puerta de entrada al clúster. Nadie puede modificar objetos internos del agregado directamente — todo pasa por la raíz.

El Agregado define una frontera de consistencia: los invariantes del dominio se garantizan dentro de esa frontera, no más allá.

// MAL: acceso directo a objetos internos
order.getItems().add(new OrderItem(productId, quantity, price));
order.setTotal(calculateTotal(order.getItems()));

// BIEN: todo pasa por la raíz del agregado
order.addItem(productId, quantity, price);
// el agregado mantiene sus propios invariantes internamente

La raíz del agregado es la que tiene identidad (@Entity con ID). Los objetos internos pueden ser Entidades o Valores — pero eso es un detalle de implementación, no el punto central.

Objetos de Valor: subutilizados y poderosos

Los Objetos de Valor (Value Objects) son tan importantes como los Agregados, y son los más ignorados.

Un Objeto de Valor no tiene identidad — se define por sus atributos. Dos objetos de valor con los mismos atributos son idénticos. Son inmutables por diseño.

Money(100, "USD") y Money(100, "USD") son el mismo valor. No importa cuál es “el objeto”. Lo que importa es el valor que representan.

// Sin Objetos de Valor: primitivos sueltos, reglas de negocio dispersas
public void process(double amount, String currency, String fromAccount, String toAccount) { ... }

// Con Objetos de Valor: conceptos del dominio explícitos
public void process(Money amount, AccountId from, AccountId to) { ... }

Los Objetos de Valor eliminan el Primitive Obsession (obsesión por tipos primitivos), encapsulan validaciones y comportamiento junto con los datos, y hacen el código más expresivo y el modelo más fiel al dominio.

Eventos de Dominio

Los Eventos de Dominio (Domain Events) modelan hechos que ocurrieron en el dominio. No son eventos técnicos — son conceptos del negocio expresados como datos inmutables.

OrderConfirmed, PaymentProcessed, InventoryReserved. Cada uno representa algo significativo que el dominio reconoce como un hecho.

Los eventos de dominio permiten desacoplar agregados, habilitar comunicación entre contextos, y construir sistemas orientados a eventos que reflejan el flujo real del negocio.

public class Order {
    public void confirm() {
        // lógica de negocio
        this.status = OrderStatus.CONFIRMED;
        this.confirmedAt = Instant.now();

        // publica el hecho
        registerEvent(new OrderConfirmed(this.id, this.customerId, this.confirmedAt));
    }
}

El modelo anémico: el anti-patrón que DDD busca eliminar

El Modelo de Dominio Anémico (Anemic Domain Model) es lo que obtienes cuando aplicas nombres de DDD sin su esencia: objetos que tienen datos pero no comportamiento, donde la lógica de negocio vive en servicios que operan sobre esos objetos desde afuera.

// Modelo anémico: Order no hace nada, solo tiene datos
public class Order {
    private String status;
    private List<OrderItem> items;
    private BigDecimal total;
    // getters y setters
}

// Toda la lógica en el servicio
public class OrderService {
    public void confirmOrder(Order order) {
        if (order.getStatus().equals("PENDING")) {
            order.setStatus("CONFIRMED");
            order.setConfirmedAt(LocalDateTime.now());
            notificationService.sendConfirmation(order);
            inventoryService.reserve(order.getItems());
        }
    }
}

Este patrón parece correcto porque separa “datos” de “lógica”. Pero en DDD, esa separación es el problema. El modelo del dominio debería encapsular tanto los datos como el comportamiento que los protege y los transforma. Un objeto que no puede hacer nada por sí mismo no es un modelo — es una estructura de datos con un nombre de dominio.


DDD tiene un costo real

DDD no es una técnica que se aplica a todo. Es una inversión significativa que tiene sentido en dominios complejos donde el negocio tiene reglas sofisticadas, el conocimiento del dominio es difícil de capturar, y el software necesita evolucionar con el dominio durante años.

No tiene sentido en CRUDs simples, en herramientas internas de bajo valor, o en prototipos. Aplicar DDD donde no corresponde produce complejidad innecesaria sin beneficio real.

La señal de que DDD vale la pena no es la tecnología — es la complejidad del dominio. Si el experto de negocio puede explicar las reglas en 15 minutos, probablemente no necesitas DDD. Si necesita horas para cubrir todos los casos especiales, excepciones y estados posibles, DDD es donde debería empezar el diseño.


Por dónde empezar realmente

Si quieres aplicar DDD de verdad, empieza por lo estratégico:

  1. Event Storming — una sesión colaborativa con el equipo técnico y los expertos del dominio para explorar el dominio a través de eventos. Es la herramienta más práctica para descubrir el modelo antes de escribir código.

  2. Identifica tus Contextos Delimitados — dónde están las fronteras naturales del dominio, qué equipos las poseen, cómo se relacionan.

  3. Construye el Lenguaje Ubicuo — un glosario vivo de términos que el equipo usa consistentemente. Si el código no usa esos términos, hay una brecha.

  4. Luego, y solo luego, piensa en Agregados — dónde están los invariantes de negocio, qué necesita ser consistente como unidad.

Las entidades vendrán solas. No son el punto de partida.


Cierre

DDD es difícil no porque sus patrones sean complicados, sino porque requiere algo que la mayoría de los equipos no hacen bien: hablar con los expertos del dominio, entender el negocio en profundidad, y diseñar software que refleje esa comprensión en lugar de reflejar la estructura de una base de datos.

Las entidades son una herramienta. El lenguaje ubicuo, los contextos delimitados y los agregados son el corazón.

Si estás anotando clases con @Entity y pensando que estás haciendo DDD, estás en el paso cero. El trabajo real aún no ha empezado.