¿Existe una forma de desarrollar software que no dependa directamente de frameworks, bases de datos o servicios externos y que, aun así, siga siendo fácil de adaptar y escalar?
Esta es la premisa de la Arquitectura Hexagonal, también conocida como Arquitectura de puertos y adaptadores (Ports and Adapters), un patrón de diseño que se centra en crear un ecosistema de software más flexible y adaptable al separar la lógica empresarial de las dependencias externas.
En este artículo, responderemos en detalle a las preguntas: ¿Qué es?, ¿Qué beneficios tiene? y ¿Cómo implementarla? También mostraremos los desafíos a los que nos enfrentamos y compararemos la arquitectura hexagonal con otros patrones de diseño de software.
- ¿Qué es la arquitectura hexagonal?
- ¿Cuáles son los componentes de la arquitectura hexagonal?
- ¿Como se facilita el testeo de servicios?
- ¿Cuáles son los beneficios de la arquitectura empresarial?
- Arquitectura hexagonal frente a otros patrones arquitectónicos
- ¿Cómo implementar la arquitectura hexagonal? (Paso a paso)
- Desafíos comunes en la implementación de la arquitectura hexagonal
- Conclusión
¿Qué es la arquitectura hexagonal?
Este patrón de desarrollo, inicialmente propuesto por Alistair Cockburn en torno a 2005, busca que el núcleo de la aplicación (el dominio) sea independiente de detalles externos, de modo que no sea necesario tener una interfaz de usuario ni una base de datos para ejecutarla y pueda probarse en aislamiento mediante puertos y adaptadores.
A pesar de ser un concepto relativamente antiguo, sigue siendo relevante gracias a su principio de desacoplamiento entre frameworks y tecnología. En otras palabras, se trata de desacoplar lo que cambia rápido de lo que cambia lento, es decir, el negocio.
¿Cuál es el problema con el enfoque tradicional en capas?

Layered Architecture
Antes de la aparición de este y otros enfoques, lo más habitual era diseñar aplicaciones con una arquitectura de capas. En este modelo, cada capa depende de la anterior: la interfaz de usuario depende de la capa de negocio, que a su vez depende de la capa de acceso a datos.
Podemos entender cada «capa» como un paquete o librería que expone clases y métodos a la capa superior.
Por ejemplo, podríamos tener una librería para acceder a los datos:
// DataAccessLayer.dll
public class ProductDAO {
// Acceso a base de datos
}
Y otra librería para la lógica de negocio que depende directamente de la anterior:
// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO {
private ProductDAO productDAO;
// Lógica de negocio que depende del acceso a datos
}
Si bien este enfoque es fácil de entender y se ha adoptado ampliamente, tiende a generar dependencia de detalles externos. Al final, el dominio termina conociendo o adaptándose a los elementos externos o a la tecnología de turno. Además, el cambio tecnológico se vuelve complicado, las pruebas unitarias son más costosas (ya que es necesario «levantar» infraestructura o crear mocks extensivos) y el acoplamiento accidental es una consecuencia frecuente.
¿Qué aporta la arquitectura hexagonal frente al enfoque tradicional en capas?
La arquitectura hexagonal, en cambio, invierte la dirección de las dependencias: el dominio no sabe nada de los detalles externos, son los adaptadores los que conocen el dominio a través de los puertos (interfaces). Así, es posible:
- Reemplazar la base de datos sin tocar la lógica del negocio.
- Cambiar la interfaz de usuario por una API o una CLI sin afectar el núcleo.
- Probar el dominio con mocks de prueba sencillos, sin infraestructura real.
En resumen, protege el núcleo del negocio y garantiza que toda interacción con el exterior se realice a través de contratos claros. De este modo, se reduce el coste del cambio, se garantiza la calidad y se prolonga la vida útil del software, incluso cuando las tecnologías que lo rodean evolucionan.
El siguiente artículo podría interesarte: Apache Apisix | El API gateway cloud-native de alto rendimiento para arquitecturas de microservicios
¿Cuáles son los componentes de la arquitectura hexagonal?

