Domain-Driven Design Aplicado a .NET 9
[ Domain-Driven Design Aplicado a .NET 9 ]

DDD Estratégico: Modelando el Dominio

En el capítulo anterior, introdujimos los fundamentos de Domain-Driven Design (DDD) y su relevancia para proyectos modernos con .NET 9. Ahora, nos adentramos en el DDD estratégico, que se centra en modelar el dominio a un nivel macro, dividiéndolo en partes manejables y definiendo cómo interactúan. Como líder de KitsuneData Integral Solutions, he aplicado estas técnicas para alinear sistemas complejos con las necesidades de negocio, y en este capítulo te guiaré para que hagas lo mismo. Exploraremos el lenguaje ubicuo, los contextos acotados, el mapeo de contextos, herramientas como PlantUML, y un ejemplo práctico basado en un sistema de gestión empresarial.

2.1 Lenguaje ubicuo

El lenguaje ubicuo es el corazón de DDD. Es un vocabulario compartido entre desarrolladores, expertos del dominio (como analistas de negocio o gerentes de producto) y otros interesados, usado en conversaciones, documentación y código. Este lenguaje asegura que todos hablen de lo mismo, evitando malentendidos. Por ejemplo, si en un sistema de gestión empresarial el término “orden” se refiere a una solicitud de compra, ese término debe usarse consistentemente en lugar de sinónimos como “pedido” o “solicitud”.

Cómo construir un lenguaje ubicuo
  1. Colabora con expertos del dominio: Organiza talleres (como Event Storming, ver Aspectos importantes antes de seguir el viaje) para identificar términos clave.
  2. Documenta el vocabulario: Usa una wiki o herramienta como Confluence para registrar definiciones. Por ejemplo, en KitsuneData, documentamos términos como “cliente” y “orden” en un glosario compartido.
  3. Refleja el lenguaje en el código: Los nombres de clases, métodos y variables deben coincidir con el vocabulario del negocio.

Ejemplo en C#:

public class Orden
{
    public Guid Id { get; private set; }
    public Guid ClienteId { get; private set; }
    public DateTime FechaCreacion { get; private set; }

    public Orden(Guid clienteId)
    {
        Id = Guid.NewGuid();
        ClienteId = clienteId;
        FechaCreacion = DateTime.UtcNow;
    }
}

Aquí, la clase Orden usa el término del dominio, no algo genérico como Request.

2.2 Contextos acotados

Un contexto acotado es una división lógica del dominio que delimita un modelo específico con su propio lenguaje ubicuo. En sistemas complejos, un solo modelo para todo el dominio es inmanejable, por lo que DDD propone dividir el dominio en contextos más pequeños. Por ejemplo, en un sistema de gestión empresarial, “Gestión de Órdenes” y “Facturación” son contextos acotados distintos, cada uno con sus propias reglas y términos.

Beneficios de los contextos acotados
  • Reducen la complejidad: Cada contexto tiene un modelo simplificado.
  • Evitan ambigüedades: Un término como “factura” puede tener significados diferentes en cada contexto.
  • Facilitan microservicios: Cada contexto puede mapearse a un microservicio en .NET 9 (ver Capítulo 5).
Cómo definir un contexto acotado
  1. Identifica subdominios durante talleres de modelado.
  2. Asigna un lenguaje ubicuo específico a cada contexto.
  3. Define límites claros para evitar solapamientos.

Ejemplo: En un sistema de gestión empresarial, el contexto “Gestión de Órdenes” maneja la creación y seguimiento de órdenes, mientras que “Facturación” gestiona facturas y pagos. Aunque ambos usan el término “cliente”, sus significados y atributos pueden diferir.

2.3 Mapeo de contextos

Los contextos acotados no operan en aislamiento; deben interactuar. El mapeo de contextos define cómo se relacionan, usando patrones como:

  • Cliente-Proveedor: Un contexto (proveedor) ofrece una API que otro (cliente) consume.
  • Integración por eventos: Un contexto publica eventos (e.g., “Orden Creada”) que otro consume.
  • Conformista: Un contexto adopta el modelo de otro sin modificarlo.

Ejemplo en C# (publicación de un evento en .NET 9):

public record OrdenCreadaEvent(Guid OrdenId, Guid ClienteId, DateTime FechaCreacion);

public interface IEventoPublisher
{
    Task PublicarAsync<T>(T evento);
}

