Saltar al contenido

Domain-Driven Design (DDD)

Cómo DDD propone modelar el problema de negocio: Strategic Design (subdomains, bounded contexts, ubiquitous language, context maps) y Tactical Design (entities, value objects, aggregates, domain events y repositories).

9 min. de lectura

Mientras que los Drivers de Arquitectura responden:

¿Qué necesita el sistema y qué lo condiciona?

Domain-Driven Design responde:

¿Cómo entendemos y modelamos correctamente el problema de negocio?

DDD propone centrar el diseño en el domain: sus conceptos, sus reglas y su lenguaje.

DDD tiene dos grandes dimensiones: Strategic Design y Tactical Design.

Strategic Design

El Strategic Design intenta responder:

  • ¿qué partes del negocio existen?
  • ¿cómo se relacionan?
  • ¿dónde están los boundaries?
  • ¿qué lenguaje se utiliza?
  • ¿qué partes son estratégicamente importantes?

Domain y Subdomains

Imaginemos una plataforma de e-commerce:

Cada una de estas áreas puede representar un subdomain diferente, y no todas tienen necesariamente la misma importancia.

Core Domain

Es la parte donde existe una ventaja o diferenciación importante. Payments sería Core si la plataforma tiene capacidades de payment processing que representan una ventaja competitiva.

Supporting Domain

Necesario para el negocio, pero sin valor diferencial. Un ejemplo típico es Inventory.

Generic Domain

Funcionalidad común, como Authentication.

Esta clasificación ayuda a decidir dónde invertir en diseño propio y dónde apoyarse en soluciones existentes.

Bounded Context

Es uno de los conceptos más importantes de DDD. Un Bounded Context define un límite dentro del cual un modelo y su lenguaje tienen un significado específico.

El término Customer puede existir en los tres contextos sin representar el mismo modelo en cada uno.

Bounded Context ≠ Microservicio

Esta distinción es fundamental:

ConceptoQué representa
Bounded ContextLímite conceptual y de dominio
MicroservicioLímite de deployment / runtime

Un Bounded Context puede implementarse como un módulo, un boundary dentro de un monolith o un microservicio. Por eso DDD no implica microservicios.

Ubiquitous Language

El equipo y los domain experts deberían compartir un lenguaje. En Payments:

Authorize
Capture
Refund
Payment
Transaction
Provider
Settlement

La terminología debería aparecer también en el software:

payment.authorize()
payment.capture()
payment.refund()

y no esconder conceptos importantes detrás de nombres genéricos como payment.process(), payment.update() o payment.handle() cuando esos métodos abarcan múltiples reglas de negocio.

Context Map

Los bounded contexts no existen aislados. Podemos representar sus relaciones:

DDD dispone de patrones para describir estas relaciones, como Customer/Supplier, Conformist, Anti-Corruption Layer, Shared Kernel, Open Host Service y Published Language.

Un Anti-Corruption Layer, por ejemplo, protege el modelo interno de un contexto frente a conceptos provenientes de otro sistema:

Es particularmente útil al integrar sistemas legacy o terceros con modelos poco alineados.

Tactical Design

Strategic DDD define los boundaries. Tactical DDD trabaja sobre el modelo interno, con building blocks como:

  • Entity
  • Value Object
  • Aggregate
  • Aggregate Root
  • Domain Service
  • Domain Event
  • Repository
  • Factory

Entity

Tiene identidad persistente:

Payment
PaymentId = 123

Dos Payments pueden tener los mismos valores, pero seguir siendo entidades diferentes si tienen identidades diferentes.

Value Object

Se define por sus valores:

Money(100, ARS)
Currency(ARS)
Email(...)
Address(...)

Un Money(100, ARS) no necesita un MoneyId.

Aggregate

Un Aggregate es una frontera dentro de la cual se protege un conjunto de invariantes.

El Aggregate Root es el punto de entrada:

payment.authorize()
payment.capture()
payment.refund()

No queremos que cualquier código pueda modificar arbitrariamente payment.status = CAPTURED, porque podría saltarse reglas del dominio.

Proteger esas invariantes dentro del aggregate es un problema de diseño con solución conocida: cuando lo que varía según el estado son las operaciones permitidas —un pedido enviado ya no se puede cancelar—, el patrón State evita que esa regla quede repartida en condicionales por todo el código.

Aggregate Design

Una idea importante es no construir Aggregates gigantes. Un Aggregate debería contener aquello que necesita mantener consistencia transaccional. Podría ser tentador modelar todo como un único Aggregate:

Pero eso produciría transacciones muy grandes, lock contention, alto acoplamiento y menor escalabilidad. DDD ayuda precisamente a preguntar:

¿Qué cosas realmente necesitan cambiar juntas?

Domain Service

Existe lógica de dominio que no pertenece naturalmente a una sola Entity. Por ejemplo, un FraudDecisionService que combina varias fuentes:

No significa que todos los servicios de una aplicación deban ser Domain Services. Un PaymentService con veinte responsabilidades suele ser una señal de que el modelo de dominio no expresa las reglas con suficiente claridad.

Domain Events

Un Domain Event expresa un hecho —PaymentAuthorized, PaymentCaptured, PaymentRefunded— y no una orden como CapturePayment.

Los Domain Events conectan DDD con los patrones de comunicación event-driven, pero no implican por sí mismos que necesitemos Kafka o microservicios.

Repository

Un Repository abstrae la persistencia de un Aggregate. PaymentRepository podría implementarse como PostgresPaymentRepository: el dominio depende de la abstracción, no de PostgreSQL. Esta idea conecta directamente con Hexagonal, Onion y Clean Architecture.

Factory

Una Factory puede encapsular una creación compleja, como Payment.create(...), si para crear un Payment hay que validar múltiples condiciones o garantizar invariantes. Para objetos triviales como new Money(100, ARS), introducir una Factory puede ser innecesario.

Ejemplo integrado

Cuándo DDD es especialmente útil

  • dominios complejos;
  • muchas reglas de negocio;
  • terminología ambigua;
  • equipos grandes;
  • múltiples subdomains;
  • sistemas que evolucionarán durante años.

Cuándo no hace falta usar todo DDD

Para un CRUD muy simple (Users, Products, Categories), crear Aggregates, Domain Services, Factories y Context Maps para cada operación puede introducir más ceremonia que valor. DDD es sobre todo una herramienta para lidiar con la complejidad del dominio, no una checklist.

Herramientas y tecnologías

DDD no requiere una tecnología específica. Puede implementarse con Java + Spring, C# + .NET, Kotlin, TypeScript, Go o Python. Lo importante es el modelo, no el framework.

Referencias

  • Eric Evans sentó las bases de Domain-Driven Design en Domain-Driven Design: Tackling Complexity in the Heart of Software.
  • Microsoft Azure Architecture Center presenta cómo utilizar bounded contexts y principios de Domain-Driven Design para definir límites de microservicios.

Te sirvió, compartilo

// ¿te sirvió?
// compartilo