Arquitectura hexagonal en la práctica: ports, adapters y cuándo usarlos
La teoria ya la conoces
Si estas leyendo esto, probablemente ya sabes que es la arquitectura hexagonal. Alistair Cockburn la propuso en 2005 con un nombre mas largo (Ports and Adapters) y una idea simple: tu logica de negocio no deberia saber nada del mundo exterior. Las bases de datos, las APIs, los frameworks web, la interfaz de usuario: todo eso son detalles de implementacion que se conectan al nucleo a traves de puertos (interfaces) y adaptadores (implementaciones).
La teoria es elegante. La practica es donde las cosas se complican. Porque la pregunta que la teoria no responde es: cuando merece la pena? Y la respuesta honesta es que en muchos proyectos, no merece la pena. Al menos no desde el principio. (Si buscas una introduccion mas amplia a decisiones de arquitectura, nuestro articulo sobre el coste real de la deuda tecnica pone en contexto por que estas decisiones importan.)
Cuando es overkill
Vamos a ser directos. La arquitectura hexagonal añade complejidad estructural. Mas archivos, mas interfaces, mas indirecciones. Para ciertos tipos de proyectos, esa complejidad no se justifica:
CRUD simples. Si tu aplicacion es fundamentalmente un formulario conectado a una base de datos con validaciones basicas, la arquitectura hexagonal es un cañon para matar un mosquito. Un controller que llama a un servicio que llama a un repositorio es suficiente. Añadir puertos y adaptadores a una operacion de “lee esto, valida aquello, guarda lo otro” es ingenieria sin proposito.
Prototipos y MVPs. Cuando no sabes si el producto va a sobrevivir tres meses, invertir en una arquitectura desacoplada es optimizar prematuramente. El objetivo del MVP es validar la hipotesis, no ganar premios de ingenieria. Si sobrevive, refactorizas. Si no, no has desperdiciado semanas en abstracciones.
Proyectos con un solo adaptador por puerto. Si tu aplicacion tiene una base de datos PostgreSQL y siempre va a tener una base de datos PostgreSQL, crear una interfaz RepositoryPort y un adaptador PostgresAdapter es ceremonia sin beneficio. La interfaz solo tiene sentido si hay una posibilidad real de que necesites otra implementacion (testing aparte, que trataremos despues).
Entonces, cuando si tiene sentido?
Cuando aporta valor real
Logica de negocio compleja. Cuando tu dominio tiene reglas que no son triviales (calculos de precios con descuentos escalonados, validaciones que dependen de estado, flujos de trabajo con multiples condiciones), aislar esa logica del framework y la infraestructura permite testearla, entenderla y modificarla sin riesgo. Hemos trabajado en un sistema de cotizacion logistica donde la logica de calculo de precios dependia de 14 variables (un ejemplo de desarrollo a medida que justifica plenamente este enfoque) (peso, volumen, origen, destino, tipo de mercancia, urgencia, ruta, temporada, cliente, convenio…). Esa logica vive en el dominio, completamente desacoplada. Podemos testear 200 combinaciones en 3 segundos sin levantar un servidor.
Multiples canales de entrada. Si tu aplicacion se consume a traves de una API REST, un CLI, un sistema de colas, y un webhook, tener un unico nucleo con multiples adaptadores de entrada evita duplicar logica. Cada canal es un adaptador que traduce su formato al lenguaje del dominio.
Integraciones cambiantes. Si tu aplicacion se integra con servicios externos que pueden cambiar (migracion de un CRM a otro, cambio de proveedor de pagos, nueva API de un partner), los adaptadores de salida te permiten hacer el cambio sin tocar la logica de negocio. Esto no es teorico: hemos migrado un cliente de un ERP a otro cambiando un adaptador sin modificar ni una linea del dominio. (Mas sobre patrones de integracion ERP en nuestro executive brief dedicado.)
Equipos grandes. Cuando hay 8 o 10 desarrolladores trabajando en el mismo sistema, la arquitectura hexagonal establece fronteras claras. El equipo de dominio no necesita saber como funciona el adaptador de Kafka. El equipo de infraestructura no necesita entender las reglas de negocio. Las interfaces (puertos) son el contrato entre ambos.
Refactorizacion incremental
La forma mas habitual de llegar a la arquitectura hexagonal no es empezar desde cero. Es refactorizar un sistema existente que ha crecido hasta un punto donde el acoplamiento causa dolor real: tests que requieren base de datos, cambios en la API que rompen logica de negocio, o integraciones que se extienden por toda la aplicacion como enredaderas.
El proceso incremental que hemos usado en proyectos reales:
Paso 1: Extraer el dominio. Identifica la logica de negocio que esta mezclada con infraestructura. Muevela a clases o modulos puros, sin dependencias de framework, base de datos, o librerias externas. No cambies la logica. Solo muevela. Esto ya es util por si mismo, porque puedes empezar a testearla de forma aislada.
Paso 2: Definir puertos de salida. Para cada dependencia externa que usa tu dominio (base de datos, API externa, sistema de archivos, cola de mensajes), crea una interfaz que defina que necesita el dominio. No que ofrece la infraestructura, sino que necesita el dominio. La diferencia es critica. Tu dominio no necesita “un metodo findAll que devuelve una lista paginada con ordenacion”; necesita “obtener las facturas pendientes de un cliente.” El puerto refleja el lenguaje del dominio, no el de la base de datos.
Paso 3: Implementar adaptadores. Cada implementacion concreta de un puerto es un adaptador. Tu PostgresInvoiceRepository implementa InvoiceRepository (el puerto). Tu StripePaymentGateway implementa PaymentGateway. Los adaptadores viven fuera del dominio, en una capa de infraestructura.
Paso 4: Invertir dependencias. Ahora el dominio depende solo de interfaces que el mismo define. La infraestructura depende del dominio (implementa sus interfaces), no al reves. Esto es Dependency Inversion (la D de SOLID) aplicada a nivel arquitectonico. En la practica, necesitas un mecanismo de inyeccion de dependencias. En Spring, lo tienes nativo. En Python, FastAPI tiene Depends. En Node, puedes usar un contenedor simple o composicion manual.
Paso 5: Definir puertos de entrada. Opcional y muchas veces innecesario en la primera iteracion. Los puertos de entrada (use cases o application services) definen las operaciones que el mundo exterior puede invocar. Son utiles cuando tienes multiples puntos de entrada o cuando quieres documentar formalmente la API de tu dominio. Si tu unico punto de entrada es un controller REST, un servicio de aplicacion que delega al dominio es suficiente.
Estructura real de proyecto
La teoria dice “hexagonal”. Los directorios del proyecto dicen otra cosa. Esta es una estructura que hemos usado en produccion con Python (FastAPI) y que funciona para equipos de 3 a 8 personas:
src/
domain/
models/ # Entidades y value objects
ports/ # Interfaces de salida (repositories, gateways)
services/ # Logica de negocio pura
exceptions/ # Excepciones de dominio
application/
use_cases/ # Orquestacion de logica de dominio
dto/ # Data transfer objects
infrastructure/
adapters/
persistence/ # Implementaciones de repositorios
external/ # Clientes de APIs externas
messaging/ # Productores y consumidores de colas
web/
controllers/ # Endpoints REST
middleware/ # Autenticacion, logging, etc.
config/ # Inyeccion de dependencias, configuracion
Lo que importa no son los nombres de los directorios (puedes llamarlos como quieras) sino las reglas de dependencia:
domain/no importa nada deinfrastructure/ni deapplication/application/importa dedomain/pero no deinfrastructure/infrastructure/importa dedomain/yapplication/
Si tu IDE tiene analisis de dependencias (IntelliJ lo tiene, hay plugins para VS Code), configura estas reglas y haz que el CI las verifique. Una regla de dependencia violada hoy es deuda tecnica mañana.
Testing: donde brilla la arquitectura hexagonal
Si hay un argumento definitivo a favor de esta arquitectura, es la testabilidad. La logica de dominio, al no depender de infraestructura, se puede testear con tests unitarios rapidos y deterministas.
Tests de dominio. Puros, rapidos, sin dependencias externas. Testean reglas de negocio con datos de entrada conocidos y verifican resultados. 500 tests en 2 segundos. Sin base de datos, sin servidor, sin red.
Tests de adaptadores. Testean que los adaptadores implementan correctamente los puertos. El adaptador de persistencia se testea con una base de datos de test (o con testcontainers). El adaptador de API externa se testea con un mock del servicio. Estos tests son mas lentos pero necesarios para verificar integracion.
Tests de aceptacion. Testean el sistema completo, de punta a punta. Un request HTTP que recorre controller, use case, dominio, adaptador, base de datos, y vuelve. Pocos, lentos, y centrados en los flujos criticos de negocio.
La piramide de testing clasica (muchos unitarios, algunos de integracion, pocos e2e) encaja naturalmente con esta arquitectura. Sin ella, todos tus tests necesitan infraestructura y son lentos. Con ella, el 80% de tus tests son unitarios puros que corren en milisegundos.
Errores comunes
Despues de implementar (y ayudar a implementar) esta arquitectura en una docena de proyectos, hay errores que se repiten:
Sobre-abstraer. Crear interfaces para todo, incluyendo logica interna del dominio que nunca va a tener otra implementacion. Los puertos son fronteras con el exterior. Dentro del dominio, la simplicidad gana.
Anemia de dominio. Poner toda la logica en use cases y dejar las entidades como simples contenedores de datos. Si tu entidad Invoice no sabe calcular su total ni validar su consistencia, tu dominio esta anemico y los beneficios de la arquitectura se diluyen.
Puertos que reflejan la infraestructura. Un puerto findAllWithPaginationAndSortBy es un repositorio disfrazado de interfaz. El puerto debe hablar el lenguaje del dominio: getOutstandingInvoicesForClient, findOverduePayments.
No invertir las dependencias. Mover codigo a carpetas con nombres bonitos pero mantener imports directos de la base de datos en el dominio. Sin inversion de dependencias real, la arquitectura hexagonal es cosmetica.
Adapters que contienen logica de negocio. El adaptador de persistencia que calcula descuentos antes de guardar una factura. El adaptador de API externa que decide si reintentar basandose en reglas de negocio. Los adaptadores son traductores, no decisores. Si tu adaptador tiene un if que depende de una regla de negocio, esa logica deberia estar en el dominio.
Hexagonal vs Clean Architecture vs Onion
Vale la pena aclarar la relacion entre estas tres arquitecturas, porque la confusion es frecuente. Son variaciones del mismo principio (dependencias apuntando hacia el dominio) con diferencias de nomenclatura mas que de sustancia.
Hexagonal (Cockburn, 2005) habla de puertos y adaptadores. El enfasis esta en que el dominio es agnostico respecto al mundo exterior, y el mundo exterior se conecta a traves de interfaces.
Clean Architecture (Martin, 2012) añade mas capas (entities, use cases, interface adapters, frameworks) y formaliza la “regla de dependencia”: las capas internas no conocen a las externas. En la practica, la implementacion es muy similar a hexagonal con la capa de application separada.
Onion Architecture (Palermo, 2008) usa la metafora de capas concentricas. El nucleo es el dominio, las capas exteriores son infraestructura. Misma idea, diferente diagrama.
Para proyectos reales, la eleccion entre las tres es irrelevante. Lo que importa es el principio: el dominio no depende de la infraestructura. Si tu equipo entiende ese principio, da igual como lo llames. Si no lo entiende, ningun nombre lo va a arreglar.
En nuestra practica, usamos terminologia hexagonal (puertos y adaptadores) porque es la mas concreta. “Puerto” es mas claro que “capa de interface adapters.” Pero si tu equipo viene de Clean Architecture y esa terminologia les resulta familiar, usala. La pelea por nombres es la pelea menos productiva que puede tener un equipo de ingenieria.
Cuando empezar
Si estas empezando un proyecto nuevo y sabes que la logica de negocio va a ser compleja, empieza con hexagonal desde el dia uno. El coste adicional en la fase inicial es modesto (unas horas de setup) y el beneficio a largo plazo es enorme.
Si tienes un proyecto existente con dolor de acoplamiento, empieza por el Paso 1: extraer el dominio de un modulo concreto. No intentes refactorizar todo el sistema a la vez. Elige el modulo con mas reglas de negocio, aislalo, y usa ese como modelo para el resto. La refactorizacion incremental funciona. La refactorizacion big-bang, en nuestra experiencia, no.
La arquitectura hexagonal no es un fin en si misma. Es una herramienta para mantener la logica de negocio comprensible, testeable y modificable a medida que el sistema crece. Si tu sistema no va a crecer mucho, probablemente no la necesites. Si va a crecer (y la mayoria de los sistemas que sobreviven, crecen), es la mejor inversion de ingenieria que puedes hacer.
Etiquetas
Sobre el autor
abemon engineering
Equipo de ingenieria
Equipo multidisciplinar de ingenieria, datos e IA con sede en Canarias. Construimos, desplegamos y operamos soluciones de software a medida para empresas de cualquier escala.

