Arquitetura em Camadas
Organiza o software em camadas horizontais com responsabilidades bem definidas, onde cada camada depende apenas das camadas abaixo dela — criando um sentido único de dependência e isolando a lógica de negócio da infraestrutura.
Intenção
Dividir o sistema em camadas horizontais coesas, onde cada camada tem uma responsabilidade clara e conhece apenas as camadas imediatamente abaixo dela. Isso cria uma regra de dependência unidirecional: camadas superiores dependem de camadas inferiores, nunca o contrário.
A Arquitetura em Camadas (também chamada de Layered Architecture ou N-tier Architecture) é um dos padrões arquiteturais mais antigos e difundidos. É a base sobre a qual frameworks como Laravel, Spring, Django e ASP.NET constroem suas convenções de organização de código. Não é um padrão GoF — é um padrão arquitetural de granularidade maior, que organiza como grupos de classes se relacionam entre si.
Problema
Em aplicações sem separação de camadas, é comum encontrar:
- Controllers que fazem queries diretamente no banco de dados.
- Entidades de domínio que conhecem detalhes de serialização HTTP ou de persistência.
- Regras de negócio espalhadas entre controllers, models e até views.
- Testes que precisam de banco de dados real para verificar uma regra de negócio simples.
O resultado é alta fragilidade: uma mudança no banco quebra o controller; uma mudança no contrato HTTP quebra o domínio. A Arquitetura em Camadas resolve isso ao tornar explícito onde cada tipo de código vive e de quem ele pode depender.
Estrutura
As quatro camadas típicas
┌──────────────────────────────────────────────────────────┐
│ Camada de Apresentação │
│ Controllers HTTP, CLI handlers, serialização JSON, │
│ templates, validação de entrada (formato/tipo) │
└──────────────────────────┬───────────────────────────────┘
│ depende de
▼
┌──────────────────────────────────────────────────────────┐
│ Camada de Aplicação (Serviço) │
│ Casos de uso, orquestração de domínio, transações, │
│ autorização por caso de uso, DTOs de entrada/saída │
└──────────────────────────┬───────────────────────────────┘
│ depende de
▼
┌──────────────────────────────────────────────────────────┐
│ Camada de Domínio │
│ Entidades, Value Objects, regras de negócio, │
│ interfaces de repositório, eventos de domínio │
│ (núcleo puro — não importa nada das camadas externas) │
└──────────────────────────┬───────────────────────────────┘
│
┌──────────┴────────────────────────────────────────────┐
│ SEM DIP (layered clássica pura): │
│ Domínio → Infraestrutura │
│ (depende diretamente da implementação concreta) │
│ │
│ COM DIP + Repository (prática recomendada): │
│ Infraestrutura → Domínio │
│ (Infra implementa a interface definida no Domínio; │
│ o Domínio NÃO conhece a Infraestrutura concreta) │
└───────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Camada de Infraestrutura / Persistência │
│ Implementações de repositório (SQL, NoSQL, API), │
│ gateways externos, filas, cache, envio de e-mail │
└──────────────────────────────────────────────────────────┘
Sentido das setas: quem aponta depende de quem é apontado.
Com DIP, a Infraestrutura aponta para o Domínio (implementa suas interfaces);
o Domínio permanece sem dependências externas — a forma mais estável.
Strict vs Relaxed Layering
Em strict layering, cada camada só pode chamar a camada imediatamente abaixo dela. A Camada de Apresentação não pode chamar a Camada de Domínio diretamente — tem de passar pela Camada de Aplicação. Isso garante o máximo de isolamento, mas pode adicionar código de passagem (pass-through) desnecessário em casos simples.
Em relaxed layering, uma camada pode chamar qualquer camada abaixo dela, não apenas a imediata. A Camada de Apresentação pode chamar diretamente a Camada de Domínio para operações de leitura simples. Mais pragmático, mas requer disciplina para não criar atalhos que deteriorem o isolamento.
Strict layering:
Apresentação → Aplicação → Domínio → Infraestrutura
(cada camada só vê a imediatamente abaixo)
Relaxed layering:
Apresentação → Aplicação
Apresentação → Domínio (permitido para leituras simples)
Aplicação → Domínio
Aplicação → Infraestrutura (via interfaces do Domínio)
Domínio → (nada externo)
Como funciona — fluxo de uma requisição
Seguindo o sentido das dependências, uma requisição típica flui de cima para baixo, e a resposta sobe de volta:
- Apresentação: o Controller recebe a requisição HTTP, valida o formato dos parâmetros (tipos, campos obrigatórios) e constrói um DTO de comando/query para a camada de Aplicação.
- Aplicação: o Application Service (ou Use Case) recebe o DTO, aplica regras de autorização por caso de uso, coordena as operações de domínio necessárias e delega persistência às interfaces de repositório.
- Domínio: entidades e serviços de domínio aplicam as regras de negócio. Aqui ficam as invariantes — coisas que devem ser sempre verdadeiras, independentemente de qualquer framework ou banco.
- Infraestrutura: implementações concretas de repositórios executam queries; adaptadores de serviços externos fazem chamadas HTTP; o resultado sobe de volta pela cadeia.
A regra de ouro: as dependências só apontam para baixo. O Domínio nunca importa nada da Aplicação ou da Apresentação. A Infraestrutura implementa interfaces definidas pelo Domínio — mas o Domínio não conhece a Infraestrutura.
Quando usar
- Aplicações de negócio com lógica de domínio não trivial: sistemas com regras complexas de autorização, workflows, cálculos de negócio. A separação de camadas garante que a lógica de domínio permaneça testável e isolada.
- Equipes maiores com responsabilidades divididas: a separação em camadas cria fronteiras naturais — uma equipe cuida da camada de domínio, outra da infraestrutura, outra da apresentação — com contratos claros entre elas.
- Quando há múltiplos pontos de entrada: uma API REST, uma interface CLI e um worker de fila podem compartilhar as mesmas camadas de Aplicação e Domínio — apenas a Apresentação muda.
- Para facilitar a substituição de infraestrutura: trocar de banco de dados, de provedor de e-mail ou de sistema de filas deve impactar apenas a camada de Infraestrutura, sem tocar no Domínio.
Quando evitar
- CRUDs simples sem lógica de domínio real: aplicações que são essencialmente formulários de criação, leitura, atualização e deleção de dados sem regras complexas não justificam quatro camadas. Uma estrutura mais simples — ou mesmo MVC direto — é mais adequada.
- Microsserviços muito pequenos e focados: um serviço que faz apenas uma coisa (ex.: envio de notificações) raramente precisa de camadas de domínio separadas. O overhead arquitetural não se paga.
- Prototipagem e MVPs: a estrutura em camadas tem um custo inicial de organização. Em fases de descoberta, o foco deve ser na validação da ideia — a arquitetura pode evoluir depois.
Prós e contras
Prós
- Separação de responsabilidades clara e verificável — é possível auditar se a regra de dependência está sendo respeitada.
- Domínio completamente testável sem banco de dados, sem HTTP, sem framework.
- Facilita a substituição de infraestrutura sem impacto no domínio.
- Bem compreendida pela maioria das equipes — tem ampla documentação e exemplos em todas as linguagens principais.
- Compatível com múltiplos pontos de entrada (HTTP, CLI, workers) compartilhando o mesmo núcleo.
Contras
- Overhead de estruturação para sistemas simples — muitas camadas para um CRUD básico.
- Risco de "efeito lasanha": proliferação de camadas sem propósito claro (camada de "helpers", camada de "utilities", subcamadas de subcamadas).
- A regra de dependência não é imposta pelo compilador — requer disciplina da equipe ou ferramentas de análise estática (ex.: ArchUnit, Deptrac).
- Código de passagem (pass-through): objetos que só existem para transportar dados entre camadas sem transformação real aumentam o volume de código sem valor.
Armadilhas comuns
1. Efeito lasanha — camadas demais sem propósito
Cada nova camada adicionada deve ter uma justificativa clara: que tipo de responsabilidade ela encapsula que não pertence às camadas adjacentes? Camadas como "Utils", "Helpers", "Common" ou "Shared" que acumulam tudo que não sabe onde colocar são um sinal de que a estrutura não está sendo pensada com cuidado. Prefira poucas camadas com fronteiras nítidas a muitas camadas com fronteiras vagas.
2. Vazamento entre camadas
O tipo de violação mais comum: entidades do Domínio com anotações de banco de dados (JPA, Doctrine, Eloquent com cast que conhecem o schema), Controllers que manipulam diretamente objetos de banco (ActiveRecord no Controller), ou a camada de Domínio importando tipos de um framework HTTP. Esses vazamentos destroem o isolamento gradualmente — a violação inicial parece inofensiva, mas cria a precedência para as seguintes.
Atenção ao Active Record: ORMs com padrão Active Record (Eloquent no Laravel, ActiveRecord no Rails) fundem a entidade de domínio com a infraestrutura de persistência. São extremamente produtivos, mas tornam a separação estrita entre Domínio e Infraestrutura mais difícil. Em projetos onde essa separação é crítica, prefira o padrão Repository com mapeamento explícito.
3. Domínio anêmico
Quando as regras de negócio que deveriam ficar no Domínio migram para os Application Services, o Domínio se torna apenas um conjunto de classes de dados sem comportamento — o Anemic Domain Model descrito por Martin Fowler. O resultado é que toda regra fica em serviços, que passam a ser difíceis de compor e testar de forma isolada. Mantenha as invariantes e comportamentos centrais nas entidades e Value Objects do Domínio.
4. Não verificar a regra de dependência automaticamente
Em equipes grandes, a regra "camadas superiores não podem ser importadas por camadas inferiores" é violada aos poucos, especialmente por desenvolvedores menos experientes ou em situações de pressão. Use ferramentas de análise de dependências (ArchUnit para Java, Deptrac para PHP, módulos com restrições de importação em TypeScript/NestJS) para tornar a violação um erro de build, não apenas uma prática ruim.
Arquiteturas e padrões relacionados
O MVC pode ser visto como uma especialização da Arquitetura em Camadas focada na separação entre a camada de apresentação (View + Controller) e a camada de dados/negócio (Model). A Arquitetura em Camadas define o que acontece dentro do Model — como ele se subdivide em Aplicação, Domínio e Infraestrutura.
A Arquitetura Hexagonal compartilha a mesma preocupação de isolar o domínio da infraestrutura, mas inverte explicitamente as dependências: em vez de "infraestrutura fica abaixo do domínio", ela diz "infraestrutura implementa interfaces definidas pelo domínio". O resultado é o mesmo isolamento, mas com uma visualização diferente — o domínio no centro, adaptadores nas bordas.
Clean Architecture e Onion Architecture são evoluções da Arquitetura em Camadas com ênfase explícita na Inversão de Dependência: as camadas internas (domínio) definem interfaces; as camadas externas (infraestrutura) implementam essas interfaces. A direção da dependência é explicitamente invertida em relação à Arquitetura em Camadas clássica, onde a Infraestrutura fica "abaixo" e é naturalmente dependência das camadas superiores.