Diseño basado en dominios (DDD) en microservicios
Cuando las organizaciones pasan de una arquitectura monolítica a microservicios, se enfrentan a una pregunta crítica y de alto riesgo: ¿Cómo trazamos los límites de nuestros servicios?
En teoría, los microservicios deberían ser unidades flexibles y desacopladas que puedan desarrollarse, implementarse y escalarse de forma independiente. Sin embargo, en la práctica, muchos equipos terminan construyendo un monolito distribuido: un sistema donde los servicios están tan estrechamente acoplados que un único cambio empresarial requiere modificar e implementar múltiples servicios simultáneamente, lo que agrava la latencia de la red y los bloqueos en la implementación.
Para evitar este problema, los arquitectos de software recurren al Diseño basado en dominios (DDD). Introducido por primera vez por Eric Evans en 2003, DDD es una metodología de desarrollo de software que alinea las estructuras de código con los complejos dominios comerciales que representan.
En este artículo, exploraremos cómo DDD proporciona los planos estratégicos y tácticos para diseñar microservicios limpios, desacoplados y altamente mantenibles.
1. El plan estratégico: trazar los límites del servicio
DDD se divide en dos fases principales: Diseño estratégico y Diseño táctico. El diseño estratégico consiste en modelar contextos comerciales y definir límites de servicios. Proporciona la vista “macro” de la arquitectura.
A. Lenguaje ubicuo
Dentro de una empresa, diferentes departamentos utilizan las mismas palabras para significar cosas completamente diferentes. Por ejemplo:
- Para el equipo de ventas, un “Usuario” es un cliente potencial.
- Para el equipo de seguridad, un “Usuario” es un conjunto de credenciales de inicio de sesión.
- Para el equipo de envío, un “Usuario” es una dirección física del destinatario.
Intentar crear un modelo de base de datos único y unificado para un “usuario” que satisfaga a todos da como resultado una base de código masiva y enredada. DDD resuelve esto estableciendo un lenguaje ubicuo: un vocabulario compartido definido y utilizado tanto por expertos en el ámbito empresarial como por desarrolladores dentro de un límite específico.
B. Contextos acotados
Un Contexto delimitado es el límite explícito dentro del cual se aplica un modelo de dominio. Dentro de la frontera, todos los términos del lenguaje ubicuo tienen un significado único e inequívoco.
En lugar de un único modelo de “Usuario”, definimos modelos separados dentro de sus respectivos contextos delimitados:
- En el Contexto del pedido, tenemos un modelo
Orderque contiene información de contacto del cliente. - En el Contexto de identidad, tenemos un modelo
Credentials. - En el Contexto de envío, tenemos un modelo
DeliveryAddress.
La regla de oro de los microservicios: Un contexto delimitado se asigna directamente a un microservicio.
Al dividir el sistema en contextos acotados, nos aseguramos de que los microservicios estén débilmente acoplados y representen capacidades comerciales distintas.
C. Mapeo de contexto: cómo se comunican los servicios
Los contextos acotados no existen de forma aislada; deben interactuar. Un Mapa de contexto define las relaciones y los mecanismos de traducción entre contextos. Los patrones clave incluyen:
- Kernel compartido: dos contextos comparten un pequeño subconjunto del modelo de dominio y la base de datos (normalmente no se recomienda en microservicios debido al acoplamiento).
- Cliente-Proveedor: Un contexto (proveedor) debe proporcionar datos a otro (cliente). El proveedor debe coordinar los cronogramas de liberación con el cliente.
- Capa anticorrupción (ACL): una capa de traducción que traduce los datos entrantes de un sistema externo al modelo de dominio interno del cliente, evitando que los modelos externos contaminen la arquitectura interna.
2. El plan táctico: estructuración del código base de microservicios
Una vez que se trazan los límites del servicio mediante el diseño estratégico, Tactical Design proporciona un conjunto de patrones de diseño para estructurar el código dentro de un único microservicio.
A. Entidades y objetos de valor
Dentro de un microservicio, los modelos se dividen en dos categorías:
- Entidades: Objetos que tienen una identidad única que persiste en el tiempo, incluso si sus atributos cambian. Los ejemplos incluyen
OrderoProduct. - Objetos de valor: Objetos que no tienen una identidad única y están definidos completamente por sus atributos. Son inmutables. Los ejemplos incluyen
ShippingAddressoProductDimensions. Si dos objetos de valor tienen los mismos atributos, se consideran iguales.
B. Agregados y raíces agregadas
Un Agregado es un grupo de entidades y objetos de valor asociados que se tratan como una sola unidad para los cambios de datos. Cada agregado tiene una Raíz de agregado, que es el único punto de entrada a través del cual los objetos externos pueden interactuar con el agregado. La raíz garantiza que se apliquen todas las invariantes (reglas) comerciales.
Por ejemplo, en el diagrama de arriba:
- En el Servicio de pedidos, el
Orderes la raíz agregada. ContieneOrderItem(Entidad) yShippingAddress(Objeto de valor). Los servicios externos no pueden modificarOrderItemdirectamente; deben llamar a un método en la raízOrder(por ejemplo,order.AddItem()), que valida que el pedido aún no se haya enviado.
C. Eventos de dominio
Un Evento de Dominio es algo que sucedió en el dominio que interesa a los expertos en negocios. Se utiliza para comunicar cambios en contextos acotados de forma asincrónica.
Cuando un Agregado ejecuta un comando, publica un Evento de Dominio (por ejemplo, OrderCreated). Otros servicios escuchan este evento y actualizan su estado en consecuencia, lo que garantiza una coherencia final sin dependencias de API sincrónicas.
3. Un ejemplo concreto: servicio de pedidos versus servicio de inventario
Veamos una implementación simplificada de DDD táctico en Go para el Servicio de pedidos, que muestra cómo Aggregate Root coordina las reglas comerciales.
package domain
import (
"errors"
"time"
)
// Value Object (Immutable, identity-less)
type ShippingAddress struct {
Street string
City string
ZipCode string
}
// Entity (Has identity, mutable)
type OrderItem struct {
ProductID string
Quantity int
Price float64
}
// Aggregate Root (Enforces transactional boundaries)
type Order struct {
ID string
Items []OrderItem
Address ShippingAddress
Status string
CreatedAt time.Time
}
// NewOrder creates a new Order Aggregate Root
func NewOrder(id string, address ShippingAddress) *Order {
return &Order{
ID: id,
Items: []OrderItem{},
Address: address,
Status: "PENDING",
CreatedAt: time.Now(),
}
}
// AddItem enforces business rules before mutating state
func (o *Order) AddItem(productID string, qty int, price float64) error {
if o.Status != "PENDING" {
return errors.New("cannot add items to a finalized or cancelled order")
}
if qty <= 0 {
return errors.New("quantity must be greater than zero")
}
o.Items = append(o.Items, OrderItem{
ProductID: productID,
Quantity: qty,
Price: price,
})
return nil
}
Cuando la orden se guarda correctamente, el servicio publica un evento en un intermediario de mensajes:
{
"event_id": "evt_98231",
"event_type": "OrderCreated",
"timestamp": "2026-06-26T00:15:00Z",
"payload": {
"order_id": "ord_5521",
"items": [
{ "product_id": "prod_88", "quantity": 2 }
]
}
}
El Servicio de inventario escucha este evento, actualiza su entidad StockLevel local y completa el flujo de forma asincrónica.
4. DDD estratégico versus táctico en microservicios
| Fase / Aspecto | DDD estratégica | DDD táctico |
|---|---|---|
| Alcance | Arquitectura del sistema global (macro) | Dentro de una base de código de servicio único (micro) |
| Objetivo principal | Trazar límites de servicios limpios y desacoplados | Modele una lógica empresarial rica y aplique invariantes |
| Conceptos clave | Contextos acotados, lenguaje ubicuo, mapa de contexto | Entidades, objetos de valor, agregados, eventos de dominio |
| Audiencia | Arquitectos, gerentes de producto, desarrolladores | Desarrolladores de software, revisores de código |
| Impacto en los Microservicios | Determina el número y alcance de los servicios | Determina las transacciones de la base de datos y la estructura del directorio |
Conclusión: Comience primero con DDD estratégico
Aplicar el diseño basado en dominios a los microservicios es una forma poderosa de garantizar que su arquitectura coincida con su estructura empresarial. Sin embargo, los equipos a menudo cometen el error de centrarse demasiado en patrones tácticos (como escribir interfaces de repositorio y objetos de valor) mientras ignoran el diseño estratégico.
Si no establece los límites correctos, ninguna cantidad de código táctico limpio evitará que su sistema se convierta en un monolito distribuido. Comience siempre con un mapeo estratégico: defina su lenguaje ubicuo, agrupe la lógica en contextos acotados, mapee los flujos de comunicación y deje que los límites de sus microservicios surjan naturalmente de esas definiciones.