Arquitectura Hexagonal (Puertos y Adaptadores)
Propuesta por Alistair Cockburn en 2005, la Arquitectura Hexagonal (formalmente conocida como el patrón de Puertos y Adaptadores) busca aislar completamente la lógica de negocio central del software respecto a los frameworks, bases de datos, librerías de terceros y tecnologías de interfaz de usuario.
El objetivo es que tu aplicación pueda ejecutarse igualmente con una interfaz web HTTP, una aplicación de consola CLI, un test automatizado en memoria o un bus de mensajería sin cambiar una sola línea de la lógica de dominio.
1. Estructura y Componentes del Hexágono #
1ADAPTADORES PRIMARIOS (Driving) ADAPTADORES SECUNDARIOS (Driven) 2 ┌─────────────────────────────────────┐ ┌──────────────────────────────────────┐ 3 │ Controlador REST / GraphQL │ │ Repositorio PostgreSQL / MongoDB │ 4 │ CLI de Consola │ │ Pasarela de Pago (Stripe/PayPal) │ 5 │ Eventos RabbitMQ / Kafka │ │ Servicio de Envío de Emails │ 6 └──────────────────┬──────────────────┘ └──────────────────▲───────────────────┘ 7 │ │ 8 ▼ │ 9 ┌─────────────────────────┐ ┌────────────┴────────────┐ 10 │ PUERTOS DE ENTRADA │ │ PUERTOS DE SALIDA │ 11 │ (Casos de Uso / UseCase)│ │ (Interfaces Repository) │ 12 └────────────┬────────────┘ └────────────▲────────────┘ 13 │ │ 14 ▼ │ 15 ┌───────────────────────────────────────────────────────────┴────────────┐ 16 │ NÚCLEO DE DOMINIO │ 17 │ (Entidades, Objetos de Valor y Reglas) │ 18 │ *Cero dependencias de librerías externas* │ 19 └────────────────────────────────────────────────────────────────────────┘
2. Puertos vs Adaptadores #
- Puertos (Ports): Son interfaces de lenguaje puro (sin tecnologías) que definen los contratos de comunicación:
- Puerto de Entrada (Driver/Primary): Interfaz que expone los casos de uso (
CrearPedidoUseCase). - Puerto de Salida (Driven/Secondary): Interfaz que declara los servicios que el dominio necesita del exterior (
PedidoRepository,EmailNotifier).
- Puerto de Entrada (Driver/Primary): Interfaz que expone los casos de uso (
- Adaptadores (Adapters): Son las clases concretas que implementan la tecnología específica:
PostgresPedidoRepositoryimplementaPedidoRepository.RestPedidoControllerinvocaCrearPedidoUseCase.
3. Ejemplo Práctico: Desacoplamiento Real #
1// ─── 1. DOMINIO PURO (Sin dependencias de base de datos ni frameworks) ─── 2export class CuentaBancaria { 3 constructor(public readonly id: string, private saldo: number) {} 4 5 retirar(monto: number) { 6 if (monto > this.saldo) throw new Error("Saldo insuficiente"); 7 this.saldo -= monto; 8 } 9 10 getSaldo(): number { return this.saldo; } 11} 12 13// ─── 2. PUERTO DE SALIDA (Interfaz pura) ─── 14export interface CuentaRepository { 15 buscarPorId(id: string): Promise<CuentaBancaria | null>; 16 guardar(cuenta: CuentaBancaria): Promise<void>; 17} 18 19// ─── 3. CASO DE USO / PUERTO DE ENTRADA ─── 20export class RetirarDineroUseCase { 21 constructor(private readonly cuentaRepo: CuentaRepository) {} 22 23 async ejecutar(cuentaId: string, monto: number): Promise<void> { 24 const cuenta = await this.cuentaRepo.buscarPorId(cuentaId); 25 if (!cuenta) throw new Error("Cuenta no encontrada"); 26 27 cuenta.retirar(monto); // Regla de negocio ejecutada en el dominio 28 await this.cuentaRepo.guardar(cuenta); 29 } 30} 31 32// ─── 4. ADAPTADOR SECUNDARIO (Implementación con BD real) ─── 33export class PostgresCuentaRepository implements CuentaRepository { 34 async buscarPorId(id: string): Promise<CuentaBancaria | null> { 35 // Código de conexión SQL / ORM... 36 return new CuentaBancaria(id, 1000); 37 } 38 async guardar(cuenta: CuentaBancaria): Promise<void> { 39 // UPDATE cuentas SET saldo = ... 40 } 41}
Resumen del tema
Conceptos clave #
- Núcleo Agnóstico: el dominio no debe importar nada de Spring, React, Express, TypeORM o Hibernate.
- Inversión de Dependencias (DIP): el dominio define la interfaz del puerto y la infraestructura exterior se adapta e implementa dicho puerto.
- Testeabilidad Extrema: permite probar todos los casos de uso creando un adaptador
InMemoryCuentaRepositoryen milisegundos sin necesidad de levantar bases de datos ni Docker.
Qué debes recordar #
Tu lógica de negocio debe ser el centro de tu arquitectura; los frameworks y las bases de datos son solo detalles de implementación intercambiables.