Arquitetura de software: por que não existe bala de prata
Toda vez que alguém pergunta "qual é a arquitetura certa pra esse projeto?" a resposta honesta é "depende". Não existe bala de prata em arquitetura de software — o que funciona bem pra um time de cinquenta pessoas pode ser puro over-engineering pra um MVP de duas semanas, e vice-versa.
O que realmente define a escolha
- Tamanho do time
- Tempo de vida esperado do projeto
- Complexidade do domínio de negócio
- Recursos disponíveis (tempo, orçamento, experiência do time)
Esses fatores puxam em direções diferentes. Os três exemplos abaixo mostram abordagens distintas, cada uma com um trade-off diferente entre rigor e velocidade.
Exemplo 1 — DDD (Domain-Driven Design)
Leia mais em Domain-Driven Design, de Martin Fowler.
DDD não é uma estrutura de pastas, é uma forma de pensar o domínio de negócio antes de pensar em código.
- Linguagem ubíqua — o time (negócio + dev) usa os mesmos termos pra descrever o domínio, sem tradução entre "como o cliente chama" e "como o código chama".
- Bounded context — fronteiras explícitas entre partes do sistema que têm seu próprio modelo e vocabulário, mesmo que falem sobre a "mesma" entidade de formas diferentes.
- Agregados, entidades e value objects — formas de modelar regras de negócio garantindo consistência dentro de uma fronteira transacional.
Quando ajuda: domínios genuinamente complexos, com regras de negócio que não mapeiam bem pra CRUD (ex: sistemas financeiros, logística, seguros).
Quando é fricção: a maioria dos projetos não chega perto desse nível de complexidade. Aplicar todo o aparato estratégico do DDD (bounded contexts, agregados, linguagem ubíqua documentada) em um CRUD simples é pagar um preço de modelagem que o domínio não pede.
Exemplo 2 — Clean Architecture
Leia mais em The Clean Architecture, de Robert C. Martin (Uncle Bob).
Organiza o código em camadas concêntricas, com uma regra única: dependências sempre apontam pra dentro.
- Entities — regras de negócio mais centrais, independentes de tudo.
- Use Cases — orquestram as regras de negócio pra um caso de uso específico.
- Interface Adapters — controllers, presenters, gateways que traduzem entre o mundo externo e os use cases.
- Frameworks & Drivers — banco de dados, web framework, UI — tudo que é detalhe de implementação.
Quando ajuda: quando testabilidade e independência de framework importam de verdade — por exemplo, trocar banco de dados ou subir a mesma regra de negócio em contextos diferentes (API, CLI, worker).
Quando é fricção: o número de camadas e interfaces de indireção cresce rápido. Em um projeto pequeno, isso costuma significar mais arquivos e mais saltos pra entender um fluxo simples, sem ganho real — ninguém vai trocar de banco de dados num MVP.
Exemplo 3 — Package by Feature / Screaming Architecture
Leia mais em Screaming Architecture, também de Robert C. Martin.
Essa é a abordagem que eu sigo geralmente nos meus projetos. Em vez de organizar o projeto por camada técnica (uma pasta controllers/ com tudo, uma services/ com tudo, uma repositories/ com tudo — o chamado "package by layer"), organizo por domínio de negócio ("package by feature"). Cada domínio vira uma pasta praticamente autocontida:
/domains
/funcionario
funcionario.controller.ts
funcionario.service.ts
funcionario.repository.ts
funcionario.schema.ts
/departamento
departamento.controller.ts
departamento.service.ts
departamento.repository.ts
departamento.schema.ts
A ideia do Uncle Bob em "Screaming Architecture" é que a estrutura de pastas deveria "gritar" o domínio do negócio, não o framework — olhando a árvore de pastas, dá pra saber que o sistema lida com funcionários e departamentos, não que foi feito em Express ou NestJS. É uma aplicação mais pragmática da ideia de "separar por domínio" do DDD, sem todo o aparato estratégico (não preciso de bounded context documentado ou agregado formal pra saber que "funcionário" e "departamento" são conceitos diferentes).
Vantagens que eu sinto no dia a dia:
- Tudo que é relacionado a uma feature está num lugar só — não preciso pular entre quatro pastas técnicas pra entender o fluxo completo de "funcionário".
- Escala bem com o time: dá pra ter um time (ou uma pessoa) dono de um domínio sem pisar na pasta de outro.
- Remover ou isolar um domínio inteiro é simples — é só a pasta.
- Reduz conflito de merge, porque mudanças tendem a ficar concentradas dentro de um domínio.
Trade-off: regra de negócio que cruza domains é o ponto de atenção. Se funcionario depende de departamento, a tentação é importar o departamento.repository direto dentro do funcionario.service, criando acoplamento forte entre domains. O jeito mais saudável é funcionario.service conversar com departamento.service (ou com uma interface), nunca pular direto pro repository do outro domínio — senão a vantagem de isolamento se perde.
O que eu acho disso
Nenhuma dessas três é "a certa". São ferramentas com preços diferentes.
DDD completo só se paga quando o domínio de negócio é genuinamente complexo — a maioria dos projetos que eu vi não chega nem perto disso, e aplicar todo o ritual (linguagem ubíqua documentada, bounded contexts, agregados) em cima de um domínio simples é o tipo clássico de over-engineering: você gasta mais tempo modelando do que entregando.
Clean Architecture ajuda quando testabilidade e independência de framework importam de verdade. Mas o preço é indireção — camadas, interfaces, mais arquivos pra seguir um fluxo simples. Em projeto pequeno isso vira atrito sem retorno.
Package by Feature / Screaming Architecture é a que eu mais uso no dia a dia justamente porque entrega grande parte do benefício do DDD (coesão, fronteiras claras) com uma fração do custo de modelagem. Mas também tem limite: se o domínio de negócio for de fato complexo, só separar pastas não resolve — em algum momento as regras de consistência e os agregados do DDD fazem falta.
No fim, arquitetura é ferramenta pra resolver problemas concretos — comunicação de time, manutenibilidade, velocidade de entrega. Escolher a "mais bonita" ou a "mais na moda" sem olhar pro contexto do projeto é o erro mais comum que eu vejo. A pergunta certa nunca é "qual arquitetura é a melhor", e sim "qual o problema que eu tenho agora, e qual arquitetura resolve esse problema com o menor custo".