ArquiteturaAvançado9 min de leitura7 seçõesc. 2005, Data aproximada

Arquitetura Hexagonal

Isola o domínio no centro e o conecta ao mundo externo por portas (interfaces) e adaptadores (implementações), tornando infraestrutura um detalhe plugável.

Em 10 segundos

Domínio no centro, interfaces (portas) como única fronteira e infraestrutura plugável nas bordas — dá para trocar banco, fila ou framework sem tocar numa regra de negócio.

Antes de ter nome

A ideia de inverter a dependência para a borda já estava no DIP (Martin, 1996); Cockburn deu a ela um desenho e um nome.

  • #ports-and-adapters
  • #clean-architecture
  • #isolamento
  • #ddd
Analogia

Como um console de videogame

O console (domínio) não sabe se você usa controle com fio, sem fio ou volante — ele só expõe a entrada padrão (porta). Qualquer acessório que implemente aquele plugue (adaptador) funciona. Trocar de controle nunca exige abrir o console.

01Conceito

O problema

Regras de negócio grudadas no framework web, no ORM ou em SDKs fazem o domínio depender de detalhes de infraestrutura. Trocar banco, broker ou camada HTTP obriga a reescrever lógica que não tem nada a ver com essas tecnologias.

O acoplamento também sabota os testes: para exercitar uma regra simples é preciso subir banco, servidor e filas — o teste de unidade vira integração lenta e frágil.

O sintoma clássico é o 'domínio anêmico invertido': as entidades são structs vazias e as regras moram em controllers e services amarrados ao framework. Qualquer upgrade de versão do framework vira um projeto de risco, porque as regras passam por cima dele.

A raiz do problema é a direção das dependências. Em um sistema em camadas tradicional, o negócio depende da infraestrutura (chama o ORM, conhece o SQL). A hexagonal inverte isso: a infraestrutura passa a depender do negócio, implementando interfaces que o próprio domínio define. É o Dependency Inversion Principle aplicado à arquitetura inteira.

Onion Architecture e Clean Architecture são variações do mesmo princípio — mudam os nomes dos anéis, mas a regra de ouro é idêntica: dependências apontam sempre para dentro, e o centro não conhece ninguém.

Como se encaixa
  1. HTTP / Fila / CLI
  2. acionaPorta de entrada
  3. implementada porDomínio
  4. declaraPorta de saída
  5. implementada porSQL / APIs / Broker
A dependência aponta sempre para dentro: adaptadores conhecem portas; o domínio não conhece ninguém.
02Anatomia

Navegue pelas camadas

Camada 1/5Adaptadores de entrada (driving)

Camada 1

Adaptadores de entrada (driving)

quem aciona o domínio

Controllers HTTP, handlers de CLI, consumers de fila. Traduzem o mundo externo (request, mensagem, argumento) para uma chamada de caso de uso. Não contêm regra de negócio — só tradução e validação de formato.

No código

app.post("/pedidos", (req) => criarPedido.executar(req.body));

Se violar: regra de negócio no controller → duplicação entre canais (REST faz uma coisa, fila outra) e testes que só rodam subindo HTTP.

03Estrutura

Estrutura

Diagrama de classes
04Implementação

Código

// Porta de saída (driven): o domínio declara o que precisa
interface RepositorioPedido {
  salvar(pedido: Pedido): Promise<void>;
}

// Porta de entrada (driving): caso de uso
interface CriarPedido {
  executar(cmd: { itens: string[] }): Promise<string>;
}

// Domínio: depende só das interfaces
class ServicoPedido implements CriarPedido {
  constructor(private repo: RepositorioPedido) {}
  async executar(cmd: { itens: string[] }) {
    const pedido = Pedido.novo(cmd.itens);
    await this.repo.salvar(pedido);
    return pedido.id;
  }
}

// Adaptador driven: implementa a porta com infra concreta
class RepositorioSQL implements RepositorioPedido {
  async salvar(pedido: Pedido) { /* INSERT ... */ }
}
05No mundo real

Casos de uso

Migrar de banco sem tocar no negócio

Cenário

Uma fintech precisa trocar Postgres por DynamoDB no serviço de pedidos por causa de escala.

Como o padrão entra

Só nasce um novo adaptador driven implementando RepositorioPedido. O domínio, os casos de uso e os controllers não mudam uma linha — o deploy pode até rodar com os dois adaptadores em paralelo para comparação.

Trade-off: Exige disciplina no design das portas: se a interface vazou detalhes de SQL, a troca deixa de ser plugável.

Mesma lógica, três canais

Cenário

Um pedido pode ser criado por REST (app), por fila (integração B2B) e por CLI (backoffice).

Como o padrão entra

Três adaptadores driving finos chamam a mesma porta de entrada CriarPedido. Validação de negócio existe uma vez só, no domínio — os canais apenas traduzem formato.

Trade-off: Cada canal ganha uma camada de tradução própria — mais arquivos, menos duplicação.

CI rápido com testes de unidade

Cenário

O pipeline levava 25 min porque todo teste subia banco e broker via containers.

Como o padrão entra

Com as regras atrás de portas, os testes de domínio usam dublês em memória e rodam em milissegundos; integração real fica para uma suíte menor de testes de contrato dos adaptadores.

Trade-off: Os testes de contrato continuam necessários — dublê que diverge do adaptador real cria falsa confiança.

06Cuidado

Armadilhas

  1. Hexagonal de fachada

    Criar pastas ports/ e adapters/ mas deixar as regras nos controllers. A arquitetura vira cerimônia: você paga a indireção sem ganhar o isolamento.

  2. Porta espelhando o banco

    Desenhar portas como CRUD genérico (findById, updateAll) em vez de necessidades do domínio ('reservar estoque'). O vocabulário da infra invade o núcleo e a troca de tecnologia volta a doer.

  3. Uma porta para cada coisa

    Abstrair até lib de log e util de data. Portas nascem de necessidades do domínio que variam de implementação — abstração especulativa é só indireção sem valor.

07Decisão

Quando usar × quando evitar

Use quando

3
  • O domínio é rico e valioso, e você quer protegê-lo de mudanças de framework/infra.
  • Precisa testar regras de negócio sem subir banco, HTTP ou filas.
  • Há múltiplos pontos de entrada (REST, CLI, fila) ou de saída (SQL, NoSQL, APIs) para a mesma lógica.

Evite quando

2
  • CRUDs simples sem regras relevantes — as camadas viram cerimônia sem retorno.
  • Protótipos e MVPs de vida curta, onde a indireção atrasa a entrega.

Testar o que ficou

3 armadilhas de Arquitetura Hexagonal viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • banco real × in-memory nos testesTrocar o repositório Postgres por um em memória no teste, sem tocar no domínio, é a prova de que a porta está no lugar certo.
  • controllers do NestJSO controller é um adaptador de entrada: traduz HTTP em uma chamada de caso de uso e nada do protocolo vaza para dentro do domínio.
  • repositório com Prisma trocávelA interface de repositório é a porta; a implementação com Prisma é um adaptador que dá para substituir por outro ORM sem mexer na regra.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Domínio no centro; adapters nas bordas.
dominio.executar(cmd, { repo: adapterPostgres });

O que ele cobra

2 saltos a mais entre a chamada e o efeito.

  • Uma porta e um adaptador por integração, mais os mapeamentos entre o mundo externo e o domínio
  • A indireção extra em cada borda é peso morto num sistema pequeno

Não vale se: o sistema é pequeno, tem uma integração só e não vai trocar de tecnologia — a cerimônia de portas custa mais do que protege.

Costuma ser confundido com

Onde isto aparece

Continue por aqui