← dev-notes
DDD · PARTE 4 DE 4

Arquitectura
& Casos Prácticos

Hexagonal, Dependency Inversion, estructura en Spring Boot y cuándo NO usar DDD

DDD · P4
⬡ Hexagonal Architecture 🔄 Dependency Inversion 🌿 Spring Boot + DDD ⚠ Cuándo NO usar DDD
01 Arquitectura Hexagonal — el dominio en el centro
La sinergia que más impresiona en una entrevista senior
Hexagonal Architecture (Ports & Adapters) y DDD son naturalmente complementarios. DDD te dice qué poner en el centro (el modelo de dominio con sus Aggregates, Entities y Value Objects). Hexagonal te dice cómo aislarlo: el dominio no depende de Spring, JPA ni de ningún framework. Son los adapters los que dependen del dominio, no al revés.

⬡ Ver infografía completa: Arquitectura Hexagonal →
Capas de la arquitectura — dependencias solo apuntan hacia adentro
INFRAESTRUCTURA
@RestController JpaRepositoryImpl KafkaEventPublisher SmtpEmailAdapter StripePaymentAdapter
→ depende de Aplicación
↓ depende de ↓
APLICACIÓN
PlaceOrderUseCase ProcessPaymentService OrderRepository (port) PaymentGateway (port)
→ depende de Dominio
↓ depende de ↓
DOMINIO
Order (Aggregate) OrderItem (Entity) Money (Value Object) OrderPlaced (Event) OrderId (Value Object)
sin dependencias externas
🔌
PORT
Puerto — el contrato
Interface JavaDominio define
Una interfaz Java definida por el dominio que describe lo que necesita sin saber cómo se implementa. Es la frontera estable del sistema.
Ejemplo:
OrderRepository — el dominio lo define.
PaymentGateway — el dominio lo usa como abstracción.
Los puertos son inmutables desde el punto de vista del dominio. Cambian solo cuando cambia el negocio, no cuando cambia el proveedor.
🔧
ADAPTER
Adaptador — la implementación
InfraestructuraImplements Port
Implementación concreta del puerto. Habla con el mundo externo (BD, APIs, colas) y traduce al lenguaje del dominio.
Ejemplo:
JpaOrderRepository implements OrderRepository
StripePaymentAdapter implements PaymentGateway
Puedes tener múltiples adapters para el mismo puerto: uno real para producción, uno in-memory para tests. El dominio ni se entera.
🧪
TESTABILIDAD
El beneficio clave
Unit Test puroSin BDSin Spring
El dominio puede testearse sin levantar Spring, sin BD real y sin red. Los adapters se sustituyen por fakes o mocks en los tests de aplicación.
Capa dominio: JUnit puro, sin @SpringBootTest.
Capa aplicación: repositorio fake in-memory.
Capa infra: @SpringBootTest + Testcontainers.
Si tu test de dominio necesita Spring para arrancar, el dominio está acoplado a infraestructura. Ese es el primer síntoma de violación hexagonal.
02 Dependency Inversion — desacoplar el core de los frameworks
SIN DIP
El dominio depende de Spring/JPA
El Aggregate conoce la implementación de infraestructura. Cambiar de JPA a MongoDB, o de Spring a Quarkus, rompe el dominio.
@Entity
public class Order {
  @Id @GeneratedValue
  private Long id;
  @OneToMany(cascade=ALL)
  private List<OrderItem> items;
  // dominio contaminado por JPA
}
CON DIP
El dominio es POJO puro
El Aggregate es un POJO sin anotaciones de framework. JPA vive solo en el adapter de persistencia, que mapea a una entidad JPA separada.
// Dominio — sin imports de Spring/JPA
public final class Order {
  private final OrderId id;
  private final List<OrderItem> items;
  private OrderStatus status;
  // solo lógica de negocio aquí
}
💡
El truco del doble modelo (Domain Model + JPA Entity)
En proyectos con DDD estricto es común tener dos clases para el mismo concepto: Order (dominio, POJO puro) y OrderJpaEntity (infraestructura, @Entity). El adapter de persistencia traduce entre ambas. El coste es algo de código de mapping; el beneficio es que el dominio puede evolucionar independientemente del esquema de BD.
03 Estructura de paquetes en Spring Boot con DDD
Estructura de paquetes recomendada — organizados por capa, no por feature JAVA / SPRING BOOT
// com.myapp.orders (un paquete por Bounded Context)
├── domain/                          // POJO puros — sin Spring, sin JPA
│   ├── model/
│   │   ├── Order.java               // Aggregate Root
│   │   ├── OrderItem.java           // Entity
│   │   ├── OrderId.java             // Value Object
│   │   └── Money.java               // Value Object
│   ├── event/
│   │   └── OrderPlaced.java         // Domain Event
│   └── port/
│       ├── OrderRepository.java     // Port (interfaz)
│       └── PaymentGateway.java      // Port (interfaz)

├── application/                     // Casos de uso — orquestan, no deciden
│   └── PlaceOrderUseCase.java       // @Service — llama domain + ports

