02 - Camadas (Layered Architecture)
video: https://youtu.be/... no frontmatter da nota.A arquitetura em camadas organiza o código em fatias horizontais, cada uma com uma responsabilidade única, em que uma camada só conversa com a camada imediatamente inferior. É o estilo mais difundido na indústria, presente em aplicações corporativas de CRUD, em sistemas legados de longa vida e em boa parte dos frameworks web que separaram rotas, serviços e repositórios por padrão. Sua promessa é separação de responsabilidades com curva de aprendizado baixa, e sua armadilha recorrente é virar estrutura cerimonial, em que as camadas existem no nome e a lógica de negócio escorre para os lugares errados. Em produção, o estilo funciona bem quando cada camada tem razão de existir clara e verificável.
Conceito e Camadas Típicas
A configuração clássica tem quatro camadas: apresentação, responsável pelo protocolo e pela interação com o usuário; negócio, onde vivem as regras e os casos de uso; persistência, que traduz objetos em consultas; e banco de dados, que garante integridade e armazenamento. Vale distinguir camadas de tiers: camadas são divisões lógicas de código, e tiers são divisões físicas de implantação; um sistema pode ter quatro camadas em dois tiers. A organização pressupõe que cada nível abstrai o inferior: a camada de negócio não sabe se a apresentação é web ou batch, e a de persistência não sabe por que aquela consulta existe.
Papel de cada nível na divisão de trabalho:
- Apresentação: decide tudo que é detalhe de transporte, como rota, código de status, serialização e autenticação do canal.
- Negócio: decide o que o sistema faz com o pedido do usuário, aplicando regras que valem igualmente para web, fila ou lote.
- Persistência: decide como objetos viram linhas e consultas, sem opinião sobre quando aquilo é correto do ponto de vista comercial.
- Banco de dados: decide como o dado é guardado de forma íntegra, com restrições, índices e transações como última linha de defesa.
A força do estilo está na substituição previsível: trocar a interface gráfica, o protocolo de exposição ou o banco sem tocar nas regras, desde que as dependências apontem na direção correta. A fraqueza está no seu custo de cerimônia: cada nível extra adiciona mapeamento e chamada de passagem, e quando a lógica real é fina, o sistema inteiro vira correio de mensagens entre camadas.
Regra de Dependência e Fluxo de uma Requisição
A regra estrutural é única e inegociável: dependências apontam para baixo, e uma requisição desce camada por camada, transformando-se a cada nível, com a resposta subindo pelo mesmo caminho. Uma camada não pula níveis sem justificativa declarada e não conhece nada acima de si; a camada de negócio não pode receber um objeto HTTP, e a de persistência não pode devolver exceção com vocabulário de protocolo. É importante notar que a regra vale em tempo de compilação: o negócio referencia abstrações de persistência, e a implementação concreta é injetada em execução, o que é a base da testabilidade do estilo.
// Camada de apresentação: traduz HTTP para o contrato do negócio; sem regras
@RestController
class ControllerPedidos {
// injeta a camada de negócio; o transporte é detalhe daqui para baixo
RespostaPedido post(RequisicaoPedido req) {
// valida formato, autenticação de transporte e mapeia para comando
}
}
// Camada de negócio: onde vivem as regras; não conhece HTTP nem SQL
class ServicoPedidos {
// decide aprovação, aplica desconto, garante limite e orquestra efeitos
// chamada única para baixo: o repositório abstrai a persistência
}
// Camada de persistência: traduz objetos para consultas; sem regras de negócio
class RepositorioPedidos {
// SELECT e INSERT com mapeamento; condições comerciais não pertencem aqui
}
Fluxo típico de uma requisição de ponta a ponta:
- Entrada na apresentação: o framework recebe HTTP, autentica o transporte e valida formato de campos.
- Tradução para comando: o controller converte DTO de entrada em comando do caso de uso, sem decidir nada de negócio.
- Execução no negócio: o serviço aplica regras, consulta estado via interfaces de repositório e decide o desfecho.
- Acesso à persistência: o repositório monta a consulta, executa no banco e reconstrói entidades.
- Volta com transformação: cada camada converte de volta, e a resposta chega ao cliente com vocabulário de protocolo.
Camadas Fechadas, Abertas e o Antipadrão Sinkhole
Camadas fechadas obrigam a requisição a passar por todas elas em ordem, o que protege decisões de alto nível de mudanças em detalhes baixos. Camadas abertas são atalhos declarados, úteis quando uma camada de utilidades compartilhadas não faz sentido para todo o fluxo; a abertura precisa ser explícita, documentada e rara, ou a pilha perde sentido. O equilíbrio é decisão arquitetural: cada nível fechado compra isolamento com chamadas de passagem.
O antipadrão sinkhole surge quando a maioria das requisições atravessa todas as camadas sem fazer nada: o controller só repassa, o serviço só repassa, o repositório executa um CRUD trivial. A métrica prática é a proporção de fluxos puramente de passagem; acima de oitenta por cento, a arquitetura está cobrando cerimônia sem entregar isolamento. As saídas honestas são duas: consolidar camadas quando a lógica é realmente fina, ou aceitar o custo consciente em troca da separação que ainda faz sentido, e registrar essa escolha para que ela não seja confundida com descuido.
Regras práticas para decidir fechamento e abertura:
- Camada fechada por padrão: o fluxo passa por ela, e o atalho é exceção que precisa de motivo escrito.
- Camada aberta só com escopo claro: utilidades de formatação podem ser abertas; regras de negócio, nunca.
- Atalho permitido de cima para baixo, com registro: pular um nível é aceitável quando documentado, e nunca de baixo para cima.
Camadas Anêmicas e Vazamento de Lógica
O modelo anêmico é o sintoma clássico: entidades reduzidas a sacos de dados, com toda a lógica concentrada na camada de serviço. Com o tempo, o serviço vira um procedimento gigante, e as regras começam a escorrer para as bordas: validações comerciais no controller, condições de desconto dentro do SQL, regras de estado escondidas em triggers. O vazamento costuma aparecer quando entra o segundo canal de entrada, como um processamento em lote ou um consumidor de fila, e a regra que morava no controller precisa ser reescrita, porque nunca esteve onde deveria.
Consequências típicas do vazamento:
- Duplicação de regras: o mesmo critério de crédito implementado no endpoint, no job e na consulta, divergindo em silêncio.
- Testes frágeis: validar regra de negócio exige subir HTTP e banco, porque ela não existe isolada.
- Custo de evolução: mudar uma política comercial exige caçar sua cópia em três camadas diferentes.
- Repositório com opinião de negócio: consultas que decidem elegibilidade fazem a persistência virar segunda camada de regras.
Testabilidade e Estratégias de Verificação
O valor testável do estilo depende de as dependências serem invertidas nas bordas: a camada de negócio recebe interfaces de repositório, e não classes concretas, para que os testes substituam o nível inferior por dublês rápidos. Com isso, a maior parte das regras é verificada em testes de unidade milissegundo por caso, sem banco e sem HTTP. A camada de apresentação, fina por dever, recebe testes de contrato leves, e a persistência recebe testes de integração com banco efêmero, verificando mapeamento e consultas reais.
- Unidade no negócio: serviços testados com repositórios falsos em memória, cobrindo regras e caminhos de erro.
- Integração na persistência: consultas executadas contra banco real de teste, capturando divergência de mapeamento.
- Contrato na apresentação: rotas verificadas quanto a status, formato e autenticação de transporte, sem lógica interna.
- Cenário de ponta a ponta: poucos fluxos críticos cobertos de ponta a ponta em ambiente próximo ao de produção.
Se a injeção de dependência não existir e os serviços instanciarem repositórios diretamente, a testabilidade prometida não chega a existir; o estilo sem inversão nas bordas é apenas organização de pastas.
A proporção entre tipos de teste segue a lógica da arquitetura: como o negócio está isolado no meio, ele concentra a maioria dos testes de unidade, e as pontas recebem cobertura menor e mais específica. Quando o número de testes de ponta a ponta cresce muito, é sinal de que as regras não estão isoladas o suficiente, e o sistema está testando infraestrutura em vez de lógica.
Responsabilidades por Camada
| Camada | Responsabilidade legítima | Não deve conter |
|---|---|---|
| Apresentação | Protocolo, autenticação de transporte, mapeamento de DTO | Regras de negócio, SQL, decisão de estado |
| Negócio | Casos de uso, regras, orquestração de efeitos | Detalhes de HTTP, de ORM e de filas |
| Persistência | Consultas, mapeamento, transações técnicas | Condições comerciais, cálculo de valores |
| Banco | Integridade, índices, restrições, transações | Lógica de aplicação escondida em triggers |
A tabela funciona como checklist de revisão: quando um pull request traz condição comercial em SQL, ou cálculo de tarifa no controller, a fronteira vazou e a correção é mover a regra para o nível certo, não apenas discuti-la.
Camadas versus Arquiteturas Derivadas
Estilos como hexagonal, cebola e clean são, em essência, refinamentos do desenho em camadas que invertem a direção da dependência mais interna: em vez de o negócio depender da persistência, todos dependem de abstrações definidas pelo negócio. Para sistemas de CRUD dominante com lógica fina, camadas diretas bastam e cobram menos cerimônia. Para domínios ricos, com regras que precisam sobreviver a trocas de infraestrutura e a vários canais de entrada, a inversão de dependência paga o próprio custo, e o desenho em camadas evolui naturalmente para portas e adaptadores.
Contextos em que camadas diretas funcionam bem:
- Sistemas de back office: cadastros, aprovações e relatórios com regras modestas e canais de entrada poucos.
- Equipes em formação: a organização horizontal é didática, e a localização de qualquer código é previsível.
- Produtos com prazo curto de validação: a cerimônia mínima acelera a entrega enquanto o domínio ainda está se descobrindo.
Erros Comuns e Boas Práticas
| Sintoma em produção | Causa provável | Mitigação |
|---|---|---|
| Serviço com milhares de linhas e tudo dentro | Camada de negócio como depósito | Quebrar por casos de uso e entidades ricas |
| Mesma regra em controller, job e SQL | Lógica vazada para as bordas | Centralizar na camada de negócio e testar |
| Requisição atravessa tudo sem fazer nada | Sinkhole por hábito, não por decisão | Consolidar camadas ou registrar o custo aceito |
| Teste de regra exige banco e HTTP | Dependências concretas nas bordas | Interfaces nos repositórios e injeção |
| Exceção de SQL estourando no controller | Erros de baixo nível atravessando níveis | Tradução de exceções na fronteira da camada |
| Migrations conflitando entre equipes | Schema único sem dono por área | Convenção de prefixo por módulo e revisão cruzada |
| Consulta lenta escondida no serviço | N+1 e JOINs criados sem critério | Revisão de consultas na persistência com medição |
Boas práticas que mantêm o estilo saudável:
- Imponha a regra de dependência por teste: ferramentas de análise de dependência no pipeline valem mais que qualquer diagrama.
- Mantenha a apresentação fina: se o controller decide algo que muda comportamento comercial, a fronteira foi violada.
- Dê voz ativa à camada de negócio: entidades com comportamento e casos de uso explícitos evitam a deriva anêmica.
- Registre camadas abertas e por quê: todo atalho declarado tem contexto; sem registro, o atalho vira convenção acidental.
- Meça o sinkhole periodicamente: amostrar fluxos e classificar passagem versus lógica real orienta simplificação com dado.
- Mantenha DTOs nas bordas, não no meio: objetos de transporte param na apresentação; entidades e comandos vivem no negócio.
- Reveja a pilha a cada trimestre: camadas que não justificam mais o custo podem ser consolidadas sem drama, se a decisão for registrada.