Hexagonal architecture
La esencia de la arquitectura hexagonal es clara: aislar la lógica empresarial de fuentes externas, como frameworks, bases de datos y servicios de terceros, para facilitar su prueba y control a través de usuarios y programas. Pero, ¿cómo podemos cumplir realmente esta promesa? ¿Cómo está distribuida exactamente la arquitectura y cuáles son los componentes que permiten este aislamiento?
Para ello, debemos definir tres conceptos principales: el dominio, los adaptadores y los puertos. A continuación, entraremos en detalle en cada uno de ellos y profundizaremos en algunos conceptos básicos del modelo.
1. ¿Qué es el dominio?
El dominio representa la lógica central del negocio, es decir, sus procesos, reglas y propiedades que definen y regulan su funcionamiento. El código del dominio debe mantenerse completamente aislado de cualquier tecnología o dependencia externa, actuando como el núcleo de la aplicación. Así se garantiza un diseño limpio, coherente y totalmente desacoplado de la infraestructura que lo rodea.
2. ¿Qué son los puertos?
Los puertos son los encargados de transmitir la información de los adaptadores al núcleo de la aplicación, actuando como intermediarios, al igual que un puerto físico en un ordenador que permite conectar distintos dispositivos. El puerto actúa como un contrato que define las reglas de comunicación entre el exterior y el dominio.
Existen dos tipos de puertos:
- Puertos de entrada (Inbound Ports):Representan las operaciones que el dominio ofrece al exterior. Permiten que la aplicación reciba solicitudes o comandos desde otras capas, como la interfaz de usuario o servicios externos. En otras palabras, definen qué puede hacer el dominio cuando se le invoca desde fuera.
- Puertos de salida (Outbound Ports): Especifican las operaciones que el dominio necesita realizar hacia el exterior para, por ejemplo, acceder a bases de datos, enviar mensajes o consumir servicios externos. Estos puertos definen las necesidades del dominio en su entorno para cumplir con sus responsabilidades.
3. ¿Qué son los adaptadores?
Siguiendo la misma premisa, los adaptadores se encargan de comunicar la información externa con el núcleo de la aplicación a través de los puertos, que actúan como intermediarios. Estos adaptadores forman parte de la capa exterior del hexágono, que se centra en la infraestructura. Gracias a esta separación, es posible modificar componentes o tecnologías sin afectar a la lógica empresarial.
En este caso, también podemos distinguir dos tipos de adaptadores:
- Adaptadores de entrada (Inbound Adapters): Son los responsables de recibir las solicitudes del exterior (por ejemplo, interfaces gráficas o API REST) y traducirlas a un formato comprensible para el dominio. Su función principal es invocar los puertos de entrada correspondientes para ejecutar la lógica empresarial.
- Adaptadores de salida (Outbound Adapters): Permiten que el dominio interactúe con recursos externos, como bases de datos, servicios de terceros, sistemas de mensajería o archivos. Implementan los puertos de salida definidos por el dominio y sirven de «traducción» de las necesidades del negocio a las tecnologías específicas utilizadas en la infraestructura.
Sigue aprendiendo sobre modelos de arquitectura empresarial: Principales diferencias entre la Arquitectura Empresarial, vs Arquitectura de Soluciones, y vs Arquitectura Técnica
4. ¿Qué es la inversión de dependencias y cómo se relaciona?
Este concepto corresponde al quinto principio del conjunto de directrices de diseño en la programación orientada a objetos, conocidas por el acrónimo SOLID.
La letra D hace referencia al principio de inversión de dependencias (Dependency Inversion Principle), cuya definición original fue propuesta por el ingeniero Robert C. Martin, quien la expresó de la siguiente manera:
“Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.”
— Robert C. Martin
En otras palabras, en lugar de que los módulos de alto nivel (los que contienen la lógica principal del negocio) dependan directamente de los módulos de bajo nivel (como las bases de datos, los controladores o los servicios externos), ambos deben apoyarse en abstracciones, es decir, en interfaces o contratos que definan cómo se comunican entre sí.
De este modo, los cambios en los detalles de implementación no afectan al funcionamiento del sistema en su conjunto. Por ejemplo, si se reemplaza una base de datos o un servicio externo, solo es necesario modificar el componente que implementa la interfaz correspondiente sin alterar la lógica empresarial.
¿Como se facilita el testeo de servicios?
Al depender de abstracciones en lugar de implementaciones concretas, es posible sustituir fácilmente las dependencias reales por versiones simuladas o “mock” durante las pruebas.
Esto significa que un servicio puede probarse de manera aislada, sin necesidad de conectarse a bases de datos, API externas u otros módulos complejos. De este modo, las pruebas unitarias son más predecibles, rápidas y seguras, ya que el comportamiento de las dependencias se controla por completo dentro del entorno de prueba.
¿Cuáles son los beneficios de la arquitectura empresarial?
La arquitectura hexagonal promueve diseños de software adaptables y escalables a nuevas tecnologías. Entre sus beneficios, podemos destacar cuatro características principales:
-
Alto mantenimiento
Al separar claramente las responsabilidades entre el dominio, los puertos y los adaptadores, el código se vuelve más modular y comprensible. Así se facilita la localización y corrección de errores, así como la implementación de mejoras sin afectar a otras partes del sistema.
La independencia entre capas reduce el riesgo de regresiones y acelera los ciclos de desarrollo.
-
Testabilidad avanzada
Gracias al bajo acoplamiento entre los componentes, es posible probar cada módulo de forma aislada mediante mocks o implementaciones simuladas.
Esta característica facilita la realización de pruebas unitarias e integraciones controladas sin necesidad de utilizar infraestructura real, lo que garantiza una mayor fiabilidad y reduce el tiempo de validación en los entornos empresariales.
-
Flexibilidad
La arquitectura hexagonal permite integrar nuevas tecnologías o servicios externos sin modificar el núcleo del negocio.
Al definir puntos de conexión a través de interfaces (puertos), se pueden reemplazar o agregar adaptadores (por ejemplo, una base de datos, un proveedor de mensajería o una API externa) sin alterar la lógica principal del sistema.
-
Adaptabilidad
Esta arquitectura permite evolucionar el sistema de manera controlada. El dominio permanece estable mientras que las implementaciones tecnológicas pueden actualizarse o migrarse con una fricción mínima.
Esto se traduce en una mayor resiliencia frente a los cambios y en una vida útil más larga del software.
El siguiente artículo te pude interesar: DevOps: Arquitectura monolítica vs Microservicios


