0

Arquitectura de Software con C# 18: Casos de Uso en Arquitectura Hexagonal

Introducción a la clase

Hemos aprendido a implementar Agregados y Repositorios en Arquitectura Hexagonal. Ahora veremos cómo diseñar y estructurar Casos de Uso, que son fundamentales para orquestar la lógica de aplicación sin acoplarse a la infraestructura.

¿Qué obtendrás de esta clase?

  • Comprenderás qué son los Casos de Uso y cómo funcionan en Arquitectura Hexagonal.
  • Aprenderás a implementar Casos de Uso en C# de manera correcta.
  • Verás cómo hacer que los Casos de Uso sean totalmente independientes de la infraestructura.
  • Conocerás buenas prácticas y errores comunes al trabajar con Casos de Uso.

¿Qué es un Caso de Uso en Arquitectura Hexagonal?

Un Caso de Uso es una unidad de lógica de aplicación que:

  • Define una acción específica que el sistema debe ejecutar.
  • Orquesta la lógica de negocio sin conocer los detalles técnicos.
  • No tiene dependencias directas con bases de datos ni frameworks.
  • Usa Puertos (Interfaces) para comunicarse con la infraestructura.

Ejemplo de Casos de Uso en un sistema de pedidos

  • Crear un PedidoCrearPedidoUseCase
  • Cancelar un PedidoCancelarPedidoUseCase
  • Obtener un PedidoObtenerPedidoUseCase

Reglas clave:

  • No exponen detalles de la infraestructura.
  • Interactúan solo con interfaces (Puertos).
  • Contienen solo la lógica de aplicación, no la lógica del dominio.

Paso 1: Implementar un Puerto (Interfaz de Repositorio)

El Caso de Uso solo interactúa con una interfaz, sin importar qué base de datos o tecnología usemos.

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

Ventaja: Podemos cambiar la base de datos sin modificar los Casos de Uso.

Paso 2: Implementar un Caso de Uso – Crear Pedido

public class CrearPedidoUseCase
{
    private readonly IPedidoRepository _repository;

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

    public void Ejecutar(Pedido pedido)
    {
        // Reglas de negocio antes de guardar el pedido
        if (pedido.CalcularTotal() == 0)
        {
            throw new InvalidOperationException("El pedido no puede estar vacío.");
        }

        _repository.Guardar(pedido);
    }
}

Ventaja:

  • No depende de ninguna base de datos específica.
  • Aplica reglas de negocio antes de guardar el pedido.
  • Se puede probar fácilmente con un repositorio falso.

Paso 3: Implementar otro Caso de Uso – Obtener Pedido

public class ObtenerPedidoUseCase
{
    private readonly IPedidoRepository _repository;

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

    public Pedido Ejecutar(int id)
    {
        var pedido = _repository.ObtenerPorId(id);

        if (pedido == null)
        {
            throw new KeyNotFoundException("Pedido no encontrado.");
        }

        return pedido;
    }
}

Ventaja:

  • Manejo de errores centralizado.
  • Fácil de probar porque usa una interfaz de repositorio.

Paso 4: Implementar un Controlador que Use los Casos de Uso

El Controlador actúa como un Adaptador, llamando a los Casos de Uso.

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

    public PedidoController(CrearPedidoUseCase crearPedidoUseCase, ObtenerPedidoUseCase obtenerPedidoUseCase)
    {
        _crearPedidoUseCase = crearPedidoUseCase;
        _obtenerPedidoUseCase = obtenerPedidoUseCase;
    }

    [HttpPost]
    public IActionResult CrearPedido([FromBody] Pedido pedido)
    {
        try
        {
            _crearPedidoUseCase.Ejecutar(pedido);
            return Ok("Pedido creado exitosamente");
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }

    [HttpGet("{id}")]
    public IActionResult ObtenerPedido(int id)
    {
        try
        {
            var pedido = _obtenerPedidoUseCase.Ejecutar(id);
            return Ok(pedido);
        }
        catch (KeyNotFoundException ex)
        {
            return NotFound(ex.Message);
        }
    }
}

Ventaja:

  • El Controlador no sabe cómo se implementan los Casos de Uso.
  • Si cambiamos la base de datos, la API sigue funcionando igual.
  • Manejo de excepciones en un solo lugar.

Paso 5: Configurar la Inyección de Dependencias en ASP.NET Core

Para que ASP.NET Core use las implementaciones correctas, registramos las 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 Repositorio como Adaptador
builder.Services.AddScoped<IPedidoRepository, PedidoRepository>();

// Registrar Casos de Uso
builder.Services.AddScoped<CrearPedidoUseCase>();
builder.Services.AddScoped<ObtenerPedidoUseCase>();

var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
app.Run();

Ventaja:

  • Las dependencias se resuelven automáticamente.
  • Podemos cambiar los Repositorios sin afectar los Casos de Uso.

Errores comunes al trabajar con Casos de Uso

  • Incluir lógica de infraestructura dentro del Caso de Uso.
    • Solución: Los Casos de Uso solo deben llamar a interfaces (Puertos).
  • No manejar errores correctamente.
    • Solución: Los Casos de Uso deben lanzar excepciones y los Controladores deben manejarlas.
  • Inyectar implementaciones concretas en los Casos de Uso.
    • Solución: Siempre inyectar interfaces en lugar de clases concretas.

Cuestionario de Autoevaluación

  • ¿Por qué los Casos de Uso no deben depender de la infraestructura?
  • ¿Cómo se comunican los Casos de Uso con la infraestructura en Arquitectura Hexagonal?
  • ¿Por qué los Casos de Uso solo interactúan con interfaces?
  • ¿Cómo manejarías un error dentro de un Caso de Uso sin afectar la API?
  • ¿Cómo harías una prueba unitaria para un Caso de Uso sin usar una base de datos real?

Resumen de la Clase

  • Los Casos de Uso son la capa de aplicación que coordina la lógica de negocio sin acoplarse a la infraestructura.
  • Se comunican con la infraestructura a través de Puertos (Interfaces), asegurando el desacoplamiento.
  • Facilitan el mantenimiento y la testabilidad de la aplicación.
  • ASP.NET Core y la inyección de dependencias permiten registrar y resolver Casos de Uso de manera automática.

Próximo paso

En la siguiente clase veremos cómo implementar un ejemplo completo con Arquitectura Hexagonal y DDD en una API REST funcional en C#.

Fernando Sonego

Deja una respuesta

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