Software · Arquitetura de Software
Padrões Arquiteturais Web: Da Separação MVC ao Padrão REPR em APIs Modernas
A evolução da estruturação de backends web: da separação clássica MVC e arquiteturas em camadas até o padrão REPR (Request-Endpoint-Response), Feature Folders e persistência moderna com ORM.
A história das aplicações web e dos backends corporativos é marcada pela constante busca por modularidade, testabilidade e separação de responsabilidades. No início dos anos 2000, o modelo de arquitetura em três camadas e o padrão Model-View-Controller (MVC) revolucionaram o desenvolvimento de software ao separar a apresentação visual da lógica de negócios e do acesso ao banco de dados.
Contudo, a ascensão do desenvolvimento moderno de APIs REST, interfaces ricas orientadas a componentes no frontend (React, Angular, Vue) e microsserviços distribuídos revelou as limitações dos controladores inflados (Fat Controllers). É nesse cenário que emergem abordagens contemporâneas como o padrão REPR (Request-Endpoint-Response), arquiteturas baseadas em fatias verticais (Vertical Slice Architecture) e estruturas em Feature Folders.
O Modelo Cliente-Servidor e a Separação em Camadas
No paradigma clássico Cliente-Servidor, o sistema é dividido em nós consumidores de serviços (clientes navegadores, mobile ou outras aplicações) e nós provedores (servidores de backend e instâncias de banco de dados):
flowchart TD
subgraph Clientes["Camada de Apresentação / Clientes"]
Browser["Single Page Application (Astro/Angular/React)"]
Mobile["Aplicativo Mobile (iOS/Android)"]
end
subgraph API["Servidor de Aplicação Web / Backend"]
Endpoint["API Gateway / Endpoints HTTP REST & gRPC"]
Negocio["Camada de Serviços / Lógica de Negócio"]
AcessoDados["Camada de Repositórios & ORM"]
end
subgraph Persistencia["Armazenamento & Integração"]
DB[("Banco de Dados Relacional / NoSQL")]
Broker["Message Broker (RabbitMQ / Kafka)"]
end
Clientes -->|Requisição HTTP/JSON| Endpoint
Endpoint --> Negocio
Negocio --> AcessoDados
AcessoDados --> DB
Negocio -.->|"Eventos Assíncronos (Pub/Sub)"| Broker
A arquitetura em camadas tradicional (Layered Architecture) organiza o código por sua responsabilidade técnica:
- Presentation Layer (Apresentação): Controladores, roteamento HTTP e serialização de respostas.
- Business/Service Layer (Negócio): Regras de validação, coordenação de transações e fluxos operacionais.
- Data Access Layer (Persistência): DAOs, Repositórios e abstrações de Object-Relational Mapping (ORM).
A regra estrita da arquitetura em camadas dita que uma camada superior só pode conhecer a camada imediatamente inferior, promovendo um desacoplamento unidirecional.
A Anatomia do MVC: Triunfo e Degradação
O padrão MVC (Model-View-Controller) distribui o fluxo da aplicação em três vértices:
- Model: Encapsula o estado e as regras de negócio dos dados.
- View: A renderização visual retornada ao usuário (HTML nos frameworks clássicos como Rails, Django ou ASP.NET MVC).
- Controller: O orquestrador que intercepta o input do usuário, invoca o modelo e seleciona a view apropriada.
flowchart LR
User([Usuário]) -->|HTTP Request| C[Controller]
C -->|Atualiza / Consulta| M[(Model)]
M -->|Retorna Dados| C
C -->|Passa ViewModel| V[View Engine]
V -->|HTML Renderizado| User
O Fenômeno do "Fat Controller"
Quando o MVC foi transposto diretamente para APIs REST (onde a "View" se reduziu a um mero serializador JSON), os Controllers começaram a concentrar responsabilidades indevidas:
- Um único arquivo
UserController.csouUserController.tsacumula 15 endpoints distintos (CreateUser,ResetPassword,UploadAvatar,ListUsersWithPagination, etc.). - O construtor do controlador passa a exigir dezenas de dependências via Injeção de Dependência (IoC), degradando a manutenibilidade e os testes unitários.
O Padrão REPR: Request-Endpoint-Response
Para resolver a sobrecarga de controladores monolíticos, o padrão REPR (cunhado por Steve Smith e adotado amplamente em bibliotecas como FastEndpoints, Carter e MediatR no ecossistema .NET, além de frameworks Node/Go) propõe que APIs web sejam concebidas como uma coleção de funções isoladas: 1 Endpoint = 1 Requisição + 1 Resposta.
flowchart LR
subgraph REPR["Padrão REPR (Uma Fatia Vertical Isolada)"]
Req["Request DTO<br/>(Validação estrita e tipagem da entrada)"]
Endp["Endpoint Handler<br/>(Lógica coesa e única daquela operação)"]
Resp["Response DTO<br/>(Contrato explícito de payload de saída)"]
Req --> Endp --> Resp
end
| Critério | Padrão MVC Tradicional | Padrão REPR / Vertical Slice |
|---|---|---|
| Organização do Código | Agrupamento por pastas técnicas (Controllers/, Services/, Models/). |
Agrupamento por capacidade de negócio em Feature Folders (Features/Orders/CreateOrder/). |
| Coesão | Baixa; o controller mistura operações de leitura, criação e deleção de um recurso. | Máxima; o endpoint contém apenas a lógica específica daquele caso de uso. |
| Injeção de Dependência | Construtor sobrecarregado com 8+ serviços que nem todo método utiliza. | Cada handler injeta estritamente os 1 ou 2 serviços que sua operação requer. |
| Conflitos de Git (PRs) | Constantes; múltiplos desenvolvedores alterando o mesmo arquivo de controlador. | Quase nulos; cada nova rota é um novo arquivo isolado no repositório. |
Exemplo do Padrão REPR em C# Moderno
// Features/Users/RegisterUser/RegisterUserModels.cs
public record RegisterUserRequest(string Email, string Password, string FullName);
public record RegisterUserResponse(Guid Id, string Email, DateTime CreatedAt);
// Features/Users/RegisterUser/RegisterUserEndpoint.cs
public class RegisterUserEndpoint : Endpoint<RegisterUserRequest, RegisterUserResponse>
{
private readonly IUserRepository _repository;
private readonly IPasswordHasher _hasher;
public RegisterUserEndpoint(IUserRepository repository, IPasswordHasher hasher)
{
_repository = repository;
_hasher = hasher;
}
public override void Configure()
{
Post("/api/v1/users");
AllowAnonymous();
}
public override async Task HandleAsync(RegisterUserRequest req, CancellationToken ct)
{
if (await _repository.ExistsByEmailAsync(req.Email, ct))
{
AddError(r => r.Email, "E-mail já cadastrado na plataforma.");
await SendErrorsAsync(cancellation: ct);
return;
}
var user = new User(req.Email, _hasher.Hash(req.Password), req.FullName);
await _repository.SaveAsync(user, ct);
await SendCreatedAtAsync<GetUserByIdEndpoint>(
new { id = user.Id },
new RegisterUserResponse(user.Id, user.Email, user.CreatedAt),
cancellation: ct
);
}
}
Estratégias de Persistência: Database-First vs. Code-First em ORMs
A camada de acesso a dados e os frameworks ORM (Object-Relational Mapping, como Entity Framework Core, Hibernate ou Prisma) oferecem duas estratégias diametralmente opostas de ciclo de desenvolvimento:
flowchart TD
subgraph DBFirst["Abordagem Database-First"]
D1["1. DBA / Engenheiro cria tabelas e triggers no SGBD"]
D2["2. Ferramenta de Scaffold gera entidades de código"]
D1 --> D2
end
subgraph CodeFirst["Abordagem Code-First"]
C1["1. Engenheiro projeta classes de domínio no código"]
C2["2. Motor de Migrations gera DDL e altera o banco"]
C1 --> C2
end
- Database-First: Ideal para sistemas legados consolidados onde o esquema relacional já existe, é compartilhado por múltiplos sistemas externos e gerenciado exclusivamente por DBAs experientes.
- Code-First: O código da aplicação é a fonte única da verdade (Single Source of Truth). O desenvolvedor define as entidades em sua linguagem nativa e o pipeline de Migrations reflete as alterações de esquema de forma versionável no Git.
Comunicação Assíncrona: Message Brokers e Padrão Pub/Sub
Conforme o ecossistema web cresce, chamadas HTTP síncronas entre serviços encadeados criam acoplamento temporal e pontos únicos de falha (Cascading Failures). A introdução de comunicação orientada a mensagens assíncronas resolve esse desafio:
flowchart LR
API["API de Checkout<br/>(Publisher)"]
Queue[("Message Broker<br/>RabbitMQ / Kafka")]
Worker1["Serviço de Faturamento<br/>(Consumer 1)"]
Worker2["Serviço de Notificação<br/>(Consumer 2)"]
Worker3["Serviço de Estoque<br/>(Consumer 3)"]
API -->|Evento: PedidoCriado| Queue
Queue --> Worker1
Queue --> Worker2
Queue --> Worker3
- Message Queues (Filas ponto a ponto): Garantem que cada mensagem seja consumida por exatamente um trabalhador, ideal para distribuição uniforme de carga de tarefas pesadas.
- Publish-Subscribe (Pub/Sub): O produtor emite um evento de domínio sem conhecer os destinatários; múltiplos assinantes processam o mesmo evento de forma independente e isolada.
Conclusão
A arquitetura de software para a web não possui fórmulas universais, mas sim uma trajetória clara rumo à autonomia e à redução de complexidade acidental. Ao abandonar controladores monolíticos em favor do padrão REPR e de fatias verticais, desacoplar dependências por meio de Inversão de Controle e Repositórios bem definidos, e isolar fluxos pesados através de mensageria assíncrona, engenheiros constroem plataformas resilientes que suportam evolução acelerada sem medo de quebras colaterais.