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.