└── infrastructure/                  // Adapters — hablan con el exterior
    ├── persistence/
    │   ├── JpaOrderRepository.java  // implements OrderRepository
    │   └── OrderJpaEntity.java      // @Entity — mapeada desde Order
    ├── payment/
    │   └── StripePaymentAdapter.java // implements PaymentGateway
    └── web/
        └── OrderController.java      // @RestController — llama UseCases
🎯
La regla de oro para detectar violaciones en Spring
Si ves un import de Spring o JPA dentro de la carpeta domain/, hay una violación de DIP. El dominio no debería conocer @Service, @Repository, @Entity ni ninguna anotación de framework.
04 Cuándo NO usar DDD — la respuesta más valorada en entrevistas senior
Saber cuándo NO aplicarlo es señal de madurez técnica
Un senior no aplica DDD a todo. La complejidad de Aggregates, Repositories separados y doble modelo solo paga la factura cuando el dominio tiene reglas de negocio complejas que evolucionan. Para sistemas simples, añade complejidad sin valor.
✅   SÍ aplicar DDD
Dominio rico: reglas de negocio complejas, invariantes que proteger, estados y transiciones no triviales.
Alto acoplamiento negocio-código: los expertos del dominio cambian reglas con frecuencia y el código debe seguirlos.
Vida larga del sistema: se espera que el sistema evolucione durante años con múltiples equipos.
Múltiples subdominios: el sistema tiene áreas diferenciadas con semánticas distintas (Ventas, Logística, Facturación).
Necesidad de testabilidad: el negocio exige cobertura alta de reglas críticas sin depender de BD o externos.
❌   NO aplicar DDD
CRUD simple: si la lógica es "guardar en BD y devolver", DDD añade capas sin valor. Usa Spring Data directamente.
Dominio sin reglas: si no hay invariantes que proteger ni decisiones de negocio, no hay Aggregate que justifique el diseño.
Prototipo o MVP: cuando el objetivo es validar hipótesis rápido, DDD frena la velocidad sin añadir valor todavía.
Equipo pequeño sin experiencia en DDD: la curva de aprendizaje puede costar más que el problema que resuelve.
Dominio estable sin cambio: si las reglas no cambian y el sistema está maduro, el refactor a DDD no se amortiza.
05 Palabras clave y frases que elevan el nivel en entrevista
01
Habla de Invariantes, no de "validaciones"
La palabra más poderosa en DDD es invariante. En lugar de decir "el Aggregate valida los datos", di: "el Aggregate protege las invariantes del negocio dentro de la frontera transaccional". Eso demuestra que entiendes el propósito real de un Aggregate Root.
«El Aggregate Root es el guardián de las invariantes de negocio»
02
La Capa de Aplicación como "Director de Orquesta"
Describe tus Application Services como el director de orquesta: llaman al repositorio, recuperan el Aggregate, le delegan la decisión de negocio y publican los eventos resultantes. No tienen lógica de negocio propia — si la tienen, es un smell de diseño.
«El Application Service orquesta sin decidir. Decide el dominio»
03
Menciona la Regla de la Raíz del Aggregate
Solo se accede a objetos del Aggregate a través de su Raíz (Aggregate Root). Nunca se guarda ni se carga un objeto interno directamente desde un repositorio externo. Mencionarlo activamente en una entrevista demuestra comprensión profunda del patrón.
«Solo el Aggregate Root tiene repositorio. Los internos son privados»
04
Vincula DDD con Event Sourcing y CQRS con criterio
No los presentes como "siempre van juntos". El punto fuerte es saber que son decisiones independientes: puedes usar DDD táctico sin Event Sourcing, y puedes usar CQRS sin DDD. Si los combinas, hazlo porque el problema lo justifica, no por moda.
«DDD no requiere CQRS ni Event Sourcing — son decisiones ortogonales»
05
Referencia a proyectos reales con lenguaje correcto
Cuando hables de SubScript-Core o FridgeFlow, usa el vocabulario: "el Bounded Context de suscripciones tiene un Aggregate Subscription que protege las invariantes de renovación y cancelación". No digas "tengo una clase Order" — di "tengo un Aggregate Root Order con identidad propia y frontera transaccional".
«Aggregate con identidad propia y frontera transaccional explícita»
06 Mapa mental — DDD de punta a punta
Ruta de conceptos — de estratégico a táctico a arquitectural
ESTRATÉGICO · P1
Subdominios
Core / Supporting / Generic
Bounded Contexts
Límite del modelo
Ubiquitous Language
Lenguaje compartido
Context Mapping
ACL · C/S · Shared Kernel
TÁCTICO · P2
Entities
Identidad propia
Value Objects
Inmutables por valor
Aggregates
Guardianes de invariantes
Domain Services
Sin estado propio
PERSISTENCIA · P3
Repositories
Colección en memoria
Domain Events
Hechos pasados
Event Sourcing
Estado desde eventos
CQRS
Write vs. Read models
ARQUITECTURA · P4
Hexagonal
Ports & Adapters
DIP
POJO sin frameworks
Spring Boot
domain/ app/ infra/
Cuándo NO usarlo
CRUD simple → skip DDD