Introducción a la clase
En la clase anterior vimos cómo Vertical Slice Architecture organiza el código por Slices funcionales en lugar de capas horizontales. Ahora aprenderemos cómo estructurar correctamente una aplicación basada en Features, asegurando modularidad, escalabilidad y facilidad de mantenimiento.
En esta clase aprenderás:
- Cómo estructurar una aplicación por Features en lugar de capas tradicionales.
- Cómo manejar la separación de responsabilidades en cada Feature.
- Beneficios de este enfoque y cómo aplicarlo en C#.
¿Por qué organizar el código por Features?
En una aplicación tradicional, el código se organiza en capas como Presentación, Aplicación, Dominio e Infraestructura, lo que a menudo genera acoplamiento innecesario y complejidad en cambios.
En Vertical Slice Architecture, cada Feature contiene su propia lógica, lo que permite que la aplicación sea más modular y fácil de escalar.
Ejemplo de estructura tradicional con capas horizontales:
/Controllers
PedidoController.cs
/Services
PedidoService.cs
/Repositories
PedidoRepository.cs
/Entities
Pedido.cs
Ejemplo de estructura organizada por Features:
/Features
/CrearPedido
CrearPedidoCommand.cs
CrearPedidoHandler.cs
PedidoRepository.cs
PedidoController.cs
/ObtenerPedido
ObtenerPedidoQuery.cs
ObtenerPedidoHandler.cs
PedidoRepository.cs
PedidoController.cs
Ventajas de organizar el código por Features
- Menos acoplamiento entre capas → Cada Feature es independiente.
- Mejor mantenibilidad → Se pueden eliminar o modificar Features sin afectar otras.
- Mejor escalabilidad → Se pueden agregar nuevas Features sin reorganizar el código existente.
- Ideal para equipos grandes → Diferentes equipos pueden trabajar en Features separadas sin conflictos.
Ejemplo en C#: Separando Features correctamente
Implementaremos dos Features:
CrearPedido
ObtenerPedido
Estructura del código en Features:
PedidosAPI
│── /Features
│ ├── /CrearPedido
│ │ ├── CrearPedidoCommand.cs
│ │ ├── CrearPedidoHandler.cs
│ │ ├── PedidoRepository.cs
│ │ ├── PedidoController.cs
│ ├── /ObtenerPedido
│ │ ├── ObtenerPedidoQuery.cs
│ │ ├── ObtenerPedidoHandler.cs
│ │ ├── PedidoRepository.cs
│ │ ├── PedidoController.cs
│── Program.cs
│── appsettings.json
Paso 1: Implementar la Feature «Crear Pedido»
Definir el Comando (Request DTO)
public class CrearPedidoCommand
{
public string Cliente { get; set; }
public List<string> Productos { get; set; }
}
Implementar el Handler (Caso de Uso)
public class CrearPedidoHandler
{
private readonly PedidoRepository _repository;
public CrearPedidoHandler(PedidoRepository repository)
{
_repository = repository;
}
public void Handle(CrearPedidoCommand command)
{
var pedido = new Pedido(0, command.Cliente);
foreach (var producto in command.Productos)
{
pedido.AgregarProducto(new Producto(0, producto, 100)); // Precio por defecto
}
_repository.Guardar(pedido);
}
}
Implementar el Adaptador de Persistencia (Repositorio)
public class PedidoRepository
{
private readonly List<Pedido> _pedidos = new List<Pedido>();
public void Guardar(Pedido pedido)
{
_pedidos.Add(pedido);
}
}
Implementar el Controlador REST
[ApiController]
[Route("api/pedidos")]
public class PedidoController : ControllerBase
{
private readonly CrearPedidoHandler _crearPedidoHandler;
public PedidoController(CrearPedidoHandler crearPedidoHandler)
{
_crearPedidoHandler = crearPedidoHandler;
}
[HttpPost]
public IActionResult CrearPedido([FromBody] CrearPedidoCommand command)
{
_crearPedidoHandler.Handle(command);
return Ok("Pedido creado exitosamente");
}
}
Paso 2: Implementar la Feature «Obtener Pedido»
Definir la Query
public class ObtenerPedidoQuery
{
public int Id { get; set; }
}
Implementar el Handler (Caso de Uso)
public class ObtenerPedidoHandler
{
private readonly PedidoRepository _repository;
public ObtenerPedidoHandler(PedidoRepository repository)
{
_repository = repository;
}
public Pedido Handle(ObtenerPedidoQuery query)
{
return _repository.ObtenerPorId(query.Id);
}
}
Implementar el Controlador REST
[ApiController]
[Route("api/pedidos")]
public class PedidoController : ControllerBase
{
private readonly ObtenerPedidoHandler _obtenerPedidoHandler;
public PedidoController(ObtenerPedidoHandler obtenerPedidoHandler)
{
_obtenerPedidoHandler = obtenerPedidoHandler;
}
[HttpGet("{id}")]
public IActionResult ObtenerPedido(int id)
{
var pedido = _obtenerPedidoHandler.Handle(new ObtenerPedidoQuery { Id = id });
return pedido != null ? Ok(pedido) : NotFound();
}
}
Errores comunes al organizar código por Features
- No agrupar correctamente las Features → Cada Feature debe tener su propia estructura interna.
- Duplicación innecesaria de código → Si varias Features comparten lógica, usar servicios compartidos en lugar de duplicar código.
- No definir reglas de dependencia → Se debe evitar que una Feature dependa de otra directamente.
Cuestionario de Autoevaluación
- ¿Por qué Vertical Slice Architecture organiza el código en Features en lugar de capas?
- ¿Qué ventajas tiene organizar el código de esta manera en proyectos grandes?
- ¿Cómo puedes evitar la duplicación de código entre Features?
- ¿Por qué es importante que cada Feature sea independiente?
- ¿Cómo se relaciona este enfoque con microservicios?
Resumen de la Clase
- Vertical Slice Architecture organiza el código por Features en lugar de capas.
- Cada Feature es independiente y contiene su propia lógica de negocio, persistencia y API.
- Esto mejora la escalabilidad, mantenibilidad y facilita el trabajo en equipo.
- Es un enfoque ideal para aplicaciones grandes o en crecimiento.
Próximo paso
En la siguiente clase veremos cómo usar MediatR para manejar la separación de Features en Vertical Slice Architecture.