¡Habla con nuestros expertos!
Contacta con nuestro equipo y descubre las tecnologías de vanguardia que potenciarán tu negocio.
contáctanosArquitectura hexagonal frente a otros patrones arquitectónicos
Hasta ahora hemos hablado de las características y beneficios de la arquitectura hexagonal. Sin embargo, ¿en qué se diferencia de otros enfoques? En esta sección nos centraremos en definir estas diferencias.
Arquitectura hexagonal vs clean architecture

The clean architecture
La Arquitectura Clean (Robert C. Martin) y Hexagonal comparten los principios de independencia del framework y del dominio en el centro. Clean se basa en la idea de estructurar una aplicación en anillos concéntricos con su propio nivel de abstracción. Para ello, cuenta con cuatro componentes principales, que pueden ser:
- Entities (Entidades): Modelos del dominio con reglas de negocio específicas y de larga vida útil.
- Use Cases / Interactors (Casos de uso): Orquestan las reglas de la aplicación, coordinan entidades y definen entradas y salidas.
- Interface Adapters (Adaptadores de interfaz): Traductores entre el mundo externo y los casos de uso (controladores, presentadores, mapeadores y repositorios como interfaces).
- Frameworks & Drivers (Frameworks e infraestructura): Detalles periféricos: bases de datos, frameworks web, interfaz de usuario, mensajería, dispositivos, etc. Son reemplazables sin afectar al dominio.
Arquitectura hexagonal frente a arquitectura en cebolla (onion)

Onion Architecture
Esta arquitectura, definida por Jeffrey Palermo en 2008, se basa en el principio de desacoplamiento de la lógica empresarial respecto a los detalles de la infraestructura, como la base de datos o la interfaz de usuario (UI). Este modelo plantea estructurar las aplicaciones en capas, siendo el dominio del negocio el eje central, y los niveles siguientes pueden definirse como:
- Dominio (Domain Model): Entidades y objetos de valor con las reglas de negocio.
- Servicios de Dominio (Domain Services): Lógica de negocio que no encaja en una sola entidad; contratos como repositorios.
- Aplicación (Application Services): Orquesta casos de uso, coordina dominios y define los puertos (interfaces) necesarios.
- Infraestructura (Infrastructure): Implementaciones de los puertos: ORM/BD, mensajería, archivos, correo electrónico, API externas.
En la práctica, las diferencias entre este modelo y Clean son sutiles, ya que ambos persiguen el mismo principio: situar el dominio en el centro y aislar los detalles para facilitar las pruebas, la mantenibilidad y la sustitución de tecnologías.
Arquitectura hexagonal y Domain-Driven Design (DDD)

