Software · Arquitetura de Software

Vertical Slice Architecture: Organização por Funcionalidades em vez de Camadas Técnicas

Como a Vertical Slice Architecture maximiza a coesão do código ao agrupar controladores, regras de negócio e persistência em fatias funcionais autossuficientes.

Diagrama conceitual da Vertical Slice Architecture demonstrando o fatiamento por casos de uso e coesão funcional
Neste artigo

Em arquiteturas em camadas convencionais (como a clássica divisão em Apresentação, Aplicação, Domínio e Infraestrutura), o código é fatiado horizontalmente segundo critérios técnicos. Para implementar ou alterar uma funcionalidade simples (por exemplo, "Atualizar Endereço do Cliente"), o desenvolvedor é obrigado a percorrer e modificar arquivos espalhados em quatro ou cinco pastas diferentes do projeto, alterando controladores, serviços, interfaces abstratas, repositórios e esquemas de dados.

A Vertical Slice Architecture (Arquitetura em Fatias Verticais), popularizada por Jimmy Bogard, propõe inverter esse eixo de organização: em vez de agrupar o código por preocupações técnicas transversais, o sistema é fatiado verticalmente em torno de funcionalidades de negócio de ponta a ponta. Cada fatia (slice) encapsula tudo o que é estritamente necessário para atender a uma requisição específica.

A Mudança de Eixo: Fatiamento Horizontal versus Vertical

A transição entre esses paradigmas redefine a forma como as dependências se relacionam dentro do repositório:

flowchart TD
    subgraph Horizontal["ARQUITETURA EM CAMADAS (HORIZONTAL)"]
        direction TB
        C["Camada de Controladores (Controllers)"]
        S["Camada de Serviços de Aplicação (Services)"]
        D["Camada de Domínio e Entidades (Domain)"]
        R["Camada de Acesso a Dados (Repositories / Infra)"]
        C --> S --> D --> R
    end

    subgraph Vertical["VERTICAL SLICE ARCHITECTURE (VERTICAL)"]
        direction LR
        subgraph Slice1["Fatia: CriarPedido"]
            direction TB
            E1["Endpoint"] --> V1["Validação"] --> H1["Lógica de Negócio"] --> P1["Persistência"]
        end

        subgraph Slice2["Fatia: CancelarPedido"]
            direction TB
            E2["Endpoint"] --> V2["Validação"] --> H2["Lógica de Negócio"] --> P2["Persistência"]
        end

        subgraph Slice3["Fatia: ObterRelatorio"]
            direction TB
            E3["Endpoint"] --> Q3["Consulta SQL Direta"] --> D3["Projeção DTO"]
        end
    end

No modelo horizontal, o acoplamento é sistêmico: alterar a assinatura de um método na camada de serviço pode quebrar três outros casos de uso que reutilizavam a mesma classe genérica. Na arquitetura vertical, cada fatia possui autonomia operacional; se a regra de validação do comando CriarPedido mudar, apenas os arquivos daquela pasta específica são editados, com risco nulo de efeitos colaterais sobre CancelarPedido ou ObterRelatorio.

Coesão Espacial e Pragmatismo Técnico

A maior vantagem da Vertical Slice Architecture é permitir que cada funcionalidade adote o nível exato de sofisticação exigido pelo seu problema, sem impor abstrações globais desnecessárias:

  • Casos de Uso Simples (CRUD): Se uma consulta precisa apenas ler registros de uma tabela e devolver um JSON paginado na tela, a fatia pode executar uma consulta SQL direta otimizada via Dapper ou Entity Framework com AsNoTracking(), sem necessidade de instanciar agregados ricos de DDD, interfaces de repositório e mapeadores intermediários.
  • Casos de Uso Complexos: Se a funcionalidade envolve transferências financeiras ou regras rigorosas de concorrência, a respectiva fatia pode modelar agregados completos, objetos de valor e eventos de domínio internamente, limitando a complexidade conceitual ao escopo onde ela realmente agrega valor.
// Exemplo de fatia vertical coesa para o caso de uso 'AprovarOrcamento'
namespace MeuApp.Features.Orcamentos.AprovarOrcamento;

