Começando pela pergunta errada
A pergunta que mais ouço sobre Clean Architecture é "quantas camadas eu preciso?". É a pergunta errada, e ela explica por que tanto projeto nasce com sete pastas vazias e morre com regra de negócio dentro do controller. Camada não é objetivo. Camada é um mecanismo para uma coisa só: conseguir trocar uma decisão sem reescrever o resto. Banco, framework web, fila, provedor de e-mail, a exchange que você consulta. Se nenhuma dessas decisões tem chance real de mudar, o mecanismo não paga o próprio custo.
Então a conversa que eu gosto de ter antes de abrir o editor é outra: quais decisões deste sistema eu quero poder reverter daqui a um ano? A resposta define onde vale colocar uma fronteira e onde um módulo simples resolve.
O que estou de fato comprando
Quando separo domínio, aplicação e infraestrutura, estou comprando três coisas concretas. Primeiro, testes que não precisam de banco nem de rede: o caso de uso recebe interfaces, então eu testo a regra com objetos em memória e deixo a integração para poucos testes mais lentos. Segundo, um vocabulário compartilhado: quando entidade, caso de uso e porta têm nome do negócio, a conversa com produto e dados fica mais curta. Terceiro, a possibilidade de trocar a borda: migrei um serviço de API REST para funções Lambda atrás de SQS sem tocar em nenhum caso de uso, porque a borda era só um adaptador.
E estou pagando com três coisas igualmente concretas. Mais arquivos por funcionalidade, o que torna o onboarding mais lento. Mapeamento entre DTOs, entidades e modelos de persistência, que é código chato e que erra em silêncio. E indireção: para achar onde uma query roda de verdade, o leitor atravessa uma interface e uma injeção de dependência.
Se o sistema é um CRUD com meia dúzia de telas e um time pequeno, o custo de indireção supera o ganho. Nesses casos prefiro módulos por funcionalidade, com a regra de negócio isolada em funções puras, e deixo a arquitetura em camadas para quando aparecer a segunda integração externa ou o segundo canal de entrada. Arquitetura boa é a que você consegue adiar.
O método que uso para descobrir as fronteiras
Quando o sistema justifica a estrutura, o problema vira outro: errar os nomes e os limites no início. Esse erro é o que dói mais tarde, porque renomear um domínio depois que ele virou tabela, fila e contrato de API é caro. O que faço é um exercício curto, antes de qualquer código, e é dele que saíram os checklists que eu publicava aqui antes.
- Listo os fluxos principais em frases curtas, sem pensar em tela. "Usuário cadastra operação", "sistema simula estratégia com histórico", "robô executa ordem real".
- Grifo os substantivos e os atores. Usuário, conta, operação, ordem, estratégia, histórico de preço, exchange. Cada substantivo que aparece em mais de um fluxo é candidato a entidade.
- Agrupo em domínios que não se misturam. Aqui está o erro mais comum:
operation,orderemarket_dataparecem a mesma coisa e não são. Uma operação é a intenção do usuário, uma ordem é o que foi mandado para a exchange, dado de mercado é o que veio de fora. Misturar os três gera uma tabela com trinta colunas opcionais. - Modelo só as entidades principais, com nome e campos, sem pensar em ORM.
- Transformo cada ação em um caso de uso com nome de verbo:
SimulateGridStrategy,CreateOrder,GetAccountBalance. - Marco as fronteiras externas: API da exchange, banco, fila, storage. Cada uma vira uma interface que o domínio define e a infraestrutura implementa.
Só depois disso a estrutura de pastas aparece, e ela aparece quase sozinha:
Onde a teoria quebra na prática
Transações que cruzam domínios
O diagrama bonito tem cada domínio isolado. A vida real tem "criar a ordem e debitar o saldo" na mesma transação. Dá para resolver com um unit of work que o caso de uso abre e os repositórios compartilham, mas isso é uma decisão arquitetural que o livro não toma por você. Minha regra: se duas entidades precisam ser consistentes na mesma transação com frequência, talvez elas sejam do mesmo domínio e eu separei errado.
O domínio anêmico
É fácil acabar com entidades que são só dataclasses e casos de uso gigantes que fazem tudo. Isso é a arquitetura em camadas com nome novo. O sinal de alerta é um caso de uso com mais de uma regra de negócio dentro de if. A regra pertence à entidade; o caso de uso só orquestra.
Performance de leitura
Repositório que devolve entidade completa é ótimo para escrita e péssimo para uma tela de listagem com paginação e filtros. Separo leitura de escrita quando isso aparece: a query fala com o banco diretamente e devolve um DTO, sem passar pela entidade. É uma concessão deliberada, documentada no nome da pasta (queries/), e não uma violação escondida.
Nos primeiros projetos em que apliquei isso, criei interface para tudo, inclusive para coisas que nunca teriam segunda implementação. Hoje só crio a porta quando existe uma fronteira real (rede, disco, processo) ou quando preciso dela para testar. Interface sem segunda implementação e sem teste que a use é só cerimônia.
Resumo da conversa
- Camadas são um meio. O fim é poder reverter decisões caras.
- O investimento está em descobrir os domínios e nomear bem, não em criar pastas.
- Transações cruzadas, domínio anêmico e leitura pesada são onde o modelo precisa de concessões explícitas.
- Arquitetura boa é a que dá para adiar até o sistema pedir.
Igor Nascimento