Arquitetura de software
Arquitetura em camadas e n-tier: tiers, camadas e a regra de dependência
Iniciante11 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
Dividir em camadas é a primeira decisão de arquitetura que a maioria dos sistemas toma, e uma das mais mal compreendidas. Ao final deste guia, você vai saber distinguir tiers físicos de camadas lógicas, escolher uma estrutura de camadas sensata para uma aplicação de negócio, manter as dependências apontando na direção certa e reconhecer os antipadrões que transformam um código em camadas num labirinto.
O que você vai aprender
- Explicar a diferença entre um tier físico e uma camada lógica, e por que os dois são tão confundidos
- Descrever o que diferencia o cliente-servidor de dois tiers, o modelo de três tiers e o n-tier, e que problema cada etapa resolveu
- Estruturar as camadas de apresentação, aplicação, domínio e infraestrutura com as dependências apontando para dentro
- Documentar uma arquitetura de aplicações com uma visão de componentes e uma matriz de fluxos
- Identificar antipadrões de camadas como entidades vazando, serviços anêmicos e lógica de negócio nos controllers
Quase toda aplicação de negócio que você vai abrir na vida é organizada em camadas, e a maioria delas é mal organizada. Introduzir camadas é barato e consertá-las é caro, porque uma estrutura que deixa regras de negócio vazarem para os controllers ou SQL vazar para o domínio se espalha por milhares de classes antes que alguém perceba. Acertar exige entender três coisas que vivem sendo confundidas: tiers físicos, camadas lógicas e a direção em que as dependências podem apontar.
Tiers são físicos, camadas são lógicas
Um tier é uma fronteira de deploy: um processo, uma máquina ou um cluster que conversa com outro pela rede. Uma camada é uma fronteira no código: um conjunto de pacotes com uma responsabilidade definida e regras sobre o que pode chamar. As duas coisas são independentes. Uma aplicação desktop de processo único pode ter cinco camadas bem limpas; um sistema distribuído em seis tiers pode não ter nenhuma organização em camadas digna do nome.
A confusão surge porque o deploy three-tier clássico — um navegador, um servidor de aplicação e um banco de dados — por acaso coincide com três camadas óbvias: apresentação, lógica de negócio e acesso a dados. Essa coincidência tornou "três tiers" e "três camadas" intercambiáveis no dia a dia, e esconde uma verdade importante: mover código para outro tier é uma decisão de rede e de operação; movê-lo para outra camada é uma decisão de design. Você pode mudar um sem mudar o outro.
Como os tiers evoluíram
Cliente-servidor em dois tiers
No modelo de dois tiers, um cliente pesado contém a interface do usuário e toda a lógica de negócio, e conversa diretamente com um banco de dados compartilhado por meio de um protocolo de banco. As regras de negócio ficam no programa cliente e, às vezes, em stored procedures. O modelo é simples e rápido de construir, por isso dominou os anos 1990, mas tem problemas estruturais:
- Cada cliente precisa de uma conexão com o banco, que vira o limite de escalabilidade.
- Mudar uma regra significa reinstalar o cliente em todas as máquinas.
- O schema do banco vira o contrato de integração entre aplicações, e ninguém tem coragem de mexer nele.
- A segurança é aplicada no nível do usuário do banco, uma granularidade grossa demais para a autorização de negócio.
Três tiers
Os deploys em três tiers inseriram um servidor de aplicação entre os clientes e o banco de dados. O cliente ficou leve (com o tempo, um navegador), a lógica de negócio migrou para o tier intermediário e o banco ficou escondido atrás dele. Essa mudança resolveu o problema das conexões (o tier intermediário mantém um pool de conexões), o do deploy (um único servidor para atualizar) e o da integração (as outras aplicações chamam a interface do tier intermediário em vez de acessar as tabelas). Ela também trouxe um novo conjunto de preocupações — sessões, pools de conexão, invocação remota — que as plataformas de servidores de aplicação corporativos dos anos 2000 foram feitas para resolver.
N tiers
"N-tier" é o que acontece quando o próprio tier intermediário se divide: um servidor web que serve os arquivos estáticos e termina o TLS, um servidor de aplicação, uma camada de integração separada que encapsula sistemas legados ou APIs de parceiros, um tier de cache, um message broker, um ou mais bancos de dados. Cada tier extra traz isolamento, escalabilidade independente ou uma fronteira de segurança, e custa um salto de rede, um modo de falha e mais uma peça para operar.
| Modelo | Onde fica a lógica de negócio | O que resolveu | O que custa |
|---|---|---|---|
| Dois tiers | Programa cliente, stored procedures | Simplicidade, acesso direto aos dados | Escalabilidade, deploy, acoplamento ao schema |
| Três tiers | Servidor de aplicação | Pool de conexões, regras centralizadas, schema escondido | Sessões, chamadas remotas, um servidor para manter no ar |
| N tiers | Tiers de aplicação e de integração | Zonas de segurança, escalabilidade independente, isolamento do legado | Latência a cada salto, mais modos de falha, carga operacional |
Os quatro níveis de uma descrição de arquitetura
Ao descrever um sistema, os arquitetos costumam trabalhar em quatro níveis, cada um respondendo a uma pergunta diferente. Dar nome a eles evita aquelas reuniões em que uma pessoa discute servidores enquanto a outra discute pacotes.
- Arquitetura funcional — quais capacidades de negócio o sistema oferece e como elas se agrupam, num vocabulário que a área de negócio entende. Nenhuma tecnologia aparece aqui.
- Arquitetura de aplicações — quais aplicações e componentes existem, qual é dono de quais dados e como eles se chamam. É aqui que se desenham o diagrama de componentes e a matriz de fluxos.
- Arquitetura técnica (de infraestrutura) — quais servidores, zonas, redes, load balancers e bancos de dados hospedam esses componentes, com as políticas de disponibilidade e segurança associadas.
- Arquitetura de software — como o código dentro de um componente é organizado: frameworks, módulos, camadas, regras de codificação.
As camadas pertencem ao quarto nível; os tiers, ao terceiro. A matriz de fluxos, no segundo nível, é o artefato que liga os dois, e vale a pena produzi-la para qualquer sistema com mais do que um punhado de componentes:
| Ref. | Protocolo | Chamador | Chamado | Segurança | Rastreado |
|---|---|---|---|---|---|
| F1 | HTTPS | Navegador do cliente | Front-end web | TLS, cookie de sessão | sim |
| F2 | HTTP/JSON | Front-end web | Serviço de pedidos | mTLS dentro da zona | sim |
| F3 | JDBC | Serviço de pedidos | Banco de pedidos | Credenciais do banco vindas do vault | não |
| F4 | HTTPS | Serviço de pedidos | Provedor de pagamentos | OAuth 2.0 client credentials | sim |
Cada linha vira uma regra de firewall, uma sonda de monitoramento e um possível timeout. Escrevê-las cedo é o que separa uma arquitetura de um desenho.
Camadas lógicas dentro de um serviço
Dentro de um componente implantável, a estrutura convencional tem quatro camadas. Os nomes variam; as responsabilidades, não.
- Apresentação (ou interface) — recebe as requisições, valida o formato, converte-as em chamadas para a camada de baixo e monta as respostas. Controllers HTTP, listeners de mensagens, comandos de CLI e jobs agendados moram aqui. Ela conhece códigos de status HTTP e JSON; nada abaixo dela conhece.
- Aplicação — orquestra um caso de uso: carrega os objetos de que precisa, aciona o comportamento do domínio, gerencia a transação, publica eventos. Não contém regras de negócio próprias, apenas o sequenciamento.
- Domínio — os conceitos de negócio e as regras que os governam: o que é um pedido válido, quando um desconto se aplica, o que acontece quando o estoque acaba. É a camada que deveria sobreviver a uma troca de framework, de banco de dados ou de canal de entrega.
- Infraestrutura — conversa com o mundo externo: acesso ao banco de dados, clientes HTTP, message brokers, sistemas de arquivos, e-mail. Ela implementa interfaces declaradas pela camada de domínio ou de aplicação.
// Presentation: knows HTTP, knows nothing about pricing rules
@RestController
@RequestMapping("/orders")
class OrderController {
private final PlaceOrder placeOrder;
OrderController(PlaceOrder placeOrder) { this.placeOrder = placeOrder; }
@PostMapping
ResponseEntity<OrderResponse> create(@Valid @RequestBody OrderRequest body) {
OrderId id = placeOrder.handle(body.customerId(), body.lines());
return ResponseEntity.created(URI.create("/orders/" + id.value()))
.body(new OrderResponse(id.value()));
}
}
// Application: one use case, one transaction, no rules of its own
@Service
class PlaceOrder {
private final OrderRepository orders; // interface owned by the domain
private final PricingPolicy pricing; // domain service
PlaceOrder(OrderRepository orders, PricingPolicy pricing) {
this.orders = orders; this.pricing = pricing;
}
@Transactional
OrderId handle(CustomerId customer, List<OrderLine> lines) {
Order order = Order.draft(customer, lines); // domain enforces invariants
order.price(pricing); // domain rule, not controller code
orders.save(order);
return order.id();
}
}
// Infrastructure: implements the domain's interface with JPA
@Repository
class JpaOrderRepository implements OrderRepository {
// mapping between Order and the persistence entity lives here, not in the domain
}
O detalhe importante é a direção da seta. PlaceOrder depende de OrderRepository, uma interface declarada ao lado de Order no pacote de domínio. JpaOrderRepository, no pacote de infraestrutura, a implementa. Assim, o domínio não importa JPA, Spring nem nada além de Java puro, e você consegue escrever testes unitários de Order e PricingPolicy sem banco de dados.
Camadas estritas versus relaxadas
Em camadas estritas, cada camada só pode chamar a que está imediatamente abaixo. Em camadas relaxadas, uma camada pode chamar qualquer camada inferior, então um controller pode ir direto a um repository para uma leitura trivial. Camadas estritas produzem métodos que só repassam a chamada; camadas relaxadas convidam os controllers a acumular lógica de negócio. Um meio-termo defensável: a apresentação só chama a aplicação; a aplicação chama o domínio e as interfaces de infraestrutura; ninguém chama a apresentação.
Portas e adaptadores: a mesma ideia, desenhada de outro jeito
A arquitetura hexagonal, também chamada de portas e adaptadores, e as variantes "clean" e "onion" que vieram depois mantêm as mesmas responsabilidades, mas mudam o desenho. Em vez de uma pilha com o banco de dados embaixo, as camadas de domínio e aplicação ficam no centro, e tudo o que conversa com o mundo externo — HTTP, banco de dados, broker, interface do usuário — é um adaptador plugado numa porta (uma interface) na borda do núcleo. O banco de dados deixa de estar "abaixo" do domínio; é só mais um adaptador, no mesmo patamar que o controller web.
Por que redesenhar faz diferença? Porque na pilha tradicional a camada de persistência fica embaixo e tudo parece depender dela, então os times desenham o domínio em torno das tabelas. Colocar o domínio no centro deixa a regra de dependência explícita: as dependências do código-fonte apontam para dentro, em direção ao domínio, nunca para fora. A interface OrderRepository acima é uma porta; JpaOrderRepository é um adaptador. Se você já segue essa regra, já tem uma arquitetura hexagonal, seja qual for a cara do diagrama.
Dados que atravessam as fronteiras entre camadas
Cada fronteira levanta a pergunta de quais objetos passam por ela.
- Entre apresentação e aplicação: use objetos de request e response específicos da API (records funcionam muito bem no Java 21). Nunca exponha entidades de persistência diretamente; o contrato da API e o schema das tabelas mudam por motivos diferentes, e serializar entidades arrasta para as respostas relacionamentos com carregamento lazy e campos internos.
- Entre aplicação e domínio: passe objetos de domínio. Mapear aqui costuma ser desperdício.
- Entre domínio e infraestrutura: a interface do repository fala em termos do domínio (
Order,OrderId) e o adaptador faz o mapeamento para o que o armazenamento precisar.
A regra prática: um DTO (data transfer object) justifica sua existência quando os dois lados evoluem de forma independente ou atravessam a rede; dentro de um único processo, entre duas camadas que sempre mudam juntas, é burocracia.
Erros comuns
Lógica de negócio no controller. Um controller que verifica o estoque, calcula um desconto e decide se avisa o depósito não pode ser reaproveitado por um job batch nem por um listener de mensagens, e não pode ser testado sem HTTP. Correção: o controller traduz e delega; todo if sobre o negócio vai para o domínio.
Domínio anêmico, serviço inchado. Classes de domínio só com getters e setters, enquanto uma classe *Service concentra todas as regras. As regras acabam duplicadas entre serviços e impossíveis de garantir como invariantes. Correção: coloque o comportamento nos objetos donos dos dados — quem valida é order.addLine(...), não OrderService.validateLine(...).
Entidades vazando para a API. Retornar entidades JPA de um endpoint REST acopla o formato trafegado ao schema, causa exceções de lazy initialization e expõe campos que você nunca quis publicar. Correção: records de resposta dedicados e um mapper explícito.
Camadas por tecnologia, pacotes só por camada. Um código com pacotes de primeiro nível controllers, services e repositories, com centenas de classes em cada um, é difícil de navegar e deixa qualquer serviço chamar qualquer repository. Correção: organize primeiro por funcionalidade (orders, billing), depois por camada dentro da funcionalidade, e deixe o que é interno à funcionalidade como package-private.
Tiers adicionados por modismo. Um "tier de integração" separado para um sistema com um único parceiro externo acrescenta um salto e um deploy à toa. Correção: adicione um tier quando você souber dizer que propriedade de isolamento, escalabilidade ou segurança ele traz.
Preocupações transversais implementadas em cada camada. Log, autenticação e gerenciamento de transações copiados em todas as classes. Correção: trate-os uma única vez — filtros ou interceptors na borda, transações declarativas na camada de aplicação, aspectos onde fizer sentido.
Boas práticas
- Escreva a matriz de fluxos antes do primeiro diagrama de deploy; ela força as perguntas de segurança e rastreabilidade.
- Garanta a regra de dependência de forma automatizada. Testes no estilo ArchUnit que quebram o build quando
domainimportainfrastructurelevam uma hora para configurar e evitam anos de deriva. - Mantenha a camada de domínio livre de anotações de framework sempre que for viável. Se uma regra precisa de
@Transactionalpara estar correta, ela está na camada errada. - Faça da camada de aplicação a fronteira transacional: um caso de uso, uma transação, um único lugar para olhar quando algo ficou commitado pela metade.
- Trate um novo tier como uma decisão de produto, com um dono, um SLA e um runbook, e não como uma caixinha num slide.
- Quando uma funcionalidade atravessa várias camadas para fazer algo trivial, questione o rigor das camadas em vez de adicionar métodos que só repassam a chamada.
Principais conclusões
- Tiers são fronteiras de deploy; camadas são fronteiras no código. Mude um sem presumir que o outro precisa mudar.
- O modelo de dois tiers punha a lógica no cliente, o de três a levou para um servidor de aplicação e o n-tier dividiu o meio em nome do isolamento e da escala — cada etapa trocou simplicidade por uma propriedade que você deveria saber nomear.
- Descreva um sistema em quatro níveis — funcional, de aplicações, técnico e de software — e use uma matriz de fluxos para ligar as aplicações à infraestrutura.
- Dentro de um componente, as dependências apontam para dentro, em direção ao domínio; é o domínio que declara as interfaces que a infraestrutura implementa.
- A arquitetura hexagonal é a mesma regra com o domínio desenhado no centro, o que impede os times de projetar tudo em torno do banco de dados.
- Os antipadrões caros são lógica nos controllers, domínios anêmicos e entidades trafegando na rede; os três são baratos de evitar cedo e caros de desfazer depois.
Fontes e leituras complementares
Perguntas frequentes
- Uma arquitetura three-tier é a mesma coisa que uma arquitetura de três camadas?
- Não. Tiers são físicos: processos ou máquinas separados, como um navegador, um servidor de aplicação e um servidor de banco de dados. Camadas são lógicas: grupos de código com responsabilidades distintas que podem rodar todos num único processo. Um deploy three-tier normalmente contém mais de três camadas lógicas, e uma aplicação desktop de um único tier pode muito bem ser organizada em camadas de forma limpa.
- Cada camada deve ter suas próprias classes de dados (DTOs)?
- Tenha uma representação separada sempre que o formato ou o ciclo de vida forem diferentes. Os objetos de request e response da API devem ser distintos das entidades de persistência, porque evoluem por motivos diferentes e expõem campos diferentes. Entre a camada de aplicação e a de domínio, passar os objetos de domínio diretamente costuma funcionar bem; adicionar ali uma etapa de mapeamento é burocracia sem benefício.
- A arquitetura em camadas está ultrapassada em comparação com microsserviços?
- Elas respondem a perguntas diferentes. As camadas organizam o código dentro de uma unidade implantável; os microsserviços dividem um sistema em várias unidades implantáveis. Todo microsserviço bem construído é organizado em camadas internamente, e a maioria das migrações bem-sucedidas para microsserviços parte de um monólito cujas camadas eram limpas o bastante para serem fatiadas. Camadas mal feitas tornam os dois estilos penosos.
- Uma camada inferior pode chamar uma camada superior?
- Não diretamente. As dependências devem seguir um único sentido, geralmente de fora para dentro. Quando uma camada inferior precisa disparar um comportamento numa superior, ela publica um evento ou chama uma interface que é dela e que a camada superior implementa. Essa inversão mantém o núcleo livre de referências a frameworks e a mecanismos de entrega.