Arquitectura limpia en JavaScript: dominando Hexagonal en la práctica

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Tu código es una fortaleza: por qué la arquitectura Hexagonal salvará tu proyecto JavaScript

\n\n

¿Alguna vez te has enfrentado a una situación en la que cambiar de base de datos o reemplazar una API externa se convertía en un infierno de reescribir la mitad del proyecto? ¿O quizás escribías pruebas unitarias que se parecían más a pruebas de integración porque la lógica de negocio estaba fuertemente acoplada a Express o MongoDB? Si es así, bienvenido al mundo de la arquitectura Hexagonal (Puertos y Adaptadores).

\n\n

No es solo un patrón de moda de los libros de Alistair Cockburn. Es una forma práctica de hacer que tu código JavaScript sea robusto, comprobable e independiente de los frameworks. En este artículo no daremos aburridas conferencias. Tomaremos una tarea real (un sistema de gestión de pedidos) y la construiremos paso a paso basándonos en los principios de la arquitectura hexagonal en JavaScript. Verás cómo la lógica de negocio se mantiene inmaculadamente limpia, mientras que todo el «ruido» (bases de datos, solicitudes HTTP, colas) queda fuera.

\n\n

¿Listo? ¡Allá vamos!

\n\n

¿Qué es la arquitectura Hexagonal (y qué tienen que ver los puertos y adaptadores)?

\n\n

Imagina una esclusa (gateway) de una nave espacial. Dentro, está el panel de control (tu lógica de negocio). Fuera, hay un número infinito de planetas diferentes (bases de datos, frameworks web, servicios externos). La esclusa es el puerto (interfaz). Y el traje espacial que te permite respirar en un planeta concreto es el adaptador.

\n\n

La idea principal: el núcleo de la aplicación (core) no debe saber quién ni cómo se comunica con él. Solo declara «puertos» (interfaces). Y las implementaciones concretas (adaptadores) se conectan desde fuera. Esto proporciona tres superpoderes:

\n\n
    \n
  • Aislamiento de la lógica de negocio — cambias la base de datos sin tocar el código de cálculo de descuentos.
  • \n
  • Capacidad de prueba — pruebas la lógica sin levantar Docker ni una base de datos real.
  • \n
  • Independencia del framework — puedes migrar de Express a Fastify simplemente escribiendo un nuevo adaptador.
  • \n
\n\n

Pasemos al código.

\n\n

Configuración del proyecto y estructura de carpetas

\n\n

Para empezar, crearemos una estructura simple. Escribiremos en TypeScript puro (o JavaScript con JSDoc, según prefieras). Nuestro objetivo es un sistema de gestión de pedidos.

\n\n
src/\n├── core/\n│   ├── domain/          # Entidades y Value Objects\n│   ├── ports/           # Interfaces (puertos)\n│   └── services/        # Lógica de negocio (casos de uso)\n├── adapters/\n│   ├── primary/         # Adaptadores que llaman a nuestro núcleo (HTTP, CLI)\n│   └── secondary/       # Adaptadores que el núcleo llama (BD, APIs externas)\n└── app.js               # Punto de entrada (ensamblaje de todo)\n
\n\n

Paso 1. Núcleo: Entidades de dominio y puertos

\n\n

Empezamos por lo más importante: lo que no depende de nada. Crearemos la entidad Order (pedido).

\n\n

core/domain/Order.js

\n\n
export 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\n

Ahora definimos los puertos: las interfaces que nuestro núcleo espera del mundo exterior.

\n\n

core/ports/OrderRepository.js

\n\n
// Esto es un puerto (interfaz). No lo implementamos aquí.\nexport class OrderRepository {\n  async save(order) { throw new Error('Not implemented'); }\n  async findById(id) { throw new Error('Not implemented'); }\n}\n
\n\n

core/ports/EmailService.js

\n\n
export class EmailService {\n  async sendOrderConfirmation(order) { throw new Err                

Blogs

Book Recommendations