Arquitetura hexagonal (ports and adapters): o que é e quando usar
O que muda no seu código com arquitetura hexagonal, como são portas e adaptadores na prática e quando o padrão vale a complexidade.
Você abre o OrderService para trocar o banco de Postgres por outro fornecedor e descobre que a regra de negócio chama o ORM direto, monta SQL no meio do cálculo de desconto e ainda dispara um e-mail antes de salvar. Trocar de banco virou um projeto de três semanas com risco de regressão em tudo. Esse acoplamento entre "o que o sistema faz" e "como ele fala com o mundo" é exatamente a dor que a arquitetura hexagonal — ou ports and adapters — se propõe a resolver.
Este guia é prático: o que de fato muda no seu código, como são as portas e adaptadores em projetos reais, e — mais importante — quando essa estrutura paga o custo que cobra.
A ideia central em uma frase
Alistair Cockburn cunhou o termo para combater um problema concreto: a lógica de negócio vazando para a interface, e a interface (tela, banco, fila) vazando para a lógica. A arquitetura hexagonal coloca o domínio no centro e diz que ele só conversa com o mundo externo através de portas — interfaces que o domínio define. Tudo que é tecnologia concreta (HTTP, SQL, Kafka, SMTP) fica do lado de fora, em adaptadores que implementam essas portas.
O "hexágono" não tem nada de mágico com o número seis. O desenho é hexagonal só para sugerir que há vários lados de entrada e saída, não o tradicional "topo = UI, base = banco". A regra de ouro é uma só: as dependências apontam para dentro. O domínio nunca importa um adaptador; o adaptador é que conhece o domínio.
Portas primárias e secundárias
A confusão mais comum é achar que toda porta é "uma interface de repositório". Existem dois tipos, e a direção importa:
- Portas primárias (driving / de entrada): o que o mundo externo pode pedir ao domínio. Geralmente são os casos de uso. Um controller HTTP, um handler de fila ou um comando de CLI chamam essa porta.
- Portas secundárias (driven / de saída): o que o domínio precisa do mundo externo. Persistência, envio de e-mail, gateway de pagamento. O domínio declara a interface; o adaptador a implementa.
Em PHP, a porta secundária de persistência é só uma interface declarada no domínio:
// Domínio — não conhece Postgres, não conhece Doctrine
namespace App\Domain\Order;
interface OrderRepository
{
public function save(Order $order): void;
public function ofId(OrderId $id): ?Order;
}
E o adaptador vive longe, na borda, conhecendo a tecnologia:
// Adaptador secundário — conhece o domínio, o domínio NÃO o conhece
namespace App\Infrastructure\Persistence;
use App\Domain\Order\OrderRepository;
final class DoctrineOrderRepository implements OrderRepository
{
public function __construct(private EntityManagerInterface $em) {}
public function save(Order $order): void
{
$this->em->persist(OrderMapper::toEntity($order));
$this->em->flush();
}
// ...
}
O caso de uso (porta primária) orquestra sem saber qual banco roda por baixo:
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders, // porta secundária
private PaymentGateway $payments, // porta secundária
) {}
public function __invoke(PlaceOrderCommand $cmd): OrderId
{
$order = Order::place($cmd->items, $cmd->customerId);
$this->payments->charge($order->total(), $cmd->card);
$this->orders->save($order);
return $order->id();
}
}
Trocar Doctrine por um repositório em memória nos testes, ou por outro ORM em produção, não toca uma linha de PlaceOrder. Esse é o ganho concreto.
O que isso resolve na prática
O benefício mais tangível não é "trocar de banco" — isso quase nunca acontece. É testabilidade. Com as portas secundárias mockadas por implementações fake, você testa toda a regra de negócio sem subir Postgres, sem Kafka, sem rede. Testes que rodavam em 40 segundos passam a rodar em 400 milissegundos, e param de quebrar por flakiness de infraestrutura.
O segundo ganho é adiar decisões. Você modela o domínio antes de cravar se o cache será Redis ou se a fila será SQS. A porta congela o contrato; o adaptador chega depois. Em projetos onde a infra ainda está em discussão, isso é libertador.
O terceiro é clareza de fronteira: quando alguém abre o pacote Domain, não há um único import de framework. Isso força o time a manter a regra de negócio expressa em termos de negócio.
O custo que ninguém menciona no slide
Hexagonal não é de graça. Cada porta secundária vira pelo menos uma interface, uma implementação real, um fake para teste e, com frequência, um mapper entre o modelo de domínio e a entidade de persistência. Para um CRUD de cadastro de produtos, isso é cerimônia pura: você criou quatro arquivos para fazer um INSERT.
A arquitetura hexagonal compartilha esse trade-off com sua prima mais conhecida — e se você quer entender por que a inversão de dependência ajuda e onde ela vira teatro, vale ler Clean Architecture: benefícios e armadilhas, que ataca o mesmo problema pelo ângulo das camadas concêntricas. A diferença de ênfase é útil: a hexagonal fala em direção (entrada vs. saída) e em substituir adaptadores; a Clean fala em camadas e na Regra de Dependência. Na prática você acaba misturando vocabulário das duas — e tudo bem.
O risco real é o over-engineering. Criar porta para tudo, inclusive para o que jamais será trocado, é o caminho mais curto para um codebase onde abrir um arquivo significa pular por seis indireções até achar a linha que realmente faz algo.
Quando vale e quando não vale
Vale quando você tem lógica de domínio rica (regras, invariantes, máquinas de estado) que precisa sobreviver a mudanças de tecnologia, e quando testabilidade é prioridade — sistemas financeiros, fiscais, de pedidos, qualquer coisa onde um bug custa caro.
Não vale para um CRUD fino, um backend-for-frontend que só repassa chamadas, um script ou um MVP que você ainda nem sabe se vai existir em três meses. Nesses casos, o domínio é tão anêmico que a porta não protege nada — protege o vazio.
Uma heurística honesta: aplique portas e adaptadores só na fronteira que dói. Se o banco nunca muda mas a integração de pagamento já trocou de fornecedor duas vezes, coloque uma porta no pagamento e deixe a persistência usar o ORM direto. Arquitetura é sobre onde gastar complexidade, não sobre aplicar o padrão inteiro em todo lugar.
Perguntas frequentes
O que é arquitetura hexagonal em termos simples?
É um jeito de organizar o código para que a regra de negócio (o "centro") não dependa de detalhes externos como banco, framework web ou fila. O centro define interfaces (portas) e o mundo externo as implementa (adaptadores), o que permite trocar tecnologia sem reescrever a lógica.
Quando devo usar ports and adapters?
Quando há lógica de domínio relevante que precisa ser testada de forma rápida e isolada, ou quando as bordas tecnológicas mudam com frequência. Para CRUDs simples e MVPs descartáveis, o custo da indireção supera o benefício.
Qual a diferença entre arquitetura hexagonal e Clean Architecture?
São primas que defendem a mesma ideia (dependências apontando para o domínio), com vocabulários diferentes. A hexagonal enfatiza portas de entrada e saída e a troca de adaptadores; a Clean organiza tudo em camadas concêntricas com a Regra de Dependência. Na prática, equipes combinam os dois conceitos.
Quantos "lados" o hexágono tem?
O número seis é irrelevante — é só um desenho. O hexágono apenas ilustra que existem vários pontos de entrada e saída, não apenas "UI em cima, banco embaixo".
O que levar deste guia
Arquitetura hexagonal não é uma promessa de "trocar de banco fácil"; é uma disciplina para manter a regra de negócio limpa de detalhes técnicos e barata de testar. O valor está nas portas que você de fato vai exercitar — testes, integrações instáveis, decisões adiadas. Aplique nas fronteiras que doem, resista à tentação de criar interface para tudo, e meça o custo da indireção contra o ganho real. Padrão bom é o que você consegue defender olho no olho para o dev que vai mantê-lo daqui a um ano.
- 01 Convertendo CSV gigantes para JSON sem travar a aba Streams, Web Workers e um truque de chunking que mantém o conversor responsivo mesmo com 200MB de dados.
- 02 chmod, chown e umask no Linux: permissões explicadas com exemplos de servidor Entenda chmod (octal e simbólico), chown e umask com profundidade suficiente para resolver qualquer Permission denied em servidor — sem adivinhação.