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
- Ejecuta la API
dotnet run
- Crear un Pedido (POST en Postman o cURL)
POST http://localhost:5000/api/pedidos
Content-Type: application/json
{
"id": 1,
"cliente": "Juan Pérez"
}
- 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#.