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.