Adapters
O papel do adapter
Seção intitulada “O papel do adapter”Se a porta é o contrato do núcleo, o adapter é o tradutor que faz uma tecnologia concreta conversar com esse contrato. Ele isola o núcleo de tudo que é volátil: protocolos, formatos de serialização, dialetos de banco, SDKs de terceiros. Quando a tecnologia muda, muda-se o adapter; o núcleo permanece intacto.
Um adapter tem uma única responsabilidade: converter e delegar. Converter o dado externo para o tipo do domínio (e vice-versa) e delegar a decisão de negócio para a porta. Ele nunca decide regras.
Adapters primários (driving / de entrada)
Seção intitulada “Adapters primários (driving / de entrada)”Ficam no lado esquerdo do hexágono e acionam o núcleo. Recebem um estímulo do mundo externo e o transformam em uma chamada a uma driving port.
- Controller REST: desserializa JSON, valida formato, chama o caso de uso, serializa a resposta.
- CLI: faz parsing de argumentos e invoca o caso de uso.
- Consumer de fila: lê a mensagem, converte e chama o caso de uso.
- Agendador (scheduler): dispara casos de uso periodicamente.
package com.exemplo.banco.infrastructure.rest;
import com.exemplo.banco.application.port.in.AbrirContaUseCase;import com.exemplo.banco.domain.Conta;import java.math.BigDecimal;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
@RestController@RequestMapping("/contas")public class ContaRestController {
private final AbrirContaUseCase abrirContaUseCase;
public ContaRestController(AbrirContaUseCase abrirContaUseCase) { this.abrirContaUseCase = abrirContaUseCase; }
public record AbrirContaRequest(String titular, BigDecimal depositoInicial) {}
public record ContaResponse(String numero, String titular, BigDecimal saldo) {}
@PostMapping public ResponseEntity<ContaResponse> abrir(@RequestBody AbrirContaRequest req) { Conta conta = abrirContaUseCase.abrir(req.titular(), req.depositoInicial()); ContaResponse body = new ContaResponse( conta.numero(), conta.titular(), conta.saldo().valor()); return ResponseEntity.ok(body); }}Adapters secundários (driven / de saída)
Seção intitulada “Adapters secundários (driven / de saída)”Ficam no lado direito e são acionados pelo núcleo. Implementam as driven ports, fornecendo a tecnologia concreta que o domínio pediu de forma abstrata.
- Adapter JPA: implementa
ContaRepositorymapeando entre a entidade de domínio e a entidade persistente. - Client HTTP: implementa um gateway chamando uma API externa.
- Produtor de fila: implementa uma porta de notificação publicando eventos.
package com.exemplo.banco.infrastructure.persistence;
import com.exemplo.banco.application.port.out.ContaRepository;import com.exemplo.banco.domain.Conta;import java.util.Optional;import org.springframework.stereotype.Component;
@Componentpublic class ContaJpaAdapter implements ContaRepository {
private final ContaJpaRepository jpaRepository;
public ContaJpaAdapter(ContaJpaRepository jpaRepository) { this.jpaRepository = jpaRepository; }
@Override public Conta salvar(Conta conta) { ContaJpaEntity entity = ContaJpaEntity.deDominio(conta); return jpaRepository.save(entity).paraDominio(); }
@Override public Optional<Conta> porNumero(String numero) { return jpaRepository.findByNumero(numero).map(ContaJpaEntity::paraDominio); }}Um adapter por tecnologia
Seção intitulada “Um adapter por tecnologia”O princípio é manter um adapter dedicado por tecnologia externa. Isso significa que oferecer a mesma funcionalidade por REST e por CLI resulta em dois adapters primários distintos, ambos chamando a mesma driving port. Da mesma forma, trocar Postgres por Mongo é escrever um novo adapter secundário que implementa a mesma driven port, sem tocar no núcleo. Essa granularidade é o que dá à arquitetura sua portabilidade e testabilidade.
Diagrama
Seção intitulada “Diagrama”graph LR
subgraph Primarios[Adapters Primarios]
R[Controller REST]
C[CLI]
Q1[Consumer Fila]
end
subgraph Nucleo[Nucleo]
DIN[Driving Ports]
DOUT[Driven Ports]
end
subgraph Secundarios[Adapters Secundarios]
J[Adapter JPA]
H[Client HTTP]
Q2[Produtor Fila]
end
R --> DIN
C --> DIN
Q1 --> DIN
J -.implementa.-> DOUT
H -.implementa.-> DOUT
Q2 -.implementa.-> DOUT
Setas cheias: chamada dos adapters primários às driving ports. Setas tracejadas: os adapters secundários implementam as driven ports. Nos dois lados, a dependência de código aponta para o núcleo.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Regra de negócio no adapter: validar saldo, calcular juros ou decidir fluxo dentro do controller. Isso pertence ao núcleo.
- Adapter que conhece outro adapter: o controller chamando o repositório JPA diretamente, pulando a porta e o application service.
- Entidade única atravessando tudo: usar a
@EntityJPA como objeto de domínio, DTO de request e resposta REST ao mesmo tempo — acopla persistência, transporte e regra. - Mapeamento espalhado: conversões domínio↔DTO/entidade fora do adapter, vazando formato externo para o núcleo.
- Adapter multitarefa: um mesmo componente falando REST e mexendo direto no banco, violando “um adapter por tecnologia”.
Relações
Seção intitulada “Relações”- Adapters implementam/consomem as Ports.
- Papéis primário/secundário detalhados em Driving e Driven.
- Adapters secundários costumam realizar os Repositórios.
- Sustentados pela inversão de dependência.
- Exemplo executável em Exemplo Hexagonal com Spring.
- Volte ao índice Arquitetura Hexagonal.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Qual a única responsabilidade legítima de um adapter?
Resposta
Converter e delegar: traduzir o dado externo para o tipo do domínio (e vice-versa) e delegar a decisão de negócio para a porta. Ele nunca implementa regras de negócio.
2. Por que “um adapter por tecnologia” e não um adapter genérico?
Resposta
Porque cada tecnologia tem detalhes voláteis próprios (protocolo, formato, SDK). Adapters dedicados permitem adicionar uma nova entrada (CLI além de REST) ou trocar uma saída (Mongo no lugar de Postgres) sem tocar o núcleo nem os outros adapters, preservando a portabilidade.
3. Um controller REST pode chamar o repositório JPA diretamente? Por quê?
Resposta
Não. Isso pula a driving port e o application service, jogando fora o isolamento do núcleo e permitindo que orquestração e regra vazem para o controller. O controller deve chamar apenas a driving port; a persistência é acessada pelo núcleo via driven port.
4. Qual a diferença de direção entre um adapter primário e um secundário?
Resposta
O adapter primário aciona o núcleo (chama uma driving port), estando no lado driving/esquerdo. O secundário é acionado pelo núcleo (implementa uma driven port), estando no lado driven/direito. Em ambos, a dependência de código aponta para dentro, mas o fluxo de controle vai em sentidos opostos.