Introducción a la clase
Hemos aprendido a organizar nuestra aplicación por Features en lugar de capas y a utilizar MediatR con CQRS. Ahora, vamos a integrar Domain-Driven Design (DDD) en Vertical Slice Architecture para que cada Feature modele correctamente el dominio del negocio.
En esta clase aprenderás:
- Cómo integrar DDD en Vertical Slice Architecture.
- Dónde ubicar Entidades, Agregados y Value Objects dentro de un Slice.
- Cómo modelar el dominio correctamente en cada Feature.
- Implementar un ejemplo práctico en C#.
¿Cómo se integra DDD en Vertical Slice Architecture?
- En Clean Architecture y Hexagonal Architecture, el dominio se mantiene en una capa separada.
- En Vertical Slice Architecture, cada Feature tiene su propio dominio encapsulado.
- Se usa Agregados, Entidades y Value Objects para representar el negocio correctamente.
Ejemplo de organización en Vertical Slice Architecture con DDD:
/Features
/CrearPedido
/Domain
Pedido.cs (Entidad - Agregado)
Producto.cs (Entidad)
Direccion.cs (Value Object)
CrearPedidoCommand.cs
CrearPedidoHandler.cs
PedidoRepository.cs
PedidoController.cs
/ObtenerPedido
/Domain
Pedido.cs (Entidad - Agregado)
Producto.cs (Entidad)
ObtenerPedidoQuery.cs
ObtenerPedidoHandler.cs
Paso 1: Implementar el Dominio de la Feature «Crear Pedido»
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» dentro del Agregado
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 en la estructura del proyecto:
/Features
/CrearPedido
/Domain
Pedido.cs
Producto.cs
Direccion.cs
Paso 2: Implementar el Caso de Uso «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);
}
}
Paso 3: Implementar el Repositorio de la Feature «Crear Pedido»
Definir la Interfaz IPedidoRepository
public interface IPedidoRepository
{
void Guardar(Pedido pedido);
Pedido ObtenerPorId(int id);
}
Implementar el Repositorio PedidoRepository
public class PedidoRepository : IPedidoRepository
{
private readonly List<Pedido> _pedidos = new List<Pedido>();
public void Guardar(Pedido pedido)
{
_pedidos.Add(pedido);
}
public Pedido ObtenerPorId(int id)
{
return _pedidos.FirstOrDefault(p => p.Id == id);
}
}
Ubicación de estos archivos en la estructura del proyecto:
/Features
/CrearPedido
/Infrastructure
IPedidoRepository.cs
PedidoRepository.cs
Paso 4: Implementar el Controlador REST para la Feature «Crear Pedido»
Definir el Controlador PedidoController
[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 5: Configurar MediatR y Repositorios en ASP.NET Core
Registrar MediatR y los Repositorios en Program.cs
var builder = WebApplication.CreateBuilder(args);
// 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();
Beneficios de integrar DDD en Vertical Slice Architecture
- Cada Feature encapsula su propio dominio, evitando dependencias entre módulos.
- Mejor separación de responsabilidades, asegurando que la lógica del negocio esté bien modelada.
- Escalabilidad modular, permitiendo que cada Feature evolucione sin afectar otras.
- Código más expresivo y alineado con el negocio, facilitando su comprensión y mantenimiento.
Errores comunes al usar DDD en Vertical Slice Architecture
- No encapsular correctamente las Entidades dentro de un Agregado → Los objetos dentro de un Agregado deben ser gestionados exclusivamente por su Entidad Raíz.
- Guardar Entidades individuales en lugar del Agregado completo → Siempre persistir y recuperar todo el Agregado para garantizar la consistencia.
- No definir Interfaces para Repositorios → Usar Interfaces permite cambiar la infraestructura sin afectar la lógica de negocio.
Cuestionario de Autoevaluación
- ¿Por qué en Vertical Slice Architecture cada Feature debe encapsular su propio dominio?
- ¿Cuál es la diferencia entre una Entidad y un Value Object en DDD?
- ¿Cómo evitar el acoplamiento entre Features en Vertical Slice Architecture?
- ¿Cómo se diferencia la persistencia de Agregados en DDD frente a Entidades individuales?
- ¿Por qué es importante separar Repositorios y Casos de Uso en cada Feature?
Resumen de la Clase
- DDD se integra en Vertical Slice Architecture encapsulando el dominio en cada Feature.
- Cada Feature contiene sus propias Entidades, Agregados y Value Objects.
- MediatR permite manejar la comunicación desacoplada entre componentes.
- Esta estructura mejora la mantenibilidad, escalabilidad y organización del código.
Próximo paso
En la siguiente clase veremos cómo implementar un ejemplo completo de Vertical Slice Architecture con DDD en C#.