

Arquitectura Hexagonal: El Patrón Ports & Adapters Explicado a Fondo
Aprende cómo la Arquitectura Hexagonal desacopla tu lógica de negocio de la infraestructura, mejorando la testabilidad, mantenibilidad y evolución de tus sistemas.
La Arquitectura Hexagonal, también conocida como el patrón Ports and Adapters, fue introducida por Alistair Cockburn en 2005 para resolver un problema fundamental en el diseño de software: la filtración de lógica de negocio hacia las capas de infraestructura y presentación.
Este patrón revolucionó la forma de pensar la estructura de las aplicaciones, proponiendo que el núcleo de la aplicación debe ser completamente independiente de las tecnologías externas — ya sean bases de datos, interfaces de usuario, APIs o frameworks.
En este artículo exploraremos en detalle qué es la Arquitectura Hexagonal, sus componentes fundamentales, beneficios, casos de uso y cómo se compara con otras arquitecturas modernas.
¿Por Qué Nació la Arquitectura Hexagonal?
En las arquitecturas tradicionales por capas (Presentación → Negocio → Datos), un problema recurrente es la contaminación entre capas:
- La lógica de negocio se mezcla con detalles de la base de datos.
- Los controladores HTTP contienen reglas de validación que deberían estar en el dominio.
- Cambiar de base de datos o framework implica reescribir gran parte del sistema.
- Las pruebas unitarias requieren levantar toda la infraestructura.
"El problema no es que las capas existan, sino que las dependencias fluyen en la dirección equivocada." — Alistair Cockburn
La Arquitectura Hexagonal resuelve esto invirtiendo las dependencias: la infraestructura depende del dominio, nunca al revés.
El Modelo: El Hexágono
La forma hexagonal es puramente simbólica — Cockburn la eligió para romper con el modelo mental de capas apiladas y proporcionar suficiente espacio visual para representar múltiples puntos de conexión.
El modelo divide la aplicación en tres zonas claramente definidas:
1. Núcleo de la Aplicación (Interior del Hexágono)
Es el corazón del sistema. Contiene:
- Entidades del dominio: Objetos que representan los conceptos de negocio (Usuario, Pedido, Factura).
- Casos de uso / Servicios de aplicación: Orquestan la lógica de negocio y definen qué hace la aplicación.
- Reglas de negocio: Validaciones, cálculos y políticas que son independientes de cualquier tecnología.
Regla fundamental: El núcleo no conoce ni importa nada de la infraestructura. No sabe si los datos vienen de PostgreSQL, MongoDB, un archivo CSV o una API externa.
2. Puertos (Ports) — Las Interfaces
Los puertos son interfaces (contratos) que definen cómo el núcleo se comunica con el mundo exterior. Representan el propósito de una conversación, no su implementación.
Existen dos tipos de puertos:
Puertos Primarios (Driving / Entrada)
Definen lo que la aplicación ofrece al mundo exterior. Son las operaciones que los actores externos pueden invocar.
// Puerto primario: lo que la aplicación ofrece interface CrearPedidoUseCase { ejecutar(datos: DatosPedido): Promise<Pedido>; } interface ConsultarPedidoUseCase { ejecutar(id: string): Promise<Pedido | null>; }
Puertos Secundarios (Driven / Salida)
Definen lo que la aplicación necesita del mundo exterior. Son las dependencias que el núcleo requiere pero no implementa.
// Puerto secundario: lo que la aplicación necesita interface PedidoRepository { guardar(pedido: Pedido): Promise<void>; buscarPorId(id: string): Promise<Pedido | null>; listarTodos(): Promise<Pedido[]>; } interface NotificacionService { enviarConfirmacion(pedido: Pedido): Promise<void>; }
3. Adaptadores (Adapters) — Las Implementaciones
Los adaptadores son el "pegamento" entre la aplicación y las tecnologías externas. Traducen las especificidades de cada actor externo al protocolo definido por el puerto.
Adaptadores Primarios (Driving)
Disparan la aplicación desde el exterior:
- Controladores REST/GraphQL
- Interfaces de línea de comandos (CLI)
- Consumers de colas de mensajes
- Test harnesses (pruebas automatizadas)
// Adaptador primario: controlador REST class PedidoController { constructor(private crearPedido: CrearPedidoUseCase) {} async handlePost(req: Request): Promise<Response> { const datos = await req.json(); const pedido = await this.crearPedido.ejecutar(datos); return Response.json(pedido, { status: 201 }); } }
Adaptadores Secundarios (Driven)
Son invocados por la aplicación para realizar tareas:
- Repositorios de base de datos (PostgreSQL, MongoDB, Redis)
- Clientes HTTP para APIs externas
- Servicios de email, SMS, notificaciones push
- Sistemas de archivos y almacenamiento en la nube
// Adaptador secundario: implementación con PostgreSQL class PostgresPedidoRepository implements PedidoRepository { async guardar(pedido: Pedido): Promise<void> { await this.db.query( 'INSERT INTO pedidos (id, cliente, total) VALUES (, , )', [pedido.id, pedido.cliente, pedido.total] ); } async buscarPorId(id: string): Promise<Pedido | null> { const result = await this.db.query('SELECT * FROM pedidos WHERE id = ', [id]); return result.rows[0] ?? null; } } // Adaptador secundario alternativo: en memoria (para testing) class InMemoryPedidoRepository implements PedidoRepository { private pedidos: Map<string, Pedido> = new Map(); async guardar(pedido: Pedido): Promise<void> { this.pedidos.set(pedido.id, pedido); } async buscarPorId(id: string): Promise<Pedido | null> { return this.pedidos.get(id) ?? null; } }
El Flujo de Dependencias
La regla más importante de la Arquitectura Hexagonal es la dirección de las dependencias:
Todas las dependencias apuntan hacia adentro — de la infraestructura hacia el dominio, nunca al revés.
| Componente | Conoce | No conoce |
|---|---|---|
| Adaptador Primario | Puerto primario (interfaz) | Implementación interna del caso de uso |
| Caso de Uso | Entidades del dominio + Puertos secundarios (interfaces) | Adaptadores, frameworks, bases de datos |
| Adaptador Secundario | Puerto secundario (interfaz) | Otros adaptadores |
| Entidades del Dominio | Nada externo | Todo lo demás |
Este flujo se logra mediante el Principio de Inversión de Dependencias (DIP): el núcleo define las interfaces (puertos), y la infraestructura las implementa (adaptadores).
Beneficios de la Arquitectura Hexagonal
1. Testabilidad Superior
Al depender solo de interfaces, el núcleo se puede testear con adaptadores "mock" sin necesidad de una base de datos real, un servidor web ni servicios externos.
// Test unitario sin infraestructura const mockRepo = new InMemoryPedidoRepository(); const mockNotificacion = new MockNotificacionService(); const useCase = new CrearPedidoService(mockRepo, mockNotificacion); const pedido = await useCase.ejecutar({ cliente: 'Juan', productos: [{ id: '1', cantidad: 2 }] }); expect(pedido.total).toBe(200); expect(mockNotificacion.enviados).toHaveLength(1);
2. Independencia Tecnológica
Si el equipo decide migrar de MongoDB a PostgreSQL, solo necesita crear un nuevo adaptador secundario. La lógica de negocio permanece intacta.
3. Evolución sin Riesgo
Agregar un nuevo canal de entrada (por ejemplo, una API GraphQL además de REST) implica crear un nuevo adaptador primario que reutiliza los mismos casos de uso.
4. Desarrollo Paralelo
Los equipos pueden trabajar simultáneamente:
- Un equipo desarrolla la lógica de negocio con adaptadores mock.
- Otro equipo implementa los adaptadores reales.
- Las interfaces (puertos) son el contrato que los conecta.
5. Claridad Arquitectónica
La separación explícita entre qué hace la aplicación (puertos) y cómo lo hace (adaptadores) facilita la comprensión del sistema por nuevos desarrolladores.
¿Cuándo Usar (y Cuándo No) la Arquitectura Hexagonal?
✅ Usar cuando:
- El dominio es complejo con muchas reglas de negocio.
- Se anticipa cambio frecuente de tecnologías o integraciones.
- La testabilidad es una prioridad del equipo.
- El sistema necesita soportar múltiples canales de entrada (REST, GraphQL, CLI, eventos).
- Se practica Domain-Driven Design (DDD).
❌ Evitar cuando:
- La aplicación es un CRUD simple con poca lógica de negocio.
- Es un MVP o prototipo que prioriza velocidad sobre estructura.
- El equipo tiene menos de 5 endpoints y complejidad mínima.
- No hay expectativa de cambio tecnológico a mediano plazo.
"La Arquitectura Hexagonal brilla cuando la complejidad del dominio justifica la inversión en estructura."
Comparativa: Hexagonal vs. Clean vs. Onion
Estas tres arquitecturas son "dialectos" del mismo principio fundamental: aislar el dominio de la infraestructura. Sin embargo, tienen matices importantes:
| Aspecto | Hexagonal | Clean Architecture | Onion Architecture |
|---|---|---|---|
| Creador | Alistair Cockburn (2005) | Robert C. Martin (2012) | Jeffrey Palermo (2008) |
| Modelo visual | Hexágono con puertos | Círculos concéntricos | Capas tipo cebolla |
| Terminología | Ports & Adapters | Entities, Use Cases, Controllers, Gateways | Domain, Application, Infrastructure |
| Énfasis | Intercambiabilidad de tecnología | Regla de dependencia estricta | Inversión de control por capas |
| Complejidad | Media | Alta | Media-Alta |
| Caso ideal | Sistemas con múltiples integraciones | Aplicaciones empresariales complejas | Microservicios con dominio rico |
Las tres comparten el mismo ADN:
- Dependencias siempre hacia adentro.
- El dominio es la capa más estable.
- La infraestructura es un detalle reemplazable.
Estructura de Carpetas Típica
Una implementación práctica de Arquitectura Hexagonal en un proyecto podría verse así:
src/
├── domain/ # Núcleo del dominio
│ ├── entities/ # Entidades de negocio
│ │ ├── Pedido.ts
│ │ └── Producto.ts
│ └── value-objects/ # Objetos de valor
│ └── Dinero.ts
├── application/ # Casos de uso
│ ├── ports/ # Puertos (interfaces)
│ │ ├── input/ # Puertos primarios
│ │ │ └── CrearPedidoUseCase.ts
│ │ └── output/ # Puertos secundarios
│ │ ├── PedidoRepository.ts
│ │ └── NotificacionService.ts
│ └── services/ # Implementación de casos de uso
│ └── CrearPedidoService.ts
├── infrastructure/ # Adaptadores
│ ├── adapters/
│ │ ├── input/ # Adaptadores primarios
│ │ │ ├── rest/
│ │ │ │ └── PedidoController.ts
│ │ │ └── cli/
│ │ │ └── PedidoCLI.ts
│ │ └── output/ # Adaptadores secundarios
│ │ ├── persistence/
│ │ │ ├── PostgresPedidoRepository.ts
│ │ │ └── InMemoryPedidoRepository.ts
│ │ └── notification/
│ │ └── EmailNotificacionService.ts
│ └── config/ # Inyección de dependencias
│ └── container.ts
└── tests/
├── unit/ # Tests con adaptadores mock
└── integration/ # Tests con adaptadores reales
Errores Comunes al Implementar
1. Crear puertos demasiado genéricos
Un puerto debe representar un propósito de negocio, no una operación técnica. GenericRepository<T> viola este principio.
2. Filtrar detalles de infraestructura al dominio
Si tus entidades de dominio tienen anotaciones de ORM o decoradores HTTP, la separación se ha roto.
3. Ignorar la inyección de dependencias
Sin un mecanismo de inyección (constructor injection, contenedor DI), los adaptadores terminan instanciados directamente en el núcleo, destruyendo el desacoplamiento.
4. Sobre-ingeniería en aplicaciones simples
No toda aplicación necesita Arquitectura Hexagonal. Un CRUD de 3 endpoints no justifica la ceremonia adicional.
Conclusión
La Arquitectura Hexagonal es mucho más que un patrón de organización de código — es una filosofía de diseño que prioriza la independencia del dominio sobre la conveniencia técnica. Sus beneficios fundamentales son:
- Testabilidad sin infraestructura real.
- Intercambiabilidad de tecnologías sin impacto en el negocio.
- Evolución del sistema sin riesgo de regresiones.
- Claridad en la separación de responsabilidades.
Para sistemas con dominios complejos, integraciones cambiantes y equipos que valoran la calidad a largo plazo, la Arquitectura Hexagonal ofrece una base sólida y probada que ha influenciado toda una generación de patrones arquitectónicos modernos.
"Permitir que una aplicación sea igualmente conducida por usuarios, programas, pruebas automatizadas o scripts batch, y ser desarrollada y probada de forma aislada de sus dispositivos y bases de datos eventuales de producción." — Alistair Cockburn


