0

Arquitectura de Software con C# 15: Cómo Integrar DDD en Arquitectura Hexagonal

Introducción a la clase

Ahora que comprendemos la Arquitectura Hexagonal (Ports & Adapters), vamos a integrar Domain-Driven Design (DDD) dentro de este enfoque.

DDD nos ayudará a modelar el dominio del negocio correctamente, asegurando que nuestra arquitectura sea flexible y que el código refleje las reglas del negocio de manera clara.

¿Qué obtendrás de esta clase?

  • Aprenderás cómo aplicar DDD dentro de la Arquitectura Hexagonal.
  • Verás cómo organizar Entidades, Agregados y Value Objects en un proyecto Hexagonal.
  • Implementarás un ejemplo en C# con una estructura basada en DDD.
  • Comprenderás cómo se integran Repositorios y Casos de Uso en esta arquitectura.

Relación entre Arquitectura Hexagonal y DDD

En la Arquitectura Hexagonal, DDD se aplica en la Capa de Dominio, asegurando que la lógica de negocio esté completamente aislada de la infraestructura.

📌 DDD dentro de la Arquitectura Hexagonal:

  • Entidades, Value Objects y Agregados → Se encuentran en el núcleo del dominio.
  • Puertos → Se definen como interfaces para interactuar con la infraestructura.
  • Adaptadores → Implementan los puertos en la capa de infraestructura.
  • Casos de Uso → Coordinan la lógica de aplicación sin depender de detalles técnicos.
 ┌──────────────────────────────┐
 │   Adaptador de Base de Datos │ (Infraestructura)
 ├──────────────────────────────┤
 │          Puerto              │ ← Interfaz de Repositorio  
 ├──────────────────────────────┤
 │        Núcleo del Dominio    │ ← Entidades, Agregados, Value Objects  
 ├──────────────────────────────┤
 │          Puerto              │ ← Interfaz de Servicio Externo  
 ├──────────────────────────────┤
 │  Adaptador de API REST       │ (Infraestructura)
 └──────────────────────────────┘

Paso 1: Implementar el Núcleo del Dominio con DDD

La Capa de Dominio contiene Entidades, Agregados y Value Objects.

Entidad Pedido con un Agregado

public class Pedido
{
    public int Id { get; private set; }
    public string Cliente { get; private set; }
    private readonly List<Producto> _productos = new List<Producto>();

    public IReadOnlyCollection<Producto> Productos => _productos.AsReadOnly();

    public Pedido(int id, string cliente)
    {
        Id = id;
        Cliente = cliente;
    }

    public void AgregarProducto(Producto producto)
    {
        _productos.Add(producto);
    }

    public decimal CalcularTotal()
    {
        return _productos.Sum(p => p.Precio);
    }
}

Paso 2: Implementar un Value Object

public class Direccion
{
    public string Calle { get; }
    public string Ciudad { get; }
    public string CodigoPostal { get; }

    public Direccion(string calle, string ciudad, string codigoPostal)
    {
        Calle = calle;
        Ciudad = ciudad;
        CodigoPostal = codigoPostal;
    }

    public override bool Equals(object obj)
    {
        if (obj is Direccion other)
        {
            return Calle == other.Calle && Ciudad == other.Ciudad && CodigoPostal == other.CodigoPostal;
        }
        return false;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Calle, Ciudad, CodigoPostal);
    }
}

Paso 3: Definir un Puerto (Interfaz del Repositorio)

Un Puerto define cómo el dominio interactúa con la infraestructura sin conocer detalles técnicos.

public interface IPedidoRepository
{
    void Guardar(Pedido pedido);
    Pedido ObtenerPorId(int id);
}

Paso 4: Implementar un Caso de Uso con DDD

public class CrearPedidoUseCase
{
    private readonly IPedidoRepository _repository;

    public CrearPedidoUseCase(IPedidoRepository repository)
    {
        _repository = repository;
    }

    public void Ejecutar(Pedido pedido)
    {
        _repository.Guardar(pedido);
    }
}

Paso 5: Implementar un Adaptador de Base de Datos con EF Core

Este adaptador implementa la interfaz del repositorio (Puerto) usando Entity Framework Core.

public class PedidoRepository : IPedidoRepository
{
    private readonly AppDbContext _context;

    public PedidoRepository(AppDbContext context)
    {
        _context = context;
    }

    public void Guardar(Pedido pedido)
    {
        _context.Pedidos.Add(pedido);
        _context.SaveChanges();
    }

    public Pedido ObtenerPorId(int id)
    {
        return _context.Pedidos.FirstOrDefault(p => p.Id == id);
    }
}

Paso 6: Implementar un Adaptador de API REST

El controlador REST actúa como adaptador, permitiendo que la API se comunique con el dominio a través de los Casos de Uso.

[ApiController]
[Route("api/pedidos")]
public class PedidoController : ControllerBase
{
    private readonly CrearPedidoUseCase _crearPedidoUseCase;
    private readonly IPedidoRepository _pedidoRepository;

    public PedidoController(CrearPedidoUseCase crearPedidoUseCase, IPedidoRepository pedidoRepository)
    {
        _crearPedidoUseCase = crearPedidoUseCase;
        _pedidoRepository = pedidoRepository;
    }

    [HttpPost]
    public IActionResult CrearPedido([FromBody] Pedido pedido)
    {
        _crearPedidoUseCase.Ejecutar(pedido);
        return Ok("Pedido creado exitosamente");
    }

    [HttpGet("{id}")]
    public IActionResult ObtenerPedido(int id)
    {
        var pedido = _pedidoRepository.ObtenerPorId(id);
        if (pedido == null)
            return NotFound();

        return Ok(pedido);
    }
}

Beneficios de Integrar DDD en Arquitectura Hexagonal

  • Desacoplamiento total → Separa completamente la lógica del negocio de la infraestructura.
  • Alta flexibilidad → Se pueden cambiar bases de datos o proveedores sin afectar el dominio.
  • Modelado claro del dominio → La lógica del negocio es más expresiva y mantenible.
  • Fácil de probar → Se pueden escribir pruebas unitarias sin depender de la base de datos.

Errores comunes al integrar DDD en Arquitectura Hexagonal

  • No usar interfaces para definir los Puertos.
    Solución: Siempre definir interfaces en la Capa de Aplicación y solo implementarlas en Infraestructura.
  • Mezclar infraestructura con el dominio.
    Solución: Mantener la lógica de negocio en la Capa de Dominio y la persistencia en la Capa de Infraestructura.
  • No encapsular correctamente los Agregados.
    Solución: Evitar modificar las Entidades de un Agregado directamente fuera del Agregado.

Cuestionario de Autoevaluación

  • ¿Cómo encaja DDD dentro de la Arquitectura Hexagonal?
  • ¿Por qué es importante usar Puertos y Adaptadores en este enfoque?
  • ¿Cómo desacoplar la infraestructura de la lógica de negocio con interfaces?
  • ¿Qué ventajas tiene encapsular Entidades dentro de un Agregado?
  • ¿Cómo probarías un Caso de Uso sin necesidad de una base de datos?

Resumen de la Clase

  • DDD se integra en Arquitectura Hexagonal estructurando el dominio en Entidades, Value Objects y Agregados.
  • Los Puertos (Interfaces) permiten que el núcleo del negocio no dependa de tecnologías externas.
  • Los Adaptadores implementan los puertos y conectan la infraestructura con el dominio.
  • Este enfoque permite construir software modular, escalable y altamente flexible.

Próximo paso

En la siguiente clase veremos cómo desacoplar la infraestructura con Puertos y Adaptadores en Arquitectura Hexagonal.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *