0

Arquitectura de Software con C# 19: Ejemplo Práctico – API REST Completa con Arquitectura Hexagonal y DDD

Introducción a la clase

Hemos aprendido los conceptos clave de Arquitectura Hexagonal con DDD, incluyendo Agregados, Repositorios, Casos de Uso y Adaptadores. Ahora vamos a construir una API REST funcional en C# con ASP.NET Core y Entity Framework Core, aplicando todo lo aprendido.

¿Qué obtendrás de esta clase?

  • Construirás una API REST con Arquitectura Hexagonal y DDD.
  • Implementarás persistencia con Entity Framework Core.
  • Aplicarás Casos de Uso y Repositorios desacoplados.
  • Configurarás ASP.NET Core para manejar la inyección de dependencias.

Paso 1: Estructura del Proyecto

Organizaremos el código en capas siguiendo la Arquitectura Hexagonal.

/PedidosAPI
│── /Core
│   ├── /Dominio
│   │   ├── Entidades
│   │   ├── ValueObjects
│   │   ├── Agregados
│   ├── /Aplicacion
│   │   ├── CasosDeUso
│   │   ├── Interfaces (Puertos)
│── /Infraestructura
│   ├── Persistencia
│   ├── Repositorios (Adaptadores)
│── /Presentacion
│   ├── Controladores (Adaptadores)
│── /Configuracion
│── Program.cs
│── appsettings.json

Reglas clave:

  • La lógica de negocio está en la Capa de Dominio.
  • Los Casos de Uso están en la Capa de Aplicación y solo usan interfaces.
  • Los Adaptadores (Controladores y Repositorios) están en Infraestructura y Presentación.

Paso 2: Implementar el Núcleo del Dominio (Capa de Core – Dominio)

Entidad Pedido como 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);
    }
}

Value Object «Dirección»

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;
    }
}

Paso 3: Definir los Puertos (Interfaces en la Capa de Aplicación)

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

Paso 4: Implementar Casos de Uso en la Capa de Aplicación

Caso de Uso – Crear Pedido

public class CrearPedidoUseCase
{
    private readonly IPedidoRepository _repository;

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

    public void Ejecutar(Pedido pedido)
    {
        if (pedido.CalcularTotal() == 0)
        {
            throw new InvalidOperationException("El pedido no puede estar vacío.");
        }

        _repository.Guardar(pedido);
    }
}

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;
    }
}

Paso 5: Implementar el Adaptador de Infraestructura (Repositorio con EF Core)

Configurar DbContext

public class AppDbContext : DbContext
{
    public DbSet<Pedido> Pedidos { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Pedido>().HasKey(p => p.Id);
    }
}

Implementar el Adaptador del Repositorio

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 el Adaptador de Presentación (Controlador en ASP.NET Core)

[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);
        }
    }
}

Paso 7: Configurar la Aplicación en ASP.NET Core

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 y Casos de Uso en la Inyección de Dependencias
builder.Services.AddScoped<IPedidoRepository, PedidoRepository>();
builder.Services.AddScoped<CrearPedidoUseCase>();
builder.Services.AddScoped<ObtenerPedidoUseCase>();

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

app.Run();

Prueba de la API REST con Postman

  1. Ejecuta la API
dotnet run
  1. Crear un Pedido (POST en Postman o cURL)
POST http://localhost:5000/api/pedidos
Content-Type: application/json

{
  "id": 1,
  "cliente": "Juan Pérez"
}
  1. Obtener un Pedido (GET en Postman o cURL)
GET http://localhost:5000/api/pedidos/1

Resumen de la Clase

  • Construimos una API REST completa con Arquitectura Hexagonal y DDD en C#.
  • Los Casos de Uso mantienen la lógica separada de la infraestructura.
  • Usamos EF Core para la persistencia y creamos un Controlador REST en ASP.NET Core.
  • Nuestro código es modular, escalable y fácil de probar.

Próximo paso

En la siguiente clase pasaremos a Vertical Slice Architecture con DDD en C#.

Fernando Sonego

Deja una respuesta

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