// 1. Contrato da Requisição (Comando)
public record AprovarOrcamentoCommand(Guid OrcamentoId, string ResponsavelAprovacao) : IRequest<IResult>;

// 2. Validador específico da funcionalidade com FluentValidation
public class AprovarOrcamentoValidator : AbstractValidator<AprovarOrcamentoCommand>
{
    public AprovarOrcamentoValidator()
    {
        RuleFor(x => x.OrcamentoId).NotEmpty();
        RuleFor(x => x.ResponsavelAprovacao).NotEmpty().MaximumLength(100);
    }
}

// 3. Manipulador que executa as regras e persiste o resultado
public class AprovarOrcamentoHandler : IRequestHandler<AprovarOrcamentoCommand, IResult>
{
    private readonly AppDbContext _db;
    public AprovarOrcamentoHandler(AppDbContext db) => _db = db;

    public async Task<IResult> Handle(AprovarOrcamentoCommand cmd, CancellationToken ct)
    {
        var orcamento = await _db.Orcamentos.FindAsync(new object[] { cmd.OrcamentoId }, ct);
        if (orcamento == null) return Results.NotFound("Orçamento não localizado.");

        orcamento.Aprovar(cmd.ResponsavelAprovacao);
        await _db.SaveChangesAsync(ct);

        return Results.Ok(new { Mensagem = "Orçamento aprovado com sucesso.", Id = orcamento.Id });
    }
}

// 4. Mapeamento de Endpoint HTTP na mesma fatia
public static class Endpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapPost("/api/orcamentos/{id}/aprovar", async (Guid id, IMediator mediator) =>
            await mediator.Send(new AprovarOrcamentoCommand(id, "UsuarioAutenticado")));
}

Nesse arranjo, toda a cadeia da requisição (do recebimento HTTP à persistência no banco) reside em um único arquivo ou em uma pasta dedicada Features/Orcamentos/AprovarOrcamento/, reduzindo drasticamente a carga cognitiva necessária para compreender e manter o código.

Comparativo: Vertical Slice versus Clean Architecture

Embora ambas compartilhem o objetivo de manter as regras de negócio desacopladas de ferramentas externas, a estratégia de divisão interna diverge substancialmente:

Critério Clean Architecture Vertical Slice Architecture
Organização de Pastas Por círculos de abstração técnica (Domain, Application, Infrastructure). Por pastas de funcionalidades (Features/Usuarios/CriarUsuario).
Reaproveitamento de Classes Alto reuso de repositórios e serviços genéricos compartilhados. Baixo acoplamento entre casos de uso; cada fatia é independente.
Flexibilidade de Design Rígida; todo caso de uso é forçado a transitar pelas 4 camadas. Dinâmica; cada fatia escolhe entre consulta direta ou modelo rico.
Facilidade de Refatoração Exige alterar múltiplos projetos e camadas para uma única tela. Alterações ficam confinadas ao diretório da funcionalidade.

Diretrizes Práticas para Produção

Na arquitetura de sistemas escaláveis, a Vertical Slice Architecture costuma ser adotada em combinação com o padrão REPR (Request-Endpoint-Response) e Minimal APIs:

  1. Evitar o "Monólito de Fatias": Compartilhar apenas infraestrutura genérica essencial (conexão de banco de dados, middleware de logging estruturado e configurações de autenticação), resistindo à tentação de criar serviços auxiliares compartilhados que voltem a acoplar as fatias.
  2. Duplicação Sadia versus Acoplamento Tóxico: Se duas fatias precisam de um DTO ligeiramente parecido, prefira duplicar as três propriedades na nova fatia em vez de criar uma classe base abstrata compartilhada que impeça a evolução independente dos casos de uso.
  3. Ponto de Partida para Microsserviços: Quando uma funcionalidade atinge um volume extremo de acessos, a extração de uma fatia vertical para um microsserviço independente requer apenas recortar aquela pasta e seu endpoint, tornando a evolução física do sistema simples e previsível.
Explorar este tema no Grafo de Conhecimento