public class OrdenService
{
    private readonly IEventoPublisher _publisher;

    public OrdenService(IEventoPublisher publisher)
    {
        _publisher = publisher;
    }

    public async Task CrearOrdenAsync(Guid clienteId)
    {
        var orden = new Orden(clienteId);
        await _publisher.PublicarAsync(new OrdenCreadaEvent(orden.Id, clienteId, orden.FechaCreacion));
    }
}

Este código muestra cómo el contexto “Gestión de Órdenes” publica un evento que puede ser consumido por el contexto “Facturación”.

2.4 Herramientas para modelado

Las herramientas de modelado visual ayudan a representar contextos acotados y sus relaciones. PlantUML es una opción popular por su simplicidad y capacidad de integrarse con flujos de desarrollo. Permite crear diagramas usando texto, lo que facilita su versionado en Git.

Ejemplo de PlantUML (mapeo de contextos):

PlantUML y diagramas

Este diagrama muestra cómo el evento OrdenCreadaEvent conecta los contextos “Gestión de Órdenes” y “Facturación”. En KitsuneData, usamos PlantUML para visualizar modelos durante talleres, asegurando que todos los interesados entiendan las relaciones.

2.5 Ejemplo práctico

Apliquemos los conceptos a un sistema de gestión empresarial, similar al caso de e-commerce del Capítulo 1, pero centrado en órdenes y facturación.

Escenario
  • Dominio: Sistema para una empresa que gestiona órdenes de compra y facturación.
  • Contextos acotados:
    • Gestión de Órdenes: Maneja la creación, actualización y seguimiento de órdenes.
    • Facturación: Genera facturas basadas en órdenes confirmadas.
  • Lenguaje ubicuo:
    • En “Gestión de Órdenes”: “orden”, “cliente”, “estado”.
    • En “Facturación”: “factura”, “cliente”, “monto total”.
  • Interacciones: Cuando una orden es confirmada, se publica un evento OrdenConfirmadaEvent que desencadena la creación de una factura.
Implementación inicial en C#

Ampliemos el ejemplo de la sección 2.3:

public class Orden
{
    public Guid Id { get; private set; }
    public Guid ClienteId { get; private set; }
    public string Estado { get; private set; }
    public List<LineaOrden> Lineas { get; private set; } = new();
    public decimal Total { get; private set; }

    private Orden() { } // Para EF Core

    public Orden(Guid clienteId)
    {
        Id = Guid.NewGuid();
        ClienteId = clienteId;
        Estado = "Creada";
    }

    public void Confirmar()
    {
        if (Lineas.Count == 0) throw new InvalidOperationException("La orden no tiene líneas.");
        Estado = "Confirmada";
    }
}

public record LineaOrden(Guid ProductoId, int Cantidad, decimal Precio)
{
    public decimal Subtotal => Cantidad * Precio;
}

public record OrdenConfirmadaEvent(Guid OrdenId, Guid ClienteId, decimal Total);

public class OrdenService
{
    private readonly IEventoPublisher _publisher;

    public OrdenService(IEventoPublisher publisher)
    {
        _publisher = publisher;
    }

    public async Task ConfirmarOrdenAsync(Guid ordenId)
    {
        var orden = /* Obtener orden desde repositorio */;
        orden.Confirmar();
        await _publisher.PublicarAsync(new OrdenConfirmadaEvent(orden.Id, orden.ClienteId, orden.Total));
    }
}
Diagrama de contexto

El siguiente diagrama PlantUML muestra la relación entre los contextos:

DDD Estratégico: Modelando el Dominio
Lecciones del ejemplo
  • Lenguaje ubicuo: Usamos “orden” y “factura” consistentemente.
  • Contextos acotados: “Gestión de Órdenes” y “Facturación” tienen modelos separados.
  • Mapeo de contextos: El evento OrdenConfirmadaEvent conecta ambos contextos.
  • Herramientas: PlantUML visualiza la arquitectura, facilitando la comunicación.

En los capítulos siguientes, implementaremos este ejemplo con persistencia (Capítulo 4) y microservicios (Capítulo 5). Este capítulo ha cubierto las bases del DDD estratégico, preparándote para modelar dominios complejos. En el próximo capítulo, exploraremos los patrones tácticos de DDD, como entidades y agregados, con ejemplos en C#.