Arquitetura Hexagonal em Java: lógica limpa sem dor

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que sua aplicação não é seu banco de dados

\n\n

Imagine que você está construindo uma casa. Fundação, paredes, telhado — tudo planejado. Mas depois de um ano, você decide trocar a fiação elétrica. E aí descobre que os fios estão embutidos no concreto e as tomadas soldadas na armadura. Para trocar um único interruptor, é preciso demolir metade da casa.

\n\n

Mais ou menos assim é uma aplicação Java clássica, onde a lógica de negócios está fortemente entrelaçada com o framework, banco de dados ou controlador REST. Quer migrar de PostgreSQL para MongoDB? Reescrever os controladores de Spring MVC para JAX-RS? Bem-vindo ao inferno da refatoração.

\n\n

A saída é a Arquitetura Hexagonal (também conhecida como "Portas e Adaptadores", ou "Clean Architecture" na interpretação de Alistair Cockburn). Não é uma bala de prata, mas é uma forma de tornar sua aplicação flexível, testável e independente do mundo externo.

\n\n

Hoje vamos analisar a Arquitetura Hexagonal em Java com exemplos reais de código, sem enrolação e sem discussões abstratas. Vamos nessa.

\n\n

O que é Arquitetura Hexagonal (Portas e Adaptadores)?

\n\n

A essência é simples: sua aplicação é um núcleo (core) que contém a lógica de negócios. Ao redor do núcleo, há um "hexágono" (na verdade, um número arbitrário de faces), através do qual o mundo externo se comunica com o núcleo.

\n\n

Cada face tem sua porta (interface). E o adaptador é a implementação da porta para uma tecnologia específica. Por exemplo:

\n
    \n
  • Porta "UserRepository" — adaptador "JpaUserRepository" ou "MongoUserRepository"
  • \n
  • Porta "SendNotification" — adaptador "EmailSender" ou "SmsSender"
  • \n
\n\n

A regra principal: o núcleo não sabe nada sobre bibliotecas externas, bancos de dados, frameworks web. Ele conhece apenas suas portas (interfaces). Isso traz várias vantagens:

\n
    \n
  • Independência da infraestrutura — você troca o BD ou framework sem mexer na lógica.
  • \n
  • Testabilidade — a lógica de negócios pode ser testada sem subir o contexto do Spring ou do banco.
  • \n
  • Legibilidade — o código do núcleo conta a história do domínio, não os detalhes técnicos.
  • \n
\n\n

Estrutura do projeto: como organizar o código

\n\n

Não existe uma única estrutura de pacotes correta, mas proponho uma opção testada com base em DDD (Domain-Driven Design) e nos princípios Hexagonal:

\n\n
com.example.app\n├── domain\n│   ├── model          // Entidades, Value Objects\n│   ├── port           // Interfaces das portas (inbound/outbound)\n│   └── service        // Serviços de negócio (casos de uso)\n├── adapter\n│   ├── inbound\n│   │   ├── rest       // Controladores REST (adaptadores de entrada)\n│   │   └── messaging  // Ouvintes Kafka/RabbitMQ\n│   └── outbound\n│       ├── persistence // Repositórios JPA, JDBC, MongoDB\n│       └── client      // Clientes HTTP, APIs externas\n└── config             // Configuração do Spring (se estiver usando)\n
\n\n

Observe: domain não importa nada de adapter. Isso é crítico. Se você vir @Entity ou @RestController no domain, significa que a arquitetura está quebrada.

\n\n

Exemplo: sistema de gerenciamento de pedidos

\n\n

Vamos projetar um sistema simples: o usuário pode criar um pedido, e o sistema verifica a disponibilidade do produto no estoque e envia uma notificação.

\n\n

Passo 1. Modelo de domínio (domain/model)

\n\n
// Order.java\npublic class Order {\n    private final OrderId id;\n    private final List<OrderItem> items;\n    private OrderStatus status;\n\n    public Order(List<OrderItem> items) {\n        this.id = new OrderId(UUID.randomUUID().toString());\n        this.items = Collections.unmodifiableList(items);\n        this.status = OrderStatus.PENDING;\n    }\n\n    public void confirm() {\n        if (status != OrderStatus.PENDING) {\n            throw new IllegalStateException("Only PENDING orders can be confirmed");\n        }\n        this.status = Ord                

Blogs

Book Recommendations