Desenvolvendo uma API com Vertical Slice Architecture em ASP.NET Core
Desenvolver uma aplicação robusta e fácil de manter é um desafio constante para desenvolvedores. Uma arquitetura bem definida pode fazer toda a diferença nesse aspecto. Neste artigo, vamos explorar a Vertical Slice Architecture e como aplicá-la na construção de uma API em ASP.NET Core.
O que é Vertical Slice Architecture?
A Vertical Slice Architecture é um padrão de arquitetura de software que organiza o código em torno das funcionalidades do sistema, em vez de camadas técnicas. Em vez de dividir o código por camadas como apresentação, lógica de negócios e acesso a dados, a Vertical Slice propõe agrupar todo o código relacionado a uma funcionalidade específica em uma única unidade vertical.
Isso significa que cada funcionalidade da aplicação terá sua própria “fatia vertical” de código, contendo tudo o que é necessário para implementá-la, incluindo interfaces de usuário, lógica de negócios, acesso a dados e testes.
Principais Diferenciais em Comparação com MVC e Clean Architecture
A Vertical Slice Architecture difere das arquiteturas MVC e Clean Architecture em vários aspectos:
- MVC (Model-View-Controller): Enquanto o MVC divide o código em três camadas (Model, View e Controller), a Vertical Slice agrupa o código por funcionalidade, tornando mais fácil manter uma visão clara e coesa da lógica relacionada a uma determinada feature.
- Clean Architecture: A Clean Architecture define camadas (Entidades, Casos de Uso, Interfaces de Usuário, etc.), enquanto a Vertical Slice organiza o código em torno de funcionalidades específicas, o que pode simplificar a estrutura da aplicação e torná-la mais orientada ao domínio.
Benefícios da Vertical Slice Architecture
- Clareza e coesão: O código relacionado a uma determinada funcionalidade está agrupado em um só lugar, facilitando sua compreensão e manutenção.
- Desenvolvimento incremental: Permite o desenvolvimento de novas funcionalidades de forma incremental, sem afetar outras partes da aplicação.
- Facilidade de testes: Os testes de unidade e integração são mais fáceis de escrever e manter, pois estão concentrados em uma única área funcional.
- Desacoplamento: Cada fatia vertical é independente das outras, o que facilita a escalabilidade e a evolução da aplicação.
Pontos a considerar da Vertical Slice Architecture
- Possível Duplicação de Lógica: Se não for bem implementada, pode levar à duplicação de lógica entre os diferentes slices.
- Curva de Aprendizado: Pode ser um pouco desafiador para desenvolvedores menos experientes se acostumarem com essa abordagem, especialmente se estiverem mais familiarizados com o MVC tradicional.
Exemplo Prático: Construindo uma API de TODO List
Vamos criar uma API simples de TODO List usando ASP.NET Core, aplicando os princípios da Vertical Slice Architecture.
Link do exemplo no GitHub: https://github.com/ismaelgasparin/vertical-slice-todo-list
Estrutura do Projeto
/VerticalSliceTodoList
/Controllers
TodoController.cs
/Features
/TodoItems
/Commands
AddTodoItemCommand.cs
UpdateTodoItemCommand.cs
MarkAsTodoItemCommand.cs
DeleteTodoItemCommand.cs
/Queries
GetTodoListQuery.cs
GetTodoItemQuery.cs
/Domain
TodoItem.cs
/Infrastructure
TodoDbContext.cs
Implementação
Começaremos definindo nossa entidade TodoItem
no domínio, que representará uma tarefa em nossa lista de TODOs.
TodoItem.cs (Domínio)
namespace VerticalSliceTodoList.Domain;
public class TodoItem
{
public Guid Id { get; private set; }
public string Title { get; private set; }
public bool IsCompleted { get; private set; }
// Outros membros e métodos omitidos para brevidade
}
Agora, vamos criar a estrutura para manipulação de TODOs na camada de features.
AddTodoItemCommand.cs (Comando)
using FluentResults;
using MediatR;
namespace VerticalSliceTodoList.Features.TodoItems.Commands;
public record AddTodoItemCommand : IRequest<Result<Guid>>
{
public string Title { get; set; } = string.Empty;
}
O comando AddTodoItemCommand
representa a ação de adicionar uma nova tarefa à lista de TODOs.
AddTodoItemCommandHandler.cs (Handler do Comando)
using FluentResults;
using MediatR;
using VerticalSliceTodoList.Domain;
using VerticalSliceTodoList.Infrastructure;
namespace VerticalSliceTodoList.Features.TodoItems.Commands;
public class AddTodoItemCommandHandler : IRequestHandler<AddTodoItemCommand, Result<Guid>>
{
private readonly TodoDbContext _context;
public AddTodoItemCommandHandler(TodoDbContext context)
{
_context = context;
}
public async Task<Result<Guid>> Handle(AddTodoItemCommand request,
CancellationToken cancellationToken)
{
var todoItem = new TodoItem(request.Title);
await _context.TodoItems.AddAsync(todoItem, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return Result.Ok(todoItem.Id);
}
}
O handler do comando AddTodoItemCommand
é responsável por executar a lógica de negócios para adicionar uma nova tarefa à lista de TODOs.
AddTodoItemCommandValidator.cs (Validador do Comando)
using FluentValidation;
namespace VerticalSliceTodoList.Features.TodoItems.Commands;
public class AddTodoItemCommandValidator : AbstractValidator<AddTodoItemCommand>
{
public AddTodoItemCommandValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.MinimumLength(3)
.MaximumLength(100);
}
}
O validador do comando AddTodoItemCommand
é responsável por executar as validações (campos obrigatórios, tamanhos mínimos e máximos) antes de adicionar uma nova tarefa à lista de TODOs. Caso alguma validação falhe, o handler AddTodoItemCommandHandler
não é executado.
TodoController.cs (Controlador)
using MediatR;
using Microsoft.AspNetCore.Mvc;
using VerticalSliceTodoList.Features.TodoItems.Commands;
using VerticalSliceTodoList.Features.TodoItems.Queries;
using VerticalSliceTodoList.Infrastructure.Extensions;
namespace VerticalSliceTodoList.Controllers;
[ApiController]
[Route("todo")]
[Produces("application/json")]
public class TodoController : ControllerBase
{
private readonly IMediator _mediator;
public TodoController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<IActionResult> AddTodoItem(
[FromBody] AddTodoItemCommand command,
CancellationToken cancellationToken = default)
{
var result = await _mediator.Send(command, cancellationToken);
return result.ToActionResult(
onSuccess: r =>
{
return new CreatedAtActionResult(
nameof(GetTodoItem), "todo", new { id = r.Value },
r.Value);
});
}
}
O controlador TodoController
define os endpoints para manipulação de TODOs, neste caso, apenas o endpoint para adicionar uma nova tarefa.
Conclusão
A Vertical Slice Architecture é uma abordagem poderosa para organizar o código de uma aplicação, proporcionando clareza, coesão e facilidade de manutenção. Ao aplicar esse padrão em nossa API de TODO List, pudemos observar como ele nos permite desenvolver funcionalidades de forma incremental e desacoplada, facilitando o desenvolvimento e a evolução da aplicação.
No entanto, é importante estar ciente de que a Vertical Slice pode adicionar complexidade em projetos simples e pode exigir um esforço adicional de organização e planejamento. Portanto, ao decidir adotar essa arquitetura, é crucial avaliar cuidadosamente as necessidades do projeto e o contexto em que será aplicada.
Link no GitHub: https://github.com/ismaelgasparin/vertical-slice-todo-list