⏱️ Lectura: 11 min
Hablar de DDD en microservicios en 2026 ya no es una moda: es una condición de supervivencia. Después de una década de equipos que partieron el monolito en 50 servicios acoplados por HTTP sincrónico, la industria aprendió a golpes que descomponer sin un modelo de dominio sólido no produce una arquitectura distribuida, sino un monolito distribuido: lo peor de los dos mundos, con despliegues acoplados, latencias acumuladas y fallos en cascada.
📑 En este artículo
- Qué es DDD y por qué sigue vigente en 2026
- Bounded Contexts: auth, pedido y envío como ejemplo
- Aggregates y Repository Pattern con código real
- Domain Events: el pegamento entre contextos
- Eventual Consistency y por qué HTTP chaining es anti-patrón
- CQRS y Event Sourcing: cuándo sí, cuándo no
- Saga Pattern: orchestration vs choreography
- Outbox Pattern: evitar el abismo entre DB y broker
- Context Mapping: cómo se relacionan los contextos
- Tendencia 2026: agentes de IA como consumidores de eventos
- Errores comunes que convierten microservicios en monolito distribuido
- Cuándo NO usar DDD
- Preguntas frecuentes
- Referencias
Esta guía recorre, de forma práctica y profunda, cómo aplicar Domain-Driven Design en 2026 para que tus microservicios sean realmente autónomos. Veremos bounded contexts, aggregates, domain events, eventual consistency, CQRS, Event Sourcing, Sagas, el patrón Outbox, context mapping y una tendencia nueva: los agentes de IA como consumidores y emisores de eventos de dominio de primera clase. Todos los ejemplos compilan y se pueden llevar a producción.
Qué es DDD y por qué sigue vigente en 2026
Domain-Driven Design, popularizado por Eric Evans en 2003 y refinado por Vaughn Vernon en Implementing Domain-Driven Design, propone que el código debe reflejar el lenguaje del negocio (ubiquitous language) y que la complejidad se domestica modelando reglas, no tablas. DDD tiene dos capas: estratégica (cómo partir el sistema en contextos) y táctica (cómo expresar el dominio con aggregates, entidades, value objects y domain events).
En 2026, con sistemas que combinan microservicios, event streaming y agentes de IA tomando decisiones sobre eventos de dominio, DDD en microservicios es la única disciplina comprobada para evitar que la complejidad te aplaste. No sustituye a la arquitectura hexagonal ni a la event-driven: las complementa.
Bounded Contexts: auth, pedido y envío como ejemplo
Un bounded context es una frontera lingüística y técnica: dentro de ella, cada término tiene un único significado. La trampa clásica es creer que “Cliente” es el mismo objeto en todos los servicios. No lo es.
- Auth: un User tiene email, hash de contraseña, roles y MFA. No sabe nada de pedidos ni direcciones.
- Pedido: un Customer tiene id, nombre para factura y preferencias. No sabe de contraseñas.
- Envío: un Recipient tiene dirección, coordenadas y ventana horaria. Ni siquiera le importa si pagó.
Son tres modelos distintos, cada uno con su base de datos, su repositorio y su deploy independiente. Compartir una tabla users entre los tres es el pecado original que transforma microservicios en un monolito distribuido.
Aggregates y Repository Pattern con código real
El aggregate es el límite de consistencia: un grupo de objetos que se modifican como unidad atómica. Tiene una raíz (aggregate root) que es el único punto de entrada. Regla de oro: una transacción modifica un solo aggregate.
Python con FastAPI y SQLAlchemy
from dataclasses import dataclass, field
from typing import List
from uuid import UUID, uuid4
@dataclass
class OrderLine:
sku: str
quantity: int
unit_price: float
@dataclass
class OrderConfirmed:
order_id: UUID
customer_id: UUID
@dataclass
class Order:
id: UUID
customer_id: UUID
status: str = "draft"
lines: List[OrderLine] = field(default_factory=list)
events: List[object] = field(default_factory=list)
def add_line(self, sku: str, qty: int, price: float) -> None:
if self.status != "draft":
raise ValueError("Order locked")
self.lines.append(OrderLine(sku, qty, price))
def confirm(self) -> None:
if not self.lines:
raise ValueError("Cannot confirm empty order")
self.status = "confirmed"
self.events.append(OrderConfirmed(self.id, self.customer_id))
class OrderRepository:
def __init__(self, session):
self.session = session
def add(self, order: Order) -> None:
self.session.add(order)
def get(self, order_id: UUID) -> Order:
return self.session.get(Order, order_id)
C# con .NET 9
public enum OrderStatus { Draft, Confirmed, Cancelled }
public class Order : AggregateRoot
{
public Guid Id { get; private set; }
public Guid CustomerId { get; private set; }
private readonly List<OrderLine> _lines = new();
public IReadOnlyList<OrderLine> Lines => _lines;
public OrderStatus Status { get; private set; } = OrderStatus.Draft;
public void AddLine(string sku, int qty, decimal price)
{
if (Status != OrderStatus.Draft)
throw new DomainException("Order is locked");
_lines.Add(new OrderLine(sku, qty, price));
}
public void Confirm()
{
if (_lines.Count == 0)
throw new DomainException("Empty order");
Status = OrderStatus.Confirmed;
AddDomainEvent(new OrderConfirmed(Id, CustomerId));
}
}
El repositorio siempre opera sobre el aggregate completo, nunca sobre hijos sueltos. Si tu OrderLineRepository tiene un Update(), ya rompiste el invariante.
Domain Events: el pegamento entre contextos
Un domain event es un hecho inmutable en pasado: OrderConfirmed, PaymentCaptured, ShipmentDispatched. Se publican desde el aggregate y otros contextos reaccionan. Así, el servicio de Pedido no llama al de Envío: emite OrderConfirmed y quien quiera reaccionar escucha.
💡 Tip: nombra los eventos en pasado y con el lenguaje del negocio.UserUpdatedno dice nada;EmailVerifiedsí.
Eventual Consistency y por qué HTTP chaining es anti-patrón
La tentación en microservicios es encadenar llamadas: el servicio A hace POST al B, que hace POST al C. Tres servicios, tres redes, tres timeouts. Si uno falla, el usuario recibe un 500 opaco y tu sistema queda inconsistente a la mitad.
La alternativa es eventual consistency sobre eventos: A confirma su transacción local, publica un evento, y B y C reaccionan de forma asíncrona. Se pierde la ilusión de “todo pasa al mismo tiempo”, pero se gana resiliencia real. Chris Richardson lleva años insistiendo en esto en microservices.io y sigue siendo la pieza que más cuesta aceptar.
CQRS y Event Sourcing: cuándo sí, cuándo no
CQRS (Command Query Responsibility Segregation) separa el modelo de escritura del de lectura. Escribes contra aggregates; lees contra vistas materializadas optimizadas. Event Sourcing va un paso más allá: el estado no se guarda, se reconstruye reproduciendo eventos.
Cuándo SÍ
- CQRS: dashboards con agregaciones pesadas, APIs con patrones de lectura muy distintos a los de escritura.
- Event Sourcing: dominios con auditoría obligatoria (banca, salud, trazabilidad logística) o necesidad de “viajar en el tiempo”.
Cuándo NO
- CRUDs básicos o dominios con lógica pobre. Microsoft Learn advierte: Event Sourcing introduce complejidad operativa alta y no vale la pena si no hay reglas que la justifiquen.
Saga Pattern: orchestration vs choreography
Una saga coordina una transacción que cruza varios servicios. Hay dos estilos:
- Coreografía: cada servicio reacciona a eventos y emite los suyos. Acoplamiento bajo, pero cuesta rastrear el flujo.
- Orquestación: un orquestador central dicta los pasos y las compensaciones. Más fácil de depurar, pero concentra lógica.
sequenceDiagram
participant P as Pedido
participant I as Inventario
participant G as Pagos
participant E as Envio
P->>I: ReservarStock
I-->>P: StockReservado
P->>G: CobrarPago
G-->>P: PagoFallido
P->>I: LiberarStock
P->>P: PedidoCancelado
Regla práctica: si la saga tiene más de 4 pasos o lógica condicional compleja, orquestación. Si es un pipeline lineal simple, coreografía.
Outbox Pattern: evitar el abismo entre DB y broker
Publicar un evento en Kafka y commitear la transacción son dos operaciones que no pueden ser atómicas. Si el broker falla después del commit, perdés el evento. El patrón Outbox resuelve esto guardando el evento en una tabla outbox dentro de la misma transacción del aggregate, y un proceso separado lo publica y marca como enviado.
-- Dentro de la misma transacción que guarda el Order
INSERT INTO outbox (id, aggregate_type, event_type, payload, created_at)
VALUES (gen_random_uuid(), 'Order', 'OrderConfirmed', :payload, now());
Un relay (Debezium, o un worker propio) lee la tabla y publica a Kafka con at-least-once. Los consumidores deben ser idempotentes.
⚠️ Ojo: sin Outbox vas a tener inconsistencias que descubrirás en producción, normalmente un viernes. No lo omitas.
Context Mapping: cómo se relacionan los contextos
Los contextos se hablan, pero no de cualquier manera. DDD define relaciones típicas:
- Anti-Corruption Layer (ACL): traduce modelos externos a tu lenguaje. Clave para integrar sistemas legacy.
- Shared Kernel: un módulo compartido pequeño. Frágil, úsalo con disciplina.
- Customer/Supplier: un contexto depende de otro y negocia requisitos.
- Conformist: aceptas el modelo del otro tal cual (normalmente cuando el otro es más poderoso).
- Open Host Service: publicas un contrato estable para varios consumidores, típicamente vía API o eventos con AsyncAPI.
Tendencia 2026: agentes de IA como consumidores de eventos
La novedad fuerte de 2026 es que los agentes de IA dejaron de ser clientes HTTP puntuales: ahora escuchan topics de Kafka como ciudadanos de primera clase. Un agente de “riesgo de fraude” consume PaymentCaptured, enriquece con señales, y emite FraudAssessed que el contexto de Pedido consume.
Para que esto no sea un caos se combinan tres piezas: AsyncAPI como contrato del canal, Schema Registry (Confluent o Apicurio) para versionar los payloads con compatibilidad hacia atrás, y políticas claras de consumer groups para que el agente reciba eventos sin bloquear a los servicios tradicionales. El agente no es especial a nivel de mensajería: sigue las mismas reglas de idempotencia y evolución de esquemas que cualquier microservicio.
Errores comunes que convierten microservicios en monolito distribuido
- Nano-microservicios: un servicio por entidad (
user-service,address-service,phone-service). Explosión de overhead sin beneficio. - Base de datos compartida: el atajo que destruye la autonomía. Si dos servicios leen la misma tabla, son el mismo servicio con despliegues separados.
- HTTP chaining sincrónico: pedido llama a inventario que llama a pagos. Latencia y fallos se multiplican.
- Eventos CRUD: publicar
UserUpdatedcon el objeto entero. Empaquetas acoplamiento dentro del evento. - Sagas implícitas: pasos coreografiados que nadie documentó. Nadie entiende el flujo completo.
- Versionado ad-hoc de eventos: romper compatibilidad sin Schema Registry y ver caer a todos los consumidores.
Cuándo NO usar DDD
DDD es caro. No lo apliques si:
- El sistema es un CRUD sin reglas de negocio relevantes.
- El equipo es pequeño (2-4 personas) y un monolito modular basta.
- El dominio aún no está entendido; primero hacé un spike, recién después modelás.
- La presión de time-to-market es tal que no hay espacio para event storming ni glosario.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿DDD obliga a usar microservicios?
No. DDD se puede aplicar perfectamente en un monolito modular, y de hecho es recomendable empezar así. Los bounded contexts se vuelven módulos con paquetes y bases de datos lógicas separadas; cuando uno crece o necesita escalar distinto, se extrae como microservicio.
¿Cuál es la diferencia entre aggregate y entidad?
Toda raíz de aggregate es una entidad, pero no toda entidad es raíz. El aggregate define la frontera de consistencia transaccional: un solo aggregate se modifica por transacción, y el acceso externo pasa solo por su raíz.
¿Necesito Event Sourcing para hacer DDD?
No. Event Sourcing es una técnica de persistencia opcional. DDD funciona con bases relacionales clásicas; los domain events se pueden publicar sin guardar la historia completa, usando Outbox y persistencia tradicional del estado actual.
¿Saga reemplaza a las transacciones distribuidas?
Sí, y es deliberado. Las transacciones distribuidas (2PC) no escalan ni sobreviven particiones de red. Las sagas aceptan la consistencia eventual y definen compensaciones explícitas cuando un paso falla, lo que encaja con la realidad de los sistemas distribuidos.
¿Cómo versiono los domain events sin romper consumidores?
Con un Schema Registry y reglas de compatibilidad hacia atrás: agregá campos opcionales, nunca renombres ni cambies tipos. Si necesitás un cambio incompatible, publicá un nuevo topic (orders.v2) y mantené ambos hasta que todos los consumidores migren.
¿Los agentes de IA necesitan tratamiento especial en DDD?
No en el modelo de dominio: son consumidores y productores de eventos como cualquier otro servicio. Sí en operaciones: suelen necesitar colas dedicadas, límites de concurrencia y observabilidad extra porque su latencia y costo varían mucho más que un microservicio tradicional.
Referencias
- Martin Fowler — Domain-Driven Design — Artículos fundacionales sobre DDD y sus patrones estratégicos.
- Microsoft Learn — DDD-oriented microservice — Guía oficial con ejemplos en .NET para aggregates, CQRS y eventos.
- microservices.io (Chris Richardson) — Catálogo canónico de patrones: Saga, Outbox, CQRS y API Composition.
- DDD Europe — Conferencia de referencia con charlas actualizadas cada año sobre modelado estratégico y táctico.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
0 Comentarios