Arquitetura hexagonal: ports and adapters
1. Introdução à Arquitetura Hexagonal
A Arquitetura Hexagonal, também conhecida como Ports and Adapters, foi proposta por Alistair Cockburn em 2005 como resposta a um problema recorrente em sistemas de software: o alto acoplamento entre o núcleo do negócio e os detalhes de infraestrutura. Em sistemas tradicionais, bibliotecas de banco de dados, frameworks web e serviços externos frequentemente "vazam" para dentro do código de domínio, tornando a lógica de negócios frágil e difícil de testar.
O padrão propõe um isolamento radical: o núcleo da aplicação — contendo as regras de negócio e entidades — deve ser completamente independente de tecnologias externas. Esse núcleo se comunica com o mundo exterior através de portas (interfaces) e adaptadores (implementações concretas). O nome "hexagonal" vem da representação visual do padrão, onde o núcleo é um hexágono cercado por adaptadores que o conectam a diferentes sistemas.
2. Estrutura Fundamental: O Hexágono e Suas Camadas
A arquitetura hexagonal organiza o sistema em três zonas principais:
Núcleo (Core/Application): Contém as entidades de domínio, regras de negócio e casos de uso. Esta camada não importa nenhuma biblioteca de infraestrutura (banco, HTTP, etc.).
Portas (Ports): Interfaces Java (ou contratos em outras linguagens) que definem como o núcleo interage com o exterior. Dividem-se em portas de entrada (inbound) e portas de saída (outbound).
Adaptadores (Adapters): Implementações concretas das portas. Adaptadores de entrada recebem requisições externas (REST, CLI, filas) e as traduzem para chamadas às portas de entrada. Adaptadores de saída implementam portas de saída para interagir com bancos, APIs externas, sistemas de arquivos, etc.
┌─────────────┐
│ Adaptador │
│ REST API │
└──────┬──────┘
│
┌──────▼──────┐
│ Porta de │
│ Entrada │
└──────┬──────┘
│
┌─────────▼─────────┐
│ NÚCLEO (Core) │
│ Entidades + Casos │
│ de Uso │
└─────────┬─────────┘
│
┌──────▼──────┐
│ Porta de │
│ Saída │
└──────┬──────┘
│
┌──────▼──────┐
│ Adaptador │
│ JPA/BD │
└─────────────┘
3. Portas de Entrada (Inbound Ports)
As portas de entrada são interfaces expostas pelo núcleo que definem os casos de uso disponíveis. Elas representam as operações que o sistema pode realizar, independentemente de como são invocadas.
public interface CreateOrderUseCase {
Order createOrder(CreateOrderCommand command);
Order createOrder(CreateOrderCommand command, PaymentMethod payment);
}
public record CreateOrderCommand(
String customerId,
List<OrderItem> items,
Address shippingAddress
) {}
Observe que a interface não faz referência a HTTP, filas ou qualquer tecnologia específica. Ela simplesmente define o contrato do caso de uso. Um controlador REST, um listener de fila ou uma CLI podem todos invocar a mesma porta de entrada.
4. Portas de Saída (Outbound Ports)
As portas de saída são interfaces que o núcleo define para interagir com sistemas externos. O princípio da Inversão de Dependência (DIP) é aplicado: o núcleo declara o contrato, e a infraestrutura implementa.
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(String orderId);
List<Order> findByCustomerId(String customerId);
void delete(String orderId);
}
public interface PaymentGateway {
PaymentResult charge(PaymentRequest request);
RefundResult refund(String transactionId);
}
O núcleo chama orderRepository.save(order) sem saber se a implementação usa JPA, JDBC, MongoDB ou um arquivo em memória. Isso permite trocar a tecnologia de persistência sem alterar uma linha do código de negócio.
5. Adaptadores de Entrada (Inbound Adapters)
Os adaptadores de entrada são responsáveis por receber requisições do mundo externo e traduzi-las para chamadas às portas de entrada. Um adaptador REST típico:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final CreateOrderUseCase createOrderUseCase;
public OrderController(CreateOrderUseCase createOrderUseCase) {
this.createOrderUseCase = createOrderUseCase;
}
@PostMapping
public ResponseEntity<OrderResponse> createOrder(
@RequestBody @Valid CreateOrderRequest request) {
CreateOrderCommand command = request.toCommand();
Order order = createOrderUseCase.createOrder(command);
return ResponseEntity.status(201).body(OrderResponse.from(order));
}
}
O adaptador converte o DTO HTTP (CreateOrderRequest) no objeto de domínio (CreateOrderCommand), chama o caso de uso e converte o resultado de volta para o formato de resposta. O núcleo permanece puro, sem anotações Spring ou referências a HTTP.
6. Adaptadores de Saída (Outbound Adapters)
Os adaptadores de saída implementam as portas definidas pelo núcleo. Um adaptador JPA para OrderRepository:
@Component
public class JpaOrderRepository implements OrderRepository {
private final SpringDataOrderRepository springRepo;
private final OrderMapper mapper;
public JpaOrderRepository(SpringDataOrderRepository springRepo,
OrderMapper mapper) {
this.springRepo = springRepo;
this.mapper = mapper;
}
@Override
public Order save(Order order) {
OrderEntity entity = mapper.toEntity(order);
OrderEntity saved = springRepo.save(entity);
return mapper.toDomain(saved);
}
@Override
public Optional<Order> findById(String orderId) {
return springRepo.findById(orderId)
.map(mapper::toDomain);
}
@Override
public List<Order> findByCustomerId(String customerId) {
return springRepo.findByCustomerId(customerId)
.stream()
.map(mapper::toDomain)
.toList();
}
@Override
public void delete(String orderId) {
springRepo.deleteById(orderId);
}
}
O adaptador gerencia toda a complexidade de ORM, mapeamento entre entidades JPA e objetos de domínio, consultas SQL e transações. O núcleo nunca vê @Entity, @Table ou EntityManager.
7. Relação com Clean Architecture e Arquitetura em Camadas
A arquitetura hexagonal compartilha princípios fundamentais com a Clean Architecture de Robert C. Martin e com a Arquitetura em Camadas tradicional, mas com diferenças importantes:
Arquitetura em Camadas Tradicional:
- Camadas: Presentation → Business → Data
- Dependências fluem de cima para baixo
- Risco: vazamento de infraestrutura para a camada de negócio
Arquitetura Hexagonal:
- Núcleo isolado no centro
- Adaptadores na periferia
- Dependências sempre apontam para dentro (Regra de Dependência)
Clean Architecture:
- Usa círculos concêntricos em vez de hexágono
- Entidades e Casos de Uso no centro
- Adaptadores de interface e infraestrutura nos círculos externos
A correspondência é direta: as "Entities" e "Use Cases" da Clean Architecture equivalem ao núcleo hexagonal. Os "Interface Adapters" e "Frameworks & Drivers" equivalem aos adaptadores. Ambas garantem que o código de domínio nunca dependa de frameworks, bancos de dados ou interfaces de usuário.
8. Vantagens, Desvantagens e Quando Aplicar
Vantagens:
- Testabilidade: O núcleo pode ser testado com mocks das portas, sem infraestrutura real
- Independência de tecnologia: Trocar de banco de dados ou framework web não afeta o domínio
- Substituição de adaptadores: Múltiplos adaptadores para a mesma porta (REST + GraphQL + gRPC)
- Manutenibilidade: Regras de negócio concentradas e isoladas de detalhes técnicos
Desvantagens:
- Complexidade inicial: Mais interfaces, classes de mapeamento e configuração
- Overhead de interfaces: Para sistemas simples, a indireção pode ser excessiva
- Curva de aprendizado: Desenvolvedores precisam entender o padrão e a inversão de dependência
Cenários recomendados:
- Sistemas com múltiplas interfaces de entrada (REST, filas, CLI)
- Microsserviços que precisam trocar tecnologias ao longo do tempo
- Domínios complexos onde a pureza do código de negócio é crítica
- Projetos com vida útil longa e necessidade de evolução tecnológica
Cenários a evitar:
- CRUDs simples sem lógica de negócio significativa
- Protótipos rápidos onde velocidade supera manutenibilidade
- Equipes pequenas sem experiência em arquitetura orientada a domínio
A arquitetura hexagonal não é uma bala de prata, mas quando aplicada em contextos adequados, oferece um dos mais robustos modelos para manter o núcleo do negócio protegido das constantes mudanças tecnológicas que caracterizam o desenvolvimento de software moderno.
Referências
- Alistair Cockburn - Hexagonal Architecture (Artigo Original) — Artigo seminal de Alistair Cockburn que introduziu o conceito de Ports and Adapters em 2005
- Martin Fowler - Inversion of Control Containers and the Dependency Injection pattern — Explicação detalhada do princípio de Inversão de Dependência que fundamenta a arquitetura hexagonal
- Robert C. Martin - The Clean Architecture — Post de Uncle Bob estabelecendo a Clean Architecture, diretamente relacionada ao padrão hexagonal
- Herberto Graça - Hexagonal Architecture: A Practical Guide — Guia prático que conecta DDD, arquitetura hexagonal e outras abordagens
- Baeldung - Hexagonal Architecture with Spring Boot — Tutorial prático implementando arquitetura hexagonal em Java com Spring Boot e exemplos de código
- Java Design Patterns - Hexagonal Architecture — Documentação do padrão com diagramas e exemplos de implementação em Java