04_DDD/01 -- Arquitectura Hexagonal.md

Arquitectura Hexagonal — Puertos y Adaptadores

El diseño tradicional de aplicaciones en capas presenta una trampa sutil: el modelo de dominio termina conociendo la base de datos. En el patrón Active Record (usado en Rails, Laravel, Hibernate con entidades JPA mapeadas al esquema), las entidades contienen el método save() y su estructura se adapta a la de las tablas. Cuando el esquema cambia, hay que cambiar los modelos; cuando se migra de base de datos, el dominio se ve afectado.

La arquitectura hexagonal — también llamada Ports and Adapters — invierte esta relación: la base de datos se adapta al dominio, nunca al revés.


Las tres capas

La arquitectura hexagonal organiza el código en tres capas concéntricas. La regla de dependencia es absoluta: las capas externas conocen a las internas; las internas nunca conocen a las externas.

Pasted image 20260428092229.png

Regla: las flechas de dependencia solo apuntan hacia DENTRO

Dominio

Es el núcleo. Contiene todo lo que importa al negocio: entidades, value objects, interfaces (puertos) y reglas de negocio. No conoce ningún framework, ninguna base de datos, ninguna librería externa.

La prueba: si un cambio en el código está motivado por una decisión de infraestructura (cambiar de PostgreSQL a MongoDB, actualizar la versión de Spring), no es dominio. Si está motivado por una regla de negocio propia, sí lo es.

Aplicación

Contiene los casos de uso (también llamados Application Services o Acciones). Cada caso de uso es una feature del sistema: "guardar una descripción", "crear un embedding", "enrutar una consulta al agente correcto". La capa de aplicación orquesta: instancia entidades del dominio, las persiste usando interfaces del dominio, y publica los eventos resultantes.

Solo conoce el dominio. No sabe si la persistencia es PostgreSQL o memoria; no sabe si el transporte es HTTP o AMQP.

Infraestructura

Todo lo que ataca a I/O: base de datos, colas de eventos, llamadas HTTP (a la API de Gemini por ejemplo), sistema de ficheros. También las librerías externas que se encapsulan detrás de interfaces de dominio. Es la capa más cambiante y la que menos debe contaminar el resto.


Puertos y Adaptadores

El nombre alternativo del patrón es más preciso. Un puerto es una interfaz definida en dominio que describe qué necesita la capa interna. Un adaptador es una implementación concreta de ese puerto en infraestructura.

DescriptionRepositoryPort  ←──  Puerto (interfaz en dominio)
       ▲
       │ implementa
       │
DescriptionRepositoryImpl  ←──  Adaptador (en infraestructura)

El caso de uso solo conoce el puerto. El día que se migre de jOOQ a JPA, el caso de uso no cambia ni una línea. El único cambio es el adaptador.

El paralelismo de los enchufes es útil aquí: si viajas al Reino Unido necesitas un adaptador para enchuftarlo al puerto del país. El puerto define la forma; el adaptador conecta con el sistema concreto.


Inversión de dependencias (DIP)

El mecanismo que hace posible la arquitectura hexagonal es el Dependency Inversion Principle (la D de SOLID). En lugar de que la capa de aplicación dependa de la implementación concreta, se introduce una interfaz en dominio. Las dos partes dependen de esa abstracción:

Sin inversión:
  Aplicación ──▶ JooqDescriptionRepository  (infraestructura)

Con inversión:
  Aplicación ──▶ DescriptionRepository (interfaz de dominio)
                        ▲
                        │ implementa
              JooqDescriptionRepository (infraestructura)

La flecha de dependencia se "invierte": la infraestructura apunta al dominio, no al revés. El dominio queda aislado.


Flujo de una petición

El controlador no sabe nada de jOOQ. El servicio de aplicación no sabe nada de SQL. El dominio no sabe nada de Spring.


Estructura de módulos en dwall-module-description

La arquitectura hexagonal se refleja directamente en la estructura Maven del módulo:

dwall-module-description/
├── description-domain/      ← DOMINIO: Description, DescriptionEntity, puertos
├── description-app/         ← APLICACIÓN: servicios, use cases, controller
└── description-persistence  ← INFRAESTRUCTURA: migraciones SQL, adaptadores jOOQ

Testing por capas

La separación en capas define de forma natural la estrategia de testing:

Tipo de testQué testeaMockea infraestructura
UnitarioCasos de uso + DominioSí — via interfaces (puertos)
IntegraciónInfraestructura (SQL, GCS, HTTP)No — usa sistemas reales
AceptaciónFlujo completo end-to-endNo

Los tests unitarios no testean una clase aislada, sino la unidad lógica del caso de uso. Un test de saveDescription pasa por DescriptionService, Description y la interfaz DescriptionRepository — sin tocar ninguna base de datos real. El repositorio se reemplaza por un mock que implementa la interfaz del dominio.

El beneficio central

Si en el futuro DWall migra su persistencia de jOOQ a JDBC directo, o a una base de datos vectorial, el dominio y sus tests no cambian. Solo cambia el adaptador. Esta es la promesa de la arquitectura hexagonal: el dominio es independiente del tiempo.