❌ Arquitectura tradicional acoplada
La lógica de negocio depende de todo
El dominio importa directamente la BD (JPA, SQLAlchemy) — cambiar de Postgres a Mongo rompe la lógica de negocio.
Los controladores HTTP llaman directo a los repositorios — no puedes probar la lógica sin levantar un servidor.
El servicio envía emails usando SMTP directamente — para testear, o mandas emails reales o mockeas librerías externas.
Una migración de framework (de Spring a Quarkus) implica reescribir lógica de negocio.
Los tests son lentos porque dependen de bases de datos reales, queues reales y APIs externas.
✅ Con Arquitectura Hexagonal
El dominio no depende de nada externo
El dominio define interfaces (Ports) — la implementación real de la BD va afuera. Cambias Postgres por Mongo sin tocar el dominio.
Puedes testear toda la lógica de negocio con implementaciones en memoria, sin HTTP ni BD real.
El servicio de email es un Port. Para tests usas un FakeEmailAdapter, en prod usas SmtpEmailAdapter.
El framework es solo un Adapter. Migrás de Spring a Quarkus cambiando solo los controllers, no el dominio.
Los tests unitarios son rapidísimos porque el dominio no tiene dependencias externas.
Port de entrada
Driving Port
Interfaz que define cómo el mundo exterior puede invocar el dominio. Es el contrato de lo que la app puede hacer.
El dominio. Define la interfaz (ej: OrderService) sin saber quién la llama.
CreateOrderUseCase GetUserQuery ProcessPaymentUseCase
Adapter de entrada
Driving Adapter
Implementación concreta que traduce una señal externa al lenguaje del dominio e invoca el Port de entrada.
La infraestructura. Conoce el framework HTTP, CLI o queue. Llama al dominio.
RestOrderController GraphQLResolver KafkaConsumer
Port de salida
Driven Port
Interfaz que define lo que el dominio necesita del exterior (persistencia, notificaciones, etc.) sin saber cómo se implementa.
El dominio. Define la interfaz sin importar la tecnología de turno (JPA, Redis, SMTP).
OrderRepository EmailNotifier PaymentGateway
Adapter de salida
Driven Adapter
Implementación concreta del Port de salida. Contiene el código de JPA, SMTP, S3, Redis, etc.
La infraestructura. Implementa la interfaz del dominio usando tecnología específica.
JpaOrderRepository SmtpEmailNotifier StripePaymentGateway
INFRAESTRUCTURA
Adapters
RestController · JpaRepository
SmtpEmailSender · KafkaProducer
S3StorageAdapter
APLICACIÓN
Use Cases
CreateOrderUseCase
ProcessPaymentUseCase
GetOrderQueryService
DOMINIO
Entities & Ports
Order · OrderItem · Customer
OrderRepository (interface)
PaymentGateway (interface)
La regla en una frase: Las dependencias siempre apuntan hacia adentro. La infraestructura conoce la aplicación, la aplicación conoce el dominio, pero el dominio no conoce a nadie. Si en tu código el dominio importa algo de infraestructura, la arquitectura hexagonal está rota.
DIP (SOLID): Este límite es la aplicación
arquitectónica del
Principio de inversión de dependencias — el dominio (alto nivel) define
puertos (abstracciones); los
adaptadores (detalles) implementan esos contratos y dependen del dominio, no al revés.
Infografía DIP →
Identidad única
Mutable
ID
Objeto del dominio con identidad única que persiste en el tiempo. Dos entidades son iguales si tienen el mismo ID, aunque sus atributos difieran.
Ejemplo: Order, User, Product. Una Order con id=42 es la misma Order aunque cambie su estado de PENDING a PAID.
Sin identidad
Inmutable
Igual por valor
Objeto que describe una característica pero sin identidad propia. Es inmutable — si necesitas "cambiar" un VO, creas uno nuevo. Dos VOs son iguales si sus valores son iguales.
Ejemplo: Money(100, "CLP"), Email("user@mail.com"), Address. Dos Money(100, "CLP") son exactamente iguales.
Aggregate Root
Consistencia
Boundary
Cluster de entidades y VOs que se tratan como una unidad. El Aggregate Root es la única puerta de entrada — nadie puede modificar los objetos internos sin pasar por él.
Ejemplo: Order es el Aggregate Root. Para añadir un OrderItem debes llamar a order.addItem(), nunca manipular el item directamente.
Caso de uso
Orquestación
Application Service
Representa una operación específica del sistema desde el punto de vista del usuario. Vive en la capa de aplicación, orquesta entidades y puertos, pero no contiene lógica de negocio.
Ejemplo: CreateOrderUseCase: carga el usuario, valida el stock, crea la Order, llama al PaymentGateway (port), guarda en el OrderRepository (port), notifica por email.
Inmutable
Pasado
Desacoplamiento
Representa algo que ocurrió en el dominio. Siempre en pasado. Permite que otros componentes reaccionen a cambios sin que el dominio conozca a sus suscriptores.
Ejemplo: OrderPlaced, PaymentProcessed, UserRegistered. Cuando se publica OrderPlaced, el servicio de email reacciona sin que el dominio lo sepa.
DDD
Límite explícito
Lenguaje ubiquo
Límite explícito dentro del cual un modelo de dominio específico es válido. Dentro del contexto, los términos tienen un significado único y preciso (Ubiquitous Language).
Ejemplo: "Cliente" en el contexto de Ventas es distinto a "Cliente" en el contexto de Soporte. Cada Bounded Context tiene su propio modelo y su propio hexágono.
Un microservicio bien diseñado suele alinearse con un Bounded Context. Arquitectura Hexagonal + DDD es la combinación más frecuente en la práctica.