0

Arquitectura de Software con C# 23: Cómo Usar DDD en Vertical Slice Architecture

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#.

Fernando Sonego

Deja una respuesta

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