Introducción a la clase
Hemos aprendido cómo integrar Domain-Driven Design (DDD) en Arquitectura Hexagonal. Ahora profundizaremos en cómo desacoplar la infraestructura utilizando Puertos y Adaptadores, asegurando que el núcleo de negocio no dependa de tecnologías externas.
¿Qué obtendrás de esta clase?
- Aprenderás cómo aplicar Puertos y Adaptadores para desacoplar la infraestructura.
- Implementarás un ejemplo práctico en C# para aislar la lógica del negocio.
- Verás cómo cambiar bases de datos o proveedores sin afectar la aplicación.
- Conocerás buenas prácticas y errores comunes al desacoplar la infraestructura.
¿Por qué desacoplar la infraestructura?
Problema: En aplicaciones monolíticas, la lógica de negocio suele estar fuertemente acoplada a la base de datos, APIs externas o frameworks específicos.
Solución: La Arquitectura Hexagonal separa el núcleo de la aplicación de los detalles de infraestructura mediante Puertos y Adaptadores.
Cómo funcionan los Puertos y Adaptadores
- Puertos (Ports) → Son interfaces que definen cómo la aplicación interactúa con el mundo exterior (base de datos, servicios externos, APIs).
- Adaptadores (Adapters) → Son implementaciones concretas de los puertos que conectan la infraestructura con el dominio.
Ejemplo en un sistema de pedidos:
- El Puerto define un contrato para interactuar con la base de datos (
IPedidoRepository
). - Un Adaptador lo implementa para conectarse a Entity Framework Core (
PedidoRepository
).
┌──────────────────────────────┐
│ Adaptador de Base de Datos │ (Infraestructura)
├──────────────────────────────┤
│ Puerto │ ← Interfaz de Repositorio
├──────────────────────────────┤
│ Núcleo del Dominio │ ← Entidades, Agregados, Casos de Uso
├──────────────────────────────┤
│ Puerto │ ← Interfaz de API Externa
├──────────────────────────────┤
│ Adaptador de API REST │ (Infraestructura)
└──────────────────────────────┘
Paso 1: Definir un Puerto para la Persistencia (Repositorio)
El Puerto es una interfaz en la Capa de Aplicación que define la funcionalidad esperada.
public interface IPedidoRepository
{
void Guardar(Pedido pedido);
Pedido ObtenerPorId(int id);
}
Ventaja: El código de la aplicación solo depende de interfaces, no de implementaciones concretas.
Paso 2: Implementar un Adaptador de Base de Datos con EF Core
El Adaptador implementa la interfaz y usa Entity Framework Core para la persistencia.
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);
}
}
Ventaja: Podemos cambiar la base de datos sin afectar el dominio ni los casos de uso.
Paso 3: Implementar un Caso de Uso en la Capa de Aplicación
El Caso de Uso no conoce la implementación del repositorio, solo la interfaz.
public class CrearPedidoUseCase
{
private readonly IPedidoRepository _repository;
public CrearPedidoUseCase(IPedidoRepository repository)
{
_repository = repository;
}
public void Ejecutar(Pedido pedido)
{
_repository.Guardar(pedido);
}
}
Ventaja: La lógica de negocio no tiene dependencias con la infraestructura.
Paso 4: Crear un Adaptador para la API REST (Controlador en ASP.NET Core)
El Controlador actúa como Adaptador, permitiendo que la API REST se comunique con la aplicación.
[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);
}
}
Ventaja: Permite que cualquier interfaz de usuario (API REST, consola, UI web) interactúe con la aplicación sin afectar la lógica de negocio.
Paso 5: Configurar la Inyección de Dependencias en ASP.NET Core
Para que .NET Core use la implementación correcta de IPedidoRepository
, configuramos el contenedor de dependencias en Program.cs
.
var builder = WebApplication.CreateBuilder(args);
// Configurar Entity Framework Core con SQL Server
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Registrar los adaptadores en el contenedor de inyección de dependencias
builder.Services.AddScoped<IPedidoRepository, PedidoRepository>();
builder.Services.AddScoped<CrearPedidoUseCase>();
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Ventaja: Permite que las dependencias sean resueltas automáticamente, evitando dependencias directas entre capas.
Errores comunes al usar Puertos y Adaptadores
- No definir interfaces para la infraestructura.
Solución: Crear siempre interfaces en la Capa de Aplicación y evitar referencias directas a tecnologías. - Acceder directamente a la base de datos desde el Caso de Uso.
Solución: Usar el repositorio como intermediario. - No usar inyección de dependencias.
Solución: Registrar las dependencias en el contenedor de .NET Core (Program.cs
).
Cuestionario de Autoevaluación
- ¿Cuál es el propósito de los Puertos en Arquitectura Hexagonal?
- ¿Por qué se usan Adaptadores en lugar de implementar la lógica directamente?
- ¿Cómo permite esta arquitectura cambiar tecnologías sin afectar el dominio?
- ¿Cuál es la diferencia entre un Caso de Uso y un Adaptador?
- ¿Cómo probarías un Caso de Uso sin necesidad de una base de datos real?
Resumen de la Clase
- Los Puertos y Adaptadores permiten desacoplar la lógica de negocio de la infraestructura.
- Los Puertos son interfaces que definen cómo el dominio interactúa con la infraestructura.
- Los Adaptadores implementan los Puertos para conectar la aplicación con bases de datos, APIs o interfaces de usuario.
- Este enfoque facilita la testabilidad y la flexibilidad para cambiar tecnologías sin afectar la aplicación.
Próximo paso
En la siguiente clase veremos cómo implementar Agregados y Repositorios en Arquitectura Hexagonal con C#.