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 Pedido →
CrearPedidoUseCase
- Cancelar un Pedido →
CancelarPedidoUseCase
- Obtener un Pedido →
ObtenerPedidoUseCase
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#.