fundamentos · temario
1.1tema 1 de 5

¿Qué es Domain-Driven Design?

El dominio como centro de gravedad del software.

Domain-Driven Design (DDD) es un enfoque de desarrollo de software propuesto por Eric Evans en 2003 que sitúa el dominio del problema —la lógica de negocio y sus reglas— en el centro de toda decisión de diseño. La premisa fundamental es que el mayor riesgo en proyectos complejos no es técnico sino conceptual: cuando los desarrolladores no entienden el negocio, el código acaba modelando suposiciones incorrectas que se acumulan silenciosamente hasta que el sistema se vuelve inmanejeable.

DDD distingue dos tipos de complejidad. La complejidad esencial es la inherente al problema: las reglas de negocio, las invariantes del dominio y las excepciones que ningún framework puede eliminar. La complejidad accidental es la que el equipo introduce innecesariamente: indirecciones prematuras, abstracciones mal ubicadas, o frameworks que dictan la forma del código antes de entender el problema. DDD se ocupa de atacar la complejidad esencial con el mejor modelo posible y de mantener la accidental al mínimo.

El corazón de DDD es el modelo del dominio: una representación en código que captura el conocimiento del negocio de forma precisa. Este modelo no es un diagrama en una pizarra ni un documento Word; es el código mismo, escrito en el lenguaje que los expertos del dominio usan. Si el modelo y el lenguaje del negocio divergen, aparece una capa invisible de traducción constante que genera errores y ralentiza el equipo.

DDD se divide en dos grandes áreas. El diseño estratégico define los límites del sistema —qué partes del negocio modelar, cómo separarlas, cómo relacionarlas— mediante conceptos como Bounded Contexts y Context Maps. El diseño táctico provee los bloques de construcción concretos —Value Objects, Entities, Aggregates, Domain Events— que implementan el modelo dentro de esos límites.

DDD no es una metodología para todos los proyectos. Tiene un coste real de aprendizaje y diseño que solo se justifica cuando la complejidad del dominio es alta: reglas de negocio que cambian con frecuencia, muchos conceptos interrelacionados, equipos grandes que necesitan trabajar en paralelo. Para un CRUD simple con tres tablas, DDD es sobre-ingeniería. Saber cuándo NO aplicarlo es parte del criterio de un buen arquitecto.

En Python, DDD se implementa con herramientas idiomáticas del lenguaje: `@dataclass(frozen=True)` para Value Objects inmutables, clases con comportamiento explícito para Entities y Aggregates, `ABC` o `Protocol` para puertos y abstracciones, y errores de dominio como objetos en lugar de excepciones para el flujo de negocio. El objetivo es que cualquier desarrollador que lea el código pueda entender el negocio sin necesitar documentación externa.

structure.txt
# domain/model/<aggregate>.py — Aggregate Root
# Concentra comportamiento y protege invariantes del dominio.
# NO importa frameworks, ORM ni infraestructura.

from dataclasses import dataclass, field
from typing import Final
from .<value_object> import <ValueObject>
from ..events.<something_happened> import <SomethingHappened>

@dataclass
class <Aggregate>:
    id: Final[<AggregateId>]
    _<attribute>: <ValueObject>
    _events: list = field(default_factory=list, repr=False)

    def <perform_action>(self, <param>: <ValueObject>) -> None:
        # Business rule: validate before mutating state
        if not self._<is_valid_condition>(<param>):
            raise <DomainError>("<rule violated>")
        self._<attribute> = <param>
        self._events.append(<SomethingHappened>(aggregate_id=self.id))

    def pull_events(self) -> list:
        # Caller drains events; aggregate does not publish directly
        events, self._events = self._events, []
        return events

    def _<is_valid_condition>(self, <param>: <ValueObject>) -> bool:
        # Private guard: keep business rules inside the aggregate
        return <condition>

Debugging lab

Detecta y corrige el error o la violación de diseño.

0/5 tests passing0%
  1. 1.1.5.1

    from infrastructure.persistence.models import OrderORM class Order: def complete(self): db = OrderORM.session() db.query(Order).filter_by(id=self.id).update({'status': 'completed'}) db.commit()

  2. 1.1.5.2

    class <Aggregate>: def get_status(self): return self._status def set_status(self, s): self._status = s def get_name(self): return self._name def set_name(self, n): self._name = n

  3. 1.1.5.3

    from application.use_cases import ValidatePolicy class <Aggregate>: def approve(self, policy: ValidatePolicy) -> None: policy.run(self) self._approved = True

  4. 1.1.5.4

    import requests class <Aggregate>: def notify(self) -> None: requests.post('https://api.example.com/notify', json={'id': str(self.id)})

  5. 1.1.5.5

    class <Aggregate>: # No DDD needed here, it is just a simple record name: str created_at: datetime updated_at: datetime