Domain Driven Designs
Este diseño, introducido inicialmente por Eric Evans en su libro Domain-Driven Design: Tackling Complexity in the Heart of Software en 2003, se centra en modelar el núcleo del negocio (por ejemplo, ventas, logística y facturación), de modo que el software refleje su lógica y reglas reales.
Para lograrlo, promueve una colaboración continua entre expertos en la materia y desarrolladores, utilizando un lenguaje común (lenguaje ubicuo) que se traduce directamente en código. De este modo, el sistema resuelve problemas técnicos y responde a las necesidades y procesos empresariales, facilitando su evolución y mantenimiento a medida que cambian los requisitos.
Entre sus componentes, podemos mencionar:
- Lenguaje ubicuo: Un vocabulario común que comparten los equipos de negocio y desarrollo y que también se utiliza en el código.
- Contextos delimitados (Bounded Contexts): Dividen el sistema en áreas con un modelo y un lenguaje propios para evitar confusiones.
- Entidades y Objetos de Valor: Representan los conceptos importantes del negocio, tienen identidad propia y describen características o atributos.
- Agregados: Grupos de entidades y valores tratados como una unidad coherente.
- Servicios y Repositorios: Los servicios ejecutan acciones empresariales y los repositorios permiten guardar y recuperar información del dominio sin exponer detalles técnicos.
- Eventos de Dominio: Describen hechos importantes que ocurren dentro de la empresa.
- Capa de Aplicación: Coordina las acciones del sistema sin contener la lógica del negocio.
- Capa Anti-Corrupción: Protege el modelo de dominio cuando se conecta con sistemas externos.
Papel en los microservicios

Microservices architecture
Un microservicio es una pequeña unidad de servicio independiente que ejecuta tareas específicas dentro de un sistema más amplio. Cada microservicio opera de forma autónoma y se comunica con los demás a través de APIs bien definidas.
En el contexto de la arquitectura hexagonal, por ejemplo, cada microservicio puede implementar la arquitectura de manera interna, organizando su estructura en torno al dominio y separando claramente la lógica empresarial de los detalles externos.
Esto significa que en cada microservicio se pueden definir puertos (interfaces) que representan las entradas y salidas del sistema, así como adaptadores que implementan estas interfaces para interactuar con bases de datos, API externas o colas de mensajería.
De este modo, cada microservicio mantiene un alto grado de independencia, verificabilidad y reemplazabilidad, alineándose con los principios de bajo acoplamiento y alta cohesión característicos de la arquitectura hexagonal y de las buenas prácticas de diseño de sistemas distribuidos.
¿Quieres saber más sobre cómo funcionan las arquitecturas de microservicios? Este recurso puede interesarte: Migración a arquitecturas basadas en microservicios: Casos de éxito
Tabla comparativa de arquitecturas
A continuación, presentamos de forma amplia las diferentes arquitecturas y su relación con Hexagonal.
| Tema | Arquitectura Hexagonal (Ports & Adapters) | Comparación / Enfoque del otro patrón | Similitudes y diferencias | Regla de dependencias / diseño |
| Hexagonal vs. Clean Architecture | Pone la lógica de negocio en el centro y aísla el interior (dominio + aplicación) de lo externo mediante puertos (interfaces) y adaptadores; los adaptadores pueden ser UI, BD, mensajería, tests, etc. | Clean Architecture también centra reglas de negocio (entidades + casos de uso) y sitúa frameworks, UI, BD en anillos externos. | Ambas separan el dominio de la infraestructura. Hexagonal se expresa por “dentro/afuera” con puertos/adaptadores; Clean usa anillos concéntricos con la regla de dependencias explícita. | Dependencias apuntan hacia adentro; los adaptadores dependen del núcleo, no al revés. Clean formaliza lo mismo con capas y círculos. |
| Hexagonal vs. Onion (Arquitectura en cebolla) | Núcleo protegido que conversa con el exterior por puertos; múltiples adaptadores para un mismo puerto. | Onion descompone el núcleo en anillos (dominio, aplicación) y empuja infraestructura a los anillos externos con IoC. | Mismo principio (dominio aislado, inversión de dependencias). La diferencia es énfasis visual/terminológico: puertos/adaptadores (Hexagonal) vs. anillos concéntricos (Onion). | En ambos, detalles (BD, UI) dependen del núcleo; reemplazables sin afectar el dominio. |
| Hexagonal y DDD | Encaja naturalmente con DDD: el núcleo suele modelar Entidades/VO, Agregados y Servicios de dominio; los puertos expresan casos de uso y políticas. | DDD es un conjunto de conceptos para modelar dominios complejos (Lenguaje Ubicuo, Bounded Contexts, Agregados, etc.). | Hexagonal aporta la forma de encapsular el modelo DDD y aislarlo; DDD aporta el contenido del núcleo (modelo). | Contextos delimitados a nivel estratégico; puertos/adaptadores ayudan a mantener los límites y a integrar contextos sin “corromper” el modelo. |
| Papel en los microservicios | Cada microservicio puede estructurarse internamente como un hexágono: núcleo de negocio + puertos/adaptadores para HTTP, mensajería, BD, etc. | Microservicios es un estilo arquitectónico distribuido; cada servicio encapsula un bounded context y publica APIs. | Complementarios: Hexagonal define la arquitectura interna de cada servicio; Microservicios define la arquitectura del sistema (despliegue, comunicación, autonomía). | Mantener el dominio en el centro de cada servicio y aislar integraciones facilita testeo, reemplazo de tecnologías y evolución independiente. |
¿Cómo implementar la arquitectura hexagonal? (Paso a paso)
- Define el núcleo (dominio + aplicación)
- Escribe primero el modelo de dominio (entidades/VO) y los casos de uso/servicios de aplicación sin ninguna librería externa.
- Objetivo: que el núcleo compile y tenga tests sin BD, web ni mensajería.
- Escribe primero el modelo de dominio (entidades/VO) y los casos de uso/servicios de aplicación sin ninguna librería externa.
- Declara los puertos como interfaces
- Puertos de entrada: cómo entra una petición al núcleo (p. ej., CreateOrder, GetBalance).
- Puertos de salida: lo que el núcleo necesita del exterior (p. ej., Payments, Inventory, Clock, EventBus).
- Los puertos viven junto al caso de uso y describen conversaciones con el exterior, no tecnologías.
- Puertos de entrada: cómo entra una petición al núcleo (p. ej., CreateOrder, GetBalance).
- Implementa adaptadores para cada puerto
- Adaptadores de entrada: Que traducen la solicitud a un puerto de entrada y formatean la respuesta.
- Adaptadores de salida: BD/REST externos/Kafka/Email que implementan los puertos de salida.
- Puedes tener varios adaptadores por puerto (p. ej., Postgres y Mongo para el mismo repositorio).
- Adaptadores de entrada: Que traducen la solicitud a un puerto de entrada y formatean la respuesta.
- Inversión de dependencias y composición
- El núcleo no conoce frameworks. En el borde de la app (bootstrap) ensambla: crea adaptadores, inyecta en casos de uso y publica controladores.
- Mantén dependencias apuntando hacia adentro (detalles → puertos → casos de uso → dominio).
- El núcleo no conoce frameworks. En el borde de la app (bootstrap) ensambla: crea adaptadores, inyecta en casos de uso y publica controladores.
- Estructura de proyecto sugerida (ejemplo orientativo)
/src
/domain # entidades, VO, reglas del dominio
/application # casos de uso + puertos (interfaces)
/adapters
/in/web # controllers/http -> puertos de entrada
/in/cli # comandos batch
/out/db # repositorios BD -> puertos de salida
/out/messaging # publicadores/consumidores -> puertos de salida
/bootstrap # composición/inyección de dependencias
La organización exacta puede variar por lenguaje, pero es importante respetar la dirección de dependencias y el aislamiento del núcleo.
- Diseña contratos estables en los puertos
- Modela operaciones y DTOs del caso de uso (no exponer modelos de BD ni de la API externa).
- Los puertos deben soportar tests de contrato para validar adaptadores intercambiables.
- Modela operaciones y DTOs del caso de uso (no exponer modelos de BD ni de la API externa).
- Testing por capas (primero el núcleo)
- Unit tests del dominio y casos de uso (sin frameworks).
- Tests de contrato para adaptadores de salida (p. ej., el repo debe comportarse igual en memoria y con Postgres).
- Tests de integración en el borde (controlador HTTP ↔ caso de uso ↔ adaptadores reales/dobles).
- Unit tests del dominio y casos de uso (sin frameworks).
- Observabilidad en los bordes, no en el núcleo
- Métricas, tracing y logging viven en adaptadores o bootstrap; el núcleo sólo usa puertos para notificar eventos si hace falta.
- Estrategia de migración / evolución
- Empieza con un monolito modular en hexágono; más adelante podrás mover adaptadores a procesos separados sin tocar el núcleo.
- Útil cuando se quiere modernizar UI/BD sin reescribir la lógica de negocio.
- Empieza con un monolito modular en hexágono; más adelante podrás mover adaptadores a procesos separados sin tocar el núcleo.
- Ejemplo mínimo de flujo
- POST /orders (adaptador HTTP) → invoca CreateOrder (puerto de entrada).
- CreateOrder usa Inventory y Payments (puertos de salida).
- Adaptadores concretos: InventoryRestAdapter, PaymentsStripeAdapter, OrderRepositoryPostgres.
- El bootstrap arma todo y expone el endpoint.
Ejemplo de implementación
Arquitectura Hexagonal en la Práctica
Para comprender cómo se materializan los principios de la Arquitectura Hexagonal, analicemos una implementación concreta de un sistema de gestión de reservas. Este ejemplo sigue las mejores prácticas establecidas en la documentación de AWS Prescriptive Guidance, demostrando la separación entre el dominio central y los adaptadores externos.
Estructura del Dominio Central
Entidad Recipient: El Corazón del Negocio
La entidad Recipient encapsula las reglas de negocio fundamentales sin ninguna dependencia de frameworks externos o infraestructura:
python
class Recipient:
def __init__(self, recipient_id: str, email: str, first_name: str, last_name: str, age: int):
self.__recipient_id = recipient_id
self.__email = email
self.__first_name = first_name
self.__last_name = last_name
self.__age = age
self.__slots = []
@property
def recipient_id(self) -> str:
return self.__recipient_id
@property
def email(self) -> str:
return self.__email
def add_reserve_slot(self, slot: ‘Slot’) -> bool:
if self.are_slots_same_date(slot):
return False
self.__slots.append(slot)
return True
def are_slots_same_date(self, slot: ‘Slot’) -> bool:
for selfslot in self.__slots:
if selfslot.reservation_date == slot.reservation_date:
return True
return False
Puertos: Las Interfaces de Comunicación
Puerto de Entrada: Gestión de Reservas
El puerto de entrada define el contrato para las operaciones que el sistema expone al mundo exterior:
python
class IRecipientInputPort(ABC):
@abstractmethod
def make_reservation(self, recipient_id: str, slot_id: str) -> Status:
pass
Implementación del Caso de Uso
python
class RecipientInputPort(IRecipientInputPort):
def __init__(self, recipient_output_port: IRecipientOutputPort, slot_output_port: ISlotOutputPort):
self.__recipient_output_port = recipient_output_port
self.__slot_output_port = slot_output_port
def make_reservation(self, recipient_id: str, slot_id: str) -> Status:
# Obtiene las entidades a través de los puertos de salida
recipient = self.__recipient_output_port.get_recipient_by_id(recipient_id)
slot = self.__slot_output_port.get_slot_by_id(slot_id)
# Ejecuta la lógica de dominio
ret = recipient.add_reserve_slot(slot)
# Persiste los cambios a través del puerto de salida
if ret == True:
ret = self.__recipient_output_port.add_reservation(recipient)
return Status.SUCCESS if ret else Status.FAILURE
Adaptadores: La Conexión con el Mundo Exterior
Adaptador de Base de Datos DynamoDB
Este adaptador implementa la persistencia concretamente para DynamoDB, manteniendo el dominio aislado de detalles de infraestructura:
python
class DDBRecipientAdapter(IRecipientAdapter):
def __init__(self):
ddb = boto3.resource(‘dynamodb’)
self.__table = ddb.Table(table_name)
def load(self, recipient_id: str) -> Recipient:
try:
response = self.__table.get_item(Key={‘pk’: ‘RECIPIENT#’ + recipient_id})
item = response[‘Item’]
return Recipient(
recipient_id=item[‘recipient_id’],
email=item[‘email’],
first_name=item[‘first_name’],
last_name=item[‘last_name’],
age=int(item[‘age’])
)
except ClientError as err:
logging.error(err)
return None
def save(self, recipient: Recipient) -> bool:
try:
self.__table.put_item(Item={
‘pk’: ‘RECIPIENT#’ + recipient.recipient_id,
‘recipient_id’: recipient.recipient_id,
‘email’: recipient.email,
‘first_name’: recipient.first_name,
‘last_name’: recipient.last_name,
‘age’: recipient.age
})
return True
except ClientError as err:
logging.error(err)
return False
Configuración y Inyección de Dependencias
Factory Pattern para el Ensamblaje
La fábrica se encarga de wirear todas las dependencias, creando una composición root donde se conectan los puertos con sus adaptadores:
python
def get_recipient_input_port():
return RecipientInputPort(
RecipientOutputPort(DDBRecipientAdapter()),
SlotOutputPort(DDBSlotAdapter())
)
Manejador Lambda como Punto de Entrada
En un entorno serverless, el handler de Lambda actúa como adaptador primario, transformando eventos en llamadas al puerto de entrada:
python
def lambda_handler(event, context):
try:
recipient_id = event[‘pathParameters’][‘recipient_id’]
slot_id = event[‘pathParameters’][‘slot_id’]
recipient_input_port = get_recipient_input_port()
status = recipient_input_port.make_reservation(recipient_id, slot_id)
return {
‘statusCode’: 200 if status == Status.SUCCESS else 400,
‘body’: json.dumps({‘status’: status.name})
}
except Exception as e:
logging.error(f»Error processing request: {str(e)}«)
return {
‘statusCode’: 500,
‘body’: json.dumps({‘error’: ‘Internal server error’})
}
Flujo de una Petición Típica
- Entrada: Una solicitud HTTP llega al adaptador primario (Lambda handler)
- Transformación: El handler extrae los parámetros y los convierte en objetos de dominio
- Delegación: Se invoca el puerto de entrada correspondiente con los parámetros
- Lógica de Negocio: El caso de uso coordina las entidades y aplica las reglas de negocio
- Persistencia: A través de los puertos de salida, se persisten los cambios
- Respuesta: El resultado fluye de vuelta a través de las capas hasta el adaptador primario
Consideraciones de implementación en diferentes lenguajes
A continuación mostraremos algunos ejemplos usando Java y Python:
Java
En Java puro, la implementación se centra en la separación de módulos y el uso de interfaces para definir los contratos.
- Estructura de Módulos: Es común organizar el proyecto en al menos dos módulos: core (o domain) y application. El módulo core contiene las entidades y las interfaces de los puertos de salida (como UserRepository), sin dependencias externas. El módulo application contiene los adaptadores y depende del core .
- Núcleo Puro: El núcleo debe poder compilarse y probarse sin necesidad de ningún framework. Las únicas dependencias permitidas son el propio lenguaje .
Ejemplo: Puerto de Salida en el Núcleo
Este código define un puerto de salida en el módulo core. La implementación concreta (adaptador) se proveerá externamente.
java
// En el módulo ‘core’
// Puerto de Salida (Outbound Port)
public interface UserRepository {
User findById(Long id);
void save(User user);
}
Spring Boot
Spring Boot es ideal para implementar esta arquitectura gracias a su sistema de inyección de dependencias. La clave es evitar que las anotaciones del framework contaminen el núcleo.
- Configuración como «Pegamento»: Utiliza clases de @Configuration para conectar los beans del núcleo con los adaptadores de Spring, manteniendo el núcleo libre de anotaciones como @Service .
- Adaptadores REST y JPA: Los controladores Spring MVC actúan como adaptadores primarios (o de entrada), mientras que los repositorios Spring Data JPA son adaptadores secundarios (o de salida) .
Ejemplo: Adaptador de Entrada (Controlador REST)
El controlador recibe peticiones HTTP y delega la lógica al servicio de aplicación (puerto de entrada) .
java
// Adaptador Primario (Primary Adapter)
@RestController
@RequestMapping(«/api/users»)
public class UserController {
private final UserService userService; // Puerto de Entrada
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping(«/{id}»)
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(UserDto.fromDomain(user));
}
}
Ejemplo: Configuración para Conectar el Núcleo
Esta clase crea el bean del servicio del núcleo, inyectándole el adaptador de repositorio .
java
@Configuration
public class UserConfiguration {
@Bean
public UserService userService(UserRepository userRepository) {
return new UserServiceImpl(userRepository);
}
}
Python
Se puede utilizar un sistema de inyección de dependencias manual o con librerías como dependency_injector.
- Estructura por Paquetes: Organiza el proyecto en paquetes como domain, ports, adapters.
- Inversión de Dependencias: Las funciones de caso de uso deben recibir los adaptadores que necesitan (por ejemplo, un repositorio) en su inicialización.
Ejemplo: Caso de Uso y Puerto (Pseudocódigo Python)
python
# En domain/ports/user_repository.py (Puerto de Salida)
class UserRepository:
def find_by_id(self, user_id):
raise NotImplementedError
# En domain/services/user_service.py (Caso de Uso / Puerto de Entrada)
class UserService:
def __init__(self, user_repository: UserRepository):
self._user_repository = user_repository
def get_user(self, user_id):
return self._user_repository.find_by_id(user_id)
# En adapters/orm/sqlalchemy_repository.py (Adaptador Secundario)
class SqlAlchemyUserRepository(UserRepository):
def find_by_id(self, user_id):
# … lógica usando SQLAlchemy …
return user
# En adapters/api/flask_app.py (Adaptador Primario)
app = Flask(__name__)
user_repository = SqlAlchemyUserRepository()
user_service = UserService(user_repository)
@app.route(«/users/<user_id>»)
def get_user(user_id):
user = user_service.get_user(user_id)
return jsonify(user.to_dict())
Otros Lenguajes & Frameworks
Los conceptos de la Arquitectura Hexagonal son aplicables a casi cualquier ecosistema.
- TypeScript/Node.js: Se puede implementar de forma muy similar a Java. Los puertos se definen como interfaces TypeScript, y los adaptadores son clases que las implementan. Los frameworks como Nest.js son muy adecuados para este patrón .
- .NET: Al igual que Spring Boot, .NET cuenta con un contenedor de inyección de dependencias robusto que facilita la conexión de las interfaces del núcleo con sus implementaciones en la capa de infraestructura .
Desafíos comunes en la implementación de la arquitectura hexagonal
Al implementar la Arquitectura Hexagonal, los equipos suelen encontrar varios desafíos recurrentes que van más allá de los aspectos técnicos. La siguiente tabla resume estos obstáculos comunes para ayudarte a identificarlos y prepararte para enfrentarlos.
| Desafío | ¿Por qué sucede? | Consecuencia principal |
| Curva de aprendizaje inicial | El equipo está acostumbrado a arquitecturas en capas tradicionales; conceptos como «puertos», «adaptadores» e «inversión de dependencias» son nuevos. | Frena la velocidad del equipo al inicio y puede generar frustración. |
| Complejidad inicial y sobrecarga de código | Crear puertos, adaptadores y múltiples capas para funcionalidades simples parece excesivo. | Sensación de «estar escribiendo mucho código para poco beneficio» en proyectos pequeños. |
| Desacoplamiento excesivo («Destructive Decoupling«) | Siguiendo estrictamente las reglas, se crean abstracciones e interfaces para todo, incluso cuando no son necesarias. | El código se vuelve más difícil de seguir y mantener de lo que sería sin la arquitectura. |
| Dificultad para seguir el flujo («The Hidden Maze«) | Los casos de uso se vuelven interdependientes, creando una larga cadena de llamadas entre componentes. | Es difícil entender cómo una solicitud viaja por el sistema y depurar errores. |
| Diseño impulsado por la base de datos | Se diseña el esquema de la base de datos primero, moldeando la lógica de negocio alrededor de ella. | La lógica de negocio queda atrapada por la estructura de datos, perdiendo independencia. |
| Modelos de dominio anémicos | Las clases del núcleo solo contienen datos (getters/setters), y la lógica de negocio se dispersa en servicios. | Se pierde el objetivo de centralizar la lógica de negocio en el corazón del sistema. |
| Gestión de múltiples adaptadores | A medida que el sistema crece, se añaden más adaptadores para nuevas APIs, bases de datos, etc. | Mantener y coordinar todos estos adaptadores se vuelve una tarea compleja. |
| Resistencia al cambio cultural | No es un problema técnico, sino humano. La arquitectura requiere un cambio en la mentalidad de desarrollo. | Resistencia activa o pasiva del equipo, que se siente cómodo con las prácticas anteriores. |
Conclusión
La Arquitectura Hexagonal representa más que un simple patrón técnico: es una filosofía de diseño que prioriza la longevidad y mantenibilidad del software. Al separar claramente el núcleo del negocio de los detalles tecnológicos, las organizaciones pueden construir sistemas que evolucionen con las demandas del mercado sin requerir reescrituras costosas.
Los beneficios en testabilidad, flexibilidad y mantenimiento no se encuentran solo en papel, son visibles para equipos alrededor del mundo al adoptar este enfoque.
Sin embargo, la implementación exitosa requiere una curva de aprendizaje inicial y la dificultad de gestionar múltiples adaptadores pueden convertirse en obstáculos importantes sin la guía adecuada.
¿Listo para llevar tu arquitectura al siguiente nivel?
Si tu equipo está considerando esta transición o necesita optimizar una implementación existente, no tienes por qué hacerlo solo. Contáctanos hoy mismo e impulsemos juntos una arquitectura estructurada y desacoplada.





