0

Arquitectura de Software con C# 16: Desacoplando la Infraestructura con Puertos y Adaptadores en Arquitectura Hexagonal

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

  1. Puertos (Ports) → Son interfaces que definen cómo la aplicación interactúa con el mundo exterior (base de datos, servicios externos, APIs).
  2. 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#.

Fernando Sonego

Deja una respuesta

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