Todos os artigos
118 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Tutoriais

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.

Arquitetura hexagonal (ports and adapters): o que é e quando usar
COVER · Tutoriais

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.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil