Arquitetura Hexagonal em Kotlin: quando o código respira livremente
\n\nImagine que você está construindo uma casa. Você pode despejar a fundação, erguer as paredes e colocar as janelas imediatamente. Ou pode primeiro projetar a estrutura, instalar as utilidades e só então cuidar do acabamento. No mundo da programação, a segunda abordagem é chamada de arquitetura hexagonal (também conhecida como Ports & Adapters). E Kotlin, com sua concisão e sistema de tipos poderoso, é a linguagem ideal para dar vida a esse conceito.
\n\nEste artigo não é um tutorial chato. É um guia prático que mostrará como a Arquitetura Hexagonal em Kotlin transforma seu código em um sistema flexível, testável e independente de serviços externos. Vamos analisar um exemplo do zero, escrever código real e ver como isso funciona na prática.
\n\nO que é arquitetura hexagonal e por que você precisa dela?
\n\nAlistair Cockburn, que cunhou o termo, comparou a aplicação a um hexágono. No centro está sua lógica de negócios. Nas bordas estão as portas (interfaces) através das quais ela se comunica com o mundo externo. Os adaptadores são implementações dessas interfaces para tecnologias específicas: banco de dados, framework web, fila de mensagens.
\n\nIdeia chave: a lógica de negócios não sabe como os dados são armazenados ou qual framework está sendo usado. Ela simplesmente chama métodos das portas. Quer substituir PostgreSQL por MongoDB? Basta escrever um novo adaptador. A lógica de negócios nem perceberá a troca.
\n\nEm Kotlin, isso é especialmente conveniente graças a interfaces com implementações padrão, classes sealed e estilo funcional. Vamos ao que interessa.
\n\nProjetando o sistema: exemplo de loja online
\n\nSuponha que estamos escrevendo um microsserviço para gerenciamento de pedidos. No centro está OrderService, que cria pedidos e verifica a disponibilidade de produtos. O mundo externo é um controlador HTTP e um banco de dados.
Veja como fica a estrutura do projeto:
\n\nsrc/\n├── domain/ # Núcleo (lógica de negócios)\n│ ├── model/\n│ ├── port/\n│ └── service/\n├── adapter/\n│ ├── input/ # Adaptadores de entrada (controladores)\n│ └── output/ # Adaptadores de saída (repositórios)\n└── Application.kt # Ponto de entrada\n\n\nPasso 1. Núcleo: modelos e portas
\n\nVamos começar com o modelo de pedido. Em Kotlin, será um data class:
// domain/model/Order.kt\ndata class Order(\n val id: String,\n val customerId: String,\n val items: List<OrderItem>,\n val status: OrderStatus\n)\n\nenum class OrderStatus {\n PENDING, CONFIRMED, SHIPPED, CANCELLED\n}\n\ndata class OrderItem(\n val productId: String,\n val quantity: Int\n)\n\n\nAgora definimos as portas — interfaces através das quais o núcleo se comunica com o mundo externo. Teremos duas portas: uma para obter dados sobre produtos e outra para salvar pedidos.
\n\n// domain/port/ProductRepository.kt\ninterface ProductRepository {\n fun checkAvailability(productId: String, quantity: Int): Boolean\n}\n\n// domain/port/OrderRepository.kt\ninterface OrderRepository {\n fun save(order: Order): Order\n fun findById(id: String): Order?\n}\n\n\nObserve: em nenhum lugar há menção a banco de dados, HTTP ou frameworks. Lógica de negócios pura.
\n\nPasso 2. Serviço de negócios
\n\nAgora escrevemos OrderService, que usa essas portas. Em Kotlin, passamos dependências pelo construtor — isso é chamado de injeção de dependências (DI).
// domain/service/OrderService.kt\nclass OrderService(\n private val productRepository: ProductRepository,\n private val orderRepository: OrderRepository\n) {\n fun createOrder(customerId: String, items: List<OrderItem>): Order {\n // Verifica a disponibilidade de cada produto\n for (item in items) {\n if (!productRepository.checkA