Arquitetura Hexagonal no Node.js, também chamada de Ports and Adapters, organiza a aplicação ao redor das regras de negócio. Entradas como HTTP, fila e CLI usam portas para chamar o núcleo. Saídas como banco, cache e serviços externos implementam portas exigidas pelo núcleo.
O objetivo é impedir que framework e infraestrutura definam a estrutura do sistema. A aplicação pode ser testada sem servidor real e pode trocar adapters sem reescrever casos de uso.
Neste guia, você aprenderá portas de entrada e saída, adapters, composição, testes, eventos e boas práticas.
O hexágono
O desenho em hexágono não indica seis camadas. Ele representa várias formas de interação com o mesmo núcleo:
- HTTP;
- GraphQL;
- CLI;
- fila;
- cron;
- testes;
- banco;
- serviço externo.
Portas de entrada
Uma porta de entrada representa uma capacidade da aplicação:
export function criarRegistrarPagamento({ payments, gateway, clock }) {
return async function registrar(input) {
const result = await gateway.cobrar(input.amount);
const payment = {
id: result.id,
amount: input.amount,
status: result.status,
createdAt: clock.now()
};
await payments.salvar(payment);
return payment;
};
}HTTP e fila podem chamar a mesma função.
Adapter HTTP
export function criarPaymentController(registerPayment) {
return async function controller(req, res) {
const payment = await registerPayment({
amount: req.body.amount
});
res.status(201).json(payment);
};
}O adapter traduz protocolo para input do caso de uso.
Adapter de fila
export function criarPaymentConsumer(registerPayment) {
return async function consume(message) {
await registerPayment({ amount: message.amount });
};
}A regra não é duplicada.
Portas de saída
O caso de uso exige capacidades externas:
- salvar pagamento;
- cobrar gateway;
- obter horário;
- publicar evento;
- registrar auditoria.
Essas portas são implementadas por adapters.
Adapter PostgreSQL
export function criarPaymentRepository(pool) {
return {
async salvar(payment) {
await pool.query(
`INSERT INTO payments (id, amount, status, created_at)
VALUES ($1, $2, $3, $4)`,
[payment.id, payment.amount, payment.status, payment.createdAt]
);
}
};
}Adapter de gateway
export function criarGatewayAdapter(client) {
return {
async cobrar(amount) {
const response = await client.post('/charges', { amount });
return { id: response.id, status: response.status };
}
};
}Timeout, retry e circuit breaker ficam no adapter ou em decorators de infraestrutura.
Composition root
const pool = criarPool(config.databaseUrl);
const repository = criarPaymentRepository(pool);
const gateway = criarGatewayAdapter(httpClient);
const registerPayment = criarRegistrarPagamento({
payments: repository,
gateway,
clock: systemClock
});
const app = criarHttpApp({ registerPayment });Veja Dependency Injection no Node.js.
Estrutura de pastas
src/
core/
payment.js
register-payment.js
ports/
adapters/
http/
postgres/
payment-gateway/
messaging/
main.jsNão existe pasta obrigatória. O teste real é observar a direção dos imports.
Diferença para camadas tradicionais
Em uma arquitetura em camadas, o fluxo costuma ser controller → service → repository. Na hexagonal, o foco é distinguir núcleo, portas e adapters. Os conceitos podem coexistir.
Relação com Clean Architecture
As duas abordagens defendem dependências apontando para dentro. A Clean Architecture detalha camadas e políticas; a hexagonal enfatiza portas e adapters.
Consulte Clean Architecture no Node.js.
Repository como adapter
Um repository implementa uma porta de persistência. Veja Repository Pattern no Node.js.
Service Layer como porta de entrada
Um método de aplicação pode ser a porta usada por controllers e consumers. Consulte Service Layer no Node.js.
Testes do núcleo
const payments = criarPaymentRepositoryFake();
const gateway = {
async cobrar() {
return { id: 'p-1', status: 'approved' };
}
};
const register = criarRegistrarPagamento({ payments, gateway, clock });
const result = await register({ amount: 100 });
assert.equal(result.status, 'approved');Nenhum servidor ou banco é necessário.
Testes de adapters
Adapters precisam de testes próprios:
- controller: status, headers e serialização;
- repository: query, tipos e transação;
- gateway: request, timeout e erros;
- consumer: ack, retry e idempotência.
Adapters substituíveis
Durante desenvolvimento, uma porta pode usar memória. Em produção, PostgreSQL. A troca não deve exigir alteração do caso de uso.
Eventos
Publicação de eventos é uma porta de saída. O núcleo cria o fato; um adapter decide como persistir ou enviar.
Autorização
A regra de autorização pode fazer parte do caso de uso quando depende do negócio. Validação de token e extração de identidade pertencem ao adapter de entrada.
Erros
O núcleo lança erros próprios. Adapters traduzem para HTTP, nack, exit code ou mensagem de CLI.
Observabilidade
Logs e métricas podem ser portas ou decorators. Evite contaminar entidades com chamadas diretas ao SDK de observabilidade.
Quando usar
A arquitetura é útil quando:
- há múltiplas entradas;
- integrações mudam;
- regras de negócio são importantes;
- testes rápidos são necessários;
- equipes trabalham em adapters diferentes.
Quando simplificar
Um script pequeno ou CRUD básico pode usar módulos diretos. Criar dezenas de interfaces sem variação real aumenta custo.
Erros comuns
- chamar qualquer arquivo de porta;
- núcleo importando framework;
- adapter contendo regra de negócio;
- portas gigantes;
- duplicar entidades por camada;
- container global;
- testar apenas com mocks;
- confundir pasta com arquitetura.
Checklist
- núcleo independente;
- portas orientadas a capacidades;
- adapters pequenos;
- composition root explícito;
- erros traduzidos na borda;
- testes do núcleo rápidos;
- integração para adapters;
- sem abstrações desnecessárias.
Conclusão
Arquitetura Hexagonal no Node.js torna regras de negócio independentes de HTTP, banco e fornecedores. Portas definem o que o núcleo precisa; adapters implementam como a interação acontece.
Use o padrão para proteger áreas que realmente mudam e manter casos de uso testáveis. A arquitetura deve reduzir acoplamento, não multiplicar arquivos sem propósito.
Consulte o artigo de Alistair Cockburn sobre Arquitetura Hexagonal e a documentação de testes do Node.js.



