Seu código é uma fortaleza: por que a Arquitetura Hexagonal salvará seu projeto JavaScript
\n\nVocê já se deparou com uma situação em que trocar de banco de dados ou substituir uma API externa se transformou em um inferno de reescrita de metade do projeto? Ou, talvez, você escrevia testes unitários que mais pareciam testes de integração, porque a lógica de negócios estava fortemente acoplada ao Express ou MongoDB? Se sim — bem-vindo ao mundo da Arquitetura Hexagonal (Ports and Adapters).
\n\nEste não é apenas um padrão da moda dos livros de Alistair Cockburn. É uma forma prática de tornar seu código JavaScript robusto, testável e independente de frameworks. Neste artigo, não vamos dar palestras chatas. Vamos pegar um problema real (um sistema de gerenciamento de pedidos) e construí-lo passo a passo com os princípios da arquitetura hexagonal em JavaScript. Você verá como a lógica de negócios permanece imaculadamente pura, enquanto todo o "ruído" (bancos de dados, requisições HTTP, filas) fica do lado de fora.
\n\nPronto? Vamos lá!
\n\nO que é Arquitetura Hexagonal (e o que portos e adaptadores têm a ver com isso)?
\n\nImagine um gateway de uma nave espacial. Dentro — o painel de controle (sua lógica de negócios). Fora — uma infinidade de planetas diferentes (bancos de dados, frameworks web, serviços externos). O gateway é o porto (interface). E o traje espacial que permite respirar em um planeta específico é o adaptador.
\n\nA ideia principal: o núcleo da aplicação (core) não deve saber quem ou como está batendo à sua porta. Ele declara apenas "portos" (interfaces). E as implementações concretas (adaptadores) são conectadas externamente. Isso oferece três superpoderes:
\n\n- \n
- Isolamento da lógica de negócios — você troca o banco de dados sem tocar no código de cálculo de descontos. \n
- Testabilidade — você testa a lógica sem precisar subir Docker ou um banco de dados real. \n
- Independência de framework — você pode migrar do Express para o Fastify apenas escrevendo um novo adaptador. \n
Vamos para o código.
\n\nConfiguração do projeto e estrutura de pastas
\n\nPrimeiro, vamos criar uma estrutura simples. Escreveremos em TypeScript puro (ou JavaScript com JSDoc — como preferir). Nosso objetivo é um sistema de gerenciamento de pedidos.
\n\nsrc/\n├── core/\n│ ├── domain/ # Entidades e Value Objects\n│ ├── ports/ # Interfaces (portos)\n│ └── services/ # Lógica de negócios (casos de uso)\n├── adapters/\n│ ├── primary/ # Adaptadores que chamam nosso núcleo (HTTP, CLI)\n│ └── secondary/ # Adaptadores que o núcleo chama (BD, APIs externas)\n└── app.js # Ponto de entrada (montagem de tudo)\n\n\nPasso 1. Núcleo: Entidades de domínio e portos
\n\nComeçaremos pelo mais importante — aquilo que não depende de nada. Vamos criar a entidade Order (pedido).
core/domain/Order.js
\n\nexport class Order {\n constructor({ id, userId, items, status = 'pending' }) {\n this.id = id;\n this.userId = userId;\n this.items = items; // Array { productId, quantity, price }\n this.status = status;\n this.total = this.calculateTotal();\n }\n\n calculateTotal() {\n return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }\n\n approve() {\n if (this.status !== 'pending') {\n throw new Error('Only pending orders can be approved');\n }\n this.status = 'approved';\n }\n}\n\n\nAgora vamos definir os portos — as interfaces que nosso núcleo espera do mundo externo.
\n\ncore/ports/OrderRepository.js
\n\n// Este é um porto (interface). Não o implementamos aqui.\nexport class OrderRepository {\n async save(order) { throw new Error('Not implemented'); }\n async findById(id) { throw new Error('Not implemented'); }\n}\n\n\ncore/ports/EmailService.js
\n\nexport class EmailService {\n async sendOrderConfirmation(order) { throw new Err