0

Arquitectura de Software con C# 24: Ejemplo Práctico – API REST Completa con Vertical Slice Architecture y DDD

Introducción a la clase

Hemos aprendido cómo integrar Domain-Driven Design (DDD) en Vertical Slice Architecture, estructurando cada Feature con Entidades, Agregados y Value Objects. Ahora construiremos una API REST completa, aplicando todo lo aprendido en C# con ASP.NET Core, MediatR y Entity Framework Core (EF Core).

En esta clase aprenderás:

  • Cómo implementar Vertical Slice Architecture con DDD en una API REST.
  • Cómo estructurar un proyecto completo con Features independientes.
  • Cómo usar MediatR y CQRS para manejar Commands y Queries.
  • Cómo configurar persistencia con Entity Framework Core.

Paso 1: Estructura del Proyecto

La estructura del código sigue Vertical Slice Architecture, donde cada Feature es independiente y contiene su propia lógica de dominio y persistencia.

/PedidosAPI
│── /Features
│   ├── /CrearPedido
│   │   ├── /Domain
│   │   │   ├── Pedido.cs
│   │   │   ├── Producto.cs
│   │   │   ├── Direccion.cs
│   │   ├── /Application
│   │   │   ├── CrearPedidoCommand.cs
│   │   │   ├── CrearPedidoHandler.cs
│   │   ├── /Infrastructure
│   │   │   ├── IPedidoRepository.cs
│   │   │   ├── PedidoRepository.cs
│   │   ├── PedidoController.cs
│   ├── /ObtenerPedido
│   │   ├── /Domain
│   │   │   ├── Pedido.cs
│   │   ├── /Application
│   │   │   ├── ObtenerPedidoQuery.cs
│   │   │   ├── ObtenerPedidoHandler.cs
│   │   ├── PedidoController.cs
│── /Infrastructure
│   ├── AppDbContext.cs
│── Program.cs
│── appsettings.json

Beneficio de esta estructura:

  • Cada Feature es independiente → No hay dependencias innecesarias entre módulos.
  • Mantenibilidad y escalabilidad → Se pueden agregar nuevas Features sin modificar las existentes.
  • Separación clara del dominio y la infraestructura.

Paso 2: Implementar el Núcleo del Dominio

Definir el Agregado «Pedido»

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

Definir la Entidad «Producto»

public class Producto
{
    public int Id { get; private set; }
    public string Nombre { get; private set; }
    public decimal Precio { get; private set; }

    public Producto(int id, string nombre, decimal precio)
    {
        Id = id;
        Nombre = nombre;
        Precio = precio;
    }
}

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

Ubicación de estos archivos:

/Features/CrearPedido/Domain
    Pedido.cs
    Producto.cs
    Direccion.cs

Paso 3: Implementar la Feature «Crear Pedido»

Definir el Comando CrearPedidoCommand

public class CrearPedidoCommand : IRequest<int>
{
    public string Cliente { get; set; }
    public List<string> Productos { get; set; }
}

Implementar el Handler CrearPedidoHandler

public class CrearPedidoHandler : IRequestHandler<CrearPedidoCommand, int>
{
    private readonly IPedidoRepository _repository;

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

    public Task<int> Handle(CrearPedidoCommand request, CancellationToken cancellationToken)
    {
        var pedido = new Pedido(0, request.Cliente);

        foreach (var producto in request.Productos)
        {
            pedido.AgregarProducto(new Producto(0, producto, 100)); // Precio por defecto
        }

        _repository.Guardar(pedido);
        return Task.FromResult(pedido.Id);
    }
}

Definir el Repositorio IPedidoRepository

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

Implementar PedidoRepository con Entity Framework Core

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

Definir el Controlador REST

[ApiController]
[Route("api/pedidos")]
public class PedidoController : ControllerBase
{
    private readonly IMediator _mediator;

    public PedidoController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> CrearPedido([FromBody] CrearPedidoCommand command)
    {
        var pedidoId = await _mediator.Send(command);
        return Ok(new { Id = pedidoId, Mensaje = "Pedido creado exitosamente" });
    }
}

Paso 4: Configurar ASP.NET Core y EF Core

Definir AppDbContext

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

Configurar 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 MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

// Registrar Repositorios
builder.Services.AddScoped<IPedidoRepository, PedidoRepository>();

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

Resumen de la Clase

  • Construimos una API REST con Vertical Slice Architecture y DDD.
  • Cada Feature tiene su propio dominio encapsulado.
  • Usamos MediatR para desacoplar los Handlers de los Controladores.
  • Implementamos persistencia con Entity Framework Core.

Próximo paso

En la siguiente clase veremos las ventajas, desventajas y comparaciones entre Clean, Hexagonal y Vertical Slice Architecture.

Fernando Sonego

Deja una respuesta

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