Arquitetura hexagonal e clean architecture: fronteiras que protegem o domínio
Todo sistema de software envelhece, e a forma como ele envelhece revela a qualidade da sua arquitetura interna. Alguns sistemas continuam fáceis de mudar depois de anos, enquanto outros viram um bloco rígido onde tocar em uma parte quebra três outras. A diferença raramente está na topologia (se é monólito ou microsserviços) e quase sempre está em algo mais fundamental: como o código está organizado por dentro, e o quanto a lógica de negócio está protegida dos detalhes que mudam o tempo todo.
Este guia trata de arquitetura hexagonal e clean architecture, dois nomes para a mesma ideia central: desenhar fronteiras que protegem o domínio do sistema. O foco é a organização interna do código, um tema distinto da decisão de dividir ou não o sistema em vários serviços.
O problema: o domínio refém dos detalhes
Na forma mais comum de escrever software, a lógica de negócio se mistura com tudo: chamadas ao banco de dados, detalhes do framework web, formatos de API, envio de e-mail. Uma regra de negócio importante fica enterrada dentro de um controller que conhece HTTP, ou dentro de um método que sabe montar SQL. O resultado é que você não consegue mudar o banco, o framework ou a interface sem mexer na lógica de negócio, e não consegue testar a lógica sem subir a aplicação inteira.
O problema de fundo é a direção das dependências. Quando a lógica de negócio depende do banco e do framework, ela fica refém deles. Esses detalhes externos, que deveriam ser intercambiáveis, passam a ditar como o núcleo do sistema é escrito.
A ideia central: o domínio no centro
A arquitetura hexagonal (também chamada ports and adapters) e a clean architecture propõem a mesma inversão: coloque o domínio no centro, isolado, sem conhecer nada do mundo externo, e faça tudo o mais depender dele, nunca o contrário.
Pense em camadas concêntricas:
- No centro, o domínio: as entidades e as regras de negócio puras. Este núcleo não sabe que existe banco de dados, HTTP, JSON ou framework. Ele é escrito na linguagem do negócio, e só.
- Ao redor, os casos de uso: a lógica que orquestra o domínio para realizar as operações da aplicação (criar um pedido, cancelar uma assinatura). Também independente de tecnologia.
- Na borda, os adapters: o código que conecta o núcleo ao mundo real. O banco de dados, a API web, a fila, o provedor de e-mail. Tudo que é detalhe técnico vive aqui, na periferia.
A regra que sustenta tudo é a regra da dependência: as dependências apontam sempre para dentro. A borda conhece o centro, mas o centro nunca conhece a borda. O domínio não tem a menor ideia de qual banco ou framework o sistema usa.
Ports and adapters, na prática
Como o domínio no centro precisa, às vezes, falar com o mundo externo (salvar um pedido, por exemplo) sem depender dele, entra o conceito de port e adapter.
- Um port é uma interface definida pelo domínio, que descreve o que ele precisa em termos abstratos. Por exemplo, uma interface
RepositorioDePedidoscom um métodosalvar(pedido). O domínio depende dessa interface, não de uma implementação concreta. - Um adapter é a implementação concreta do port, que vive na borda. Um
RepositorioDePedidosPostgresimplementa a interface usando SQL. UmRepositorioDePedidosEmMemoriaa implementa com uma estrutura em memória, útil para testes.
Essa é a inversão de dependência em ação: o domínio define o contrato, e a infraestrutura o cumpre. Trocar o Postgres por outro banco significa escrever um novo adapter, sem tocar em uma linha da lógica de negócio. O framework vira um detalhe plugável na periferia, e não o alicerce sobre o qual tudo foi construído.
Onde os frameworks ficam
Aqui está uma das consequências mais contraintuitivas dessa arquitetura: o framework não é o centro do seu sistema; ele é um detalhe da borda. O framework web que recebe requisições, o ORM que fala com o banco, a biblioteca de fila, tudo isso são adapters. Eles servem o domínio, não o contrário.
Isso protege o sistema da obsolescência. Frameworks vão e vêm; regras de negócio duram décadas. Quando a lógica de negócio não conhece o framework, migrar de um framework antigo para um novo é um trabalho de borda, não uma reescrita do sistema inteiro. O núcleo permanece intacto, porque nunca soube qual framework estava do lado de fora.
O grande ganho: testabilidade
O benefício mais imediato e concreto dessa organização é a testabilidade. Como o domínio não depende de banco, rede ou framework, você testa as regras de negócio de forma pura, rápida e isolada, sem subir um banco, sem mocar meio mundo, sem esperar a aplicação inicializar.
| Aspecto | Código acoplado | Arquitetura com fronteiras |
|---|---|---|
| Testar a regra de negócio | Precisa de banco e framework | Puro e isolado, em milissegundos |
| Trocar de banco | Reescreve a lógica junto | Novo adapter, domínio intacto |
| Trocar de framework | Quase uma reescrita | Trabalho de borda |
| Entender a regra | Enterrada entre detalhes técnicos | Explícita no centro |
| Velocidade dos testes | Lenta (integração) | Rápida (unidade pura) |
Um domínio testável em isolamento é o que permite ao time refatorar com confiança e evoluir as regras de negócio sem medo, porque um teste rápido confirma a corretude a cada mudança.
O risco: overengineering
Agora, o outro lado, tão importante quanto os benefícios. Essas arquiteturas cobram um preço em cerimônia: interfaces, camadas, adapters e uma disciplina que, aplicada a um problema simples, vira burocracia pura. Um CRUD básico, sem regras de negócio relevantes, não ganha nada em ser envolvido em três camadas de abstração. Você só adiciona arquivos, indireção e atrito, para proteger um domínio que quase não existe.
Alguns sinais de que a arquitetura está sendo aplicada onde não precisa:
- O domínio é anêmico. Se as "regras de negócio" são só passar dados do request para o banco, não há núcleo a proteger.
- Cada mudança percorre cinco arquivos para adicionar um único campo, sem que isso traga clareza ou segurança real.
- As abstrações não têm segundo uso. Uma interface com uma única implementação, que nunca terá outra, é indireção sem benefício.
A maturidade está em aplicar essas fronteiras onde há domínio rico para proteger, e manter a simplicidade onde o problema é simples. Um sistema pode, e geralmente deve, ter partes com arquitetura elaborada (o núcleo de negócio complexo) e partes deliberadamente diretas (os CRUDs de apoio).
Fronteiras servem ao domínio, não ao ego
Arquitetura hexagonal e clean architecture não são sobre seguir um diagrama bonito ou impressionar em uma revisão de código. São sobre uma pergunta prática: as regras de negócio deste sistema estão protegidas dos detalhes que mudam? Onde a resposta importa, porque o domínio é valioso e vive muito, essas fronteiras pagam por si em testabilidade, longevidade e liberdade para trocar tecnologia. Onde o domínio é raso, elas viram peso morto. Desenhar fronteiras é um ato de julgamento, não de dogma: proteja o que merece proteção, e mantenha simples o que é simples.
Seu sistema ficou rígido, difícil de testar e preso a um framework? Na ConstructorTI, aplicamos fronteiras de arquitetura no ponto certo, com o domínio isolado, adapters plugáveis e testes rápidos, sem cair no overengineering onde o problema é simples. Se mudar uma regra de negócio hoje significa mexer no banco e no framework juntos, fale com a nossa engenharia e receba um diagnóstico da organização interna do seu código.