Este proyecto es una implementación práctica y completa de los conceptos explicados en el libro:
Disponible en:
Este proyecto es una implementación práctica y completa de los conceptos explicados en el ebook "Arquitectura Hexagonal en Java".
Sirve como una guía "canónica" para estructurar aplicaciones Java modernas utilizando principios de diseño limpio, separando la lógica de negocio de los detalles técnicos mediante Puertos y Adaptadores.
Este proyecto implementa TODOS los conceptos del ebook. Cada componente está vinculado directamente a los capítulos correspondientes:
-
Capítulo 3: Qué es realmente Arquitectura Hexagonal
- Ver: Estructura del proyecto (domain, application, infrastructure)
- Ver: Separación de capas y dependencias
-
Capítulo 4: Las 3 Leyes de la Pureza Hexagonal
- Ver:
build.gradle- Dependencias entre módulos - Ver:
ArchitectureTest.java- Validación automática de fronteras
- Ver:
-
Capítulo 5: Dominio Rico vs Dominio Anémico
- Ver:
domain/model/Task.java- Entidad con comportamiento (complete(),assign()) - Ver:
domain/model/vo/Money.java- Value Objects con lógica de negocio - Ver:
domain/model/Order.java- Aggregate Root con invariantes - Ver:
domain/service/TransferService.java- Domain Service - Ver:
domain/model/Account.java- Entidad rica con reglas de negocio
- Ver:
-
Capítulo 6: Reglas de Negocio sin Frameworks
- Ver: Todo el módulo
domain/- Sin dependencias de Spring/JPA - Ver:
domain/test/- Tests puros de dominio (sin frameworks)
- Ver: Todo el módulo
-
Capítulo 7: Casos de Uso y Orquestación
- Ver:
application/usecase/CreateTaskUseCaseImpl.java- Orquestación pura - Ver:
application/port/in/*- Input Ports (interfaces de casos de uso) - Ver:
application/usecase/UpdateTaskUseCaseImpl.java- Publicación de eventos
- Ver:
-
Capítulo 8: Puertos: Contratos antes que Implementaciones
- Ver:
application/port/out/TaskRepositoryPort.java- Output Port - Ver:
application/port/out/EventPublisherPort.java- Puerto para eventos - Ver:
application/port/out/NotificationSenderPort.java- Puerto para notificaciones
- Ver:
-
Capítulo 9: Adaptadores de Entrada
- Ver:
infrastructure/adapter/in/web/TaskController.java- Adaptador REST - Ver:
infrastructure/adapter/in/cli/TaskCliAdapter.java- Adaptador CLI - Ver:
infrastructure/adapter/in/event/TaskEventListener.java- Adaptador de eventos
- Ver:
-
Capítulo 10: Adaptadores de Salida
- Ver:
infrastructure/adapter/out/persistence/TaskRepositoryAdapter.java- Adaptador JPA - Ver:
infrastructure/adapter/out/persistence/mongodb/- Adaptador MongoDB - Ver:
infrastructure/adapter/out/persistence/redis/- Adaptador Redis - Ver:
infrastructure/adapter/out/persistence/postgres/- Adaptador PostgreSQL JDBC - Ver:
infrastructure/adapter/out/notification/- Adaptadores de notificación
- Ver:
-
Capítulo 11: Cambio de Framework
- Ver:
examples/quarkus-adapter/- Ejemplo de adaptador Quarkus - Ver: Misma lógica de aplicación, diferente framework
- Ver:
-
Capítulo 12: Cambio de Base de Datos
- Ver:
infrastructure/adapter/out/persistence/mongodb/- Cambio a MongoDB - Ver:
infrastructure/adapter/out/persistence/postgres/- Cambio a PostgreSQL - Ver:
application/port/out/TaskRepositoryContractTest.java- Contract Tests
- Ver:
-
Capítulo 13: Testing en Arquitectura Hexagonal
- Ver:
domain/test/- Tests de dominio (rápidos, sin frameworks) - Ver:
application/test/- Tests de casos de uso (con fakes) - Ver:
infrastructure/test/- Tests de integración (con Spring) - Ver:
application/port/out/TaskRepositoryContractTest.java- Contract Tests - Ver:
infrastructure/test/.../PostgresTaskRepositoryAdapterTest.java- Testcontainers
- Ver:
-
Capítulo 14: Evolución sin Reescritura
- Ver: Múltiples adaptadores de entrada (REST, CLI)
- Ver: Múltiples adaptadores de salida (JPA, MongoDB, Redis, PostgreSQL)
-
Capítulo 15: Entrevistas
- Este proyecto sirve como referencia para evaluar conocimiento
-
Capítulo 16: Eventos de Dominio
- Ver:
domain/event/TaskCompletedEvent.java- Eventos desde el dominio - Ver:
domain/model/Task.java- Disparo de eventos encomplete() - Ver:
infrastructure/adapter/in/event/TaskEventListener.java- Consumidor de eventos - Ver:
application/port/out/EventPublisherPort.java- Puerto para publicar eventos
- Ver:
-
Capítulo 17: Overengineering
- Ver:
infrastructure/adapter/in/web/GlobalExceptionHandler.java- Manejo simple - Ver: Estructura simple sin capas innecesarias
- Ver:
-
Capítulo 18: Hexagonal Mal Aplicada
- Ver:
ArchitectureTest.java- Validación automática con ArchUnit - Ver: Tests que previenen dependencias ilegales
- Ver:
- Java 21+ (verificar con
java -version) - Gradle (incluido como wrapper, no requiere instalación)
- Docker (opcional, solo para tests con Testcontainers)
./gradlew clean build# Todos los tests
./gradlew test
# Solo tests de dominio (rápidos)
./gradlew :domain:test
# Solo tests de aplicación
./gradlew :application:test
# Solo tests de infraestructura
./gradlew :infrastructure:test./gradlew bootRunLa aplicación estará disponible en:
- API REST:
http://localhost:8080 - Swagger UI:
http://localhost:8080/swagger-ui.html - OpenAPI JSON:
http://localhost:8080/v3/api-docs
Con la aplicación corriendo, puedes probar los endpoints:
# Crear una tarea
curl -X POST http://localhost:8080/api/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Mi primera tarea", "description": "Descripción de la tarea"}'
# Listar todas las tareas
curl http://localhost:8080/api/tasks
# Obtener una tarea por ID
curl http://localhost:8080/api/tasks/{id}
# Actualizar una tarea
curl -X PUT http://localhost:8080/api/tasks/{id} \
-H "Content-Type: application/json" \
-d '{"title": "Tarea actualizada", "description": "Nueva descripción", "completed": true}'
# Eliminar una tarea
curl -X DELETE http://localhost:8080/api/tasks/{id}Si prefieres no instalar Java/Gradle localmente:
docker-compose up --buildEsto compilará el proyecto dentro de un contenedor y levantará la aplicación.
Para usar la interfaz de línea de comandos:
# Activar perfil CLI
./gradlew bootRun --args="--spring.profiles.active=cli create 'Mi Tarea' 'Descripción'"
# Listar tareas
./gradlew bootRun --args="--spring.profiles.active=cli list"Este proyecto incluye Swagger UI configurado automáticamente para documentar y probar la API REST.
Una vez que la aplicación esté corriendo:
- Abre tu navegador en:
http://localhost:8080/swagger-ui.htmlohttp://localhost:8080/swagger-ui/index.html - Verás la interfaz interactiva de Swagger con todos los endpoints documentados
- Puedes probar los endpoints directamente desde la interfaz
- La documentación incluye descripciones, esquemas de datos y ejemplos
- ✅ Documentación automática de todos los endpoints REST
- ✅ Pruebas interactivas - ejecuta requests directamente desde el navegador
- ✅ Esquemas de datos - ve la estructura de requests y responses
- ✅ Códigos de respuesta - documentación de errores y códigos HTTP
- ✅ OpenAPI 3.0 - estándar de la industria
POST /api/tasks- Crear una nueva tareaGET /api/tasks- Listar todas las tareasGET /api/tasks/{id}- Obtener una tarea por IDPUT /api/tasks/{id}- Actualizar una tareaDELETE /api/tasks/{id}- Eliminar una tarea
Para obtener el esquema OpenAPI en formato JSON:
curl http://localhost:8080/v3/api-docsEl proyecto sigue una estructura multi-módulo con Gradle que respeta las fronteras de la Arquitectura Hexagonal:
arquitectura-hexagonal-java/
├── domain/ # 🎯 Núcleo del dominio (sin frameworks)
│ ├── model/ # Entidades y Value Objects
│ ├── service/ # Domain Services
│ └── event/ # Domain Events
├── application/ # 🔄 Capa de aplicación (orquestación)
│ ├── port/
│ │ ├── in/ # Input Ports (casos de uso)
│ │ └── out/ # Output Ports (repositorios, servicios externos)
│ └── usecase/ # Implementación de casos de uso
└── infrastructure/ # 🔌 Infraestructura (adaptadores)
├── adapter/
│ ├── in/ # Adaptadores de entrada (REST, CLI)
│ └── out/ # Adaptadores de salida (JPA, MongoDB, Redis)
└── config/ # Configuración de Spring Boot
domain: El núcleo puro de la aplicación. Contiene las Entidades y Lógica de Negocio. Sin dependencias de frameworks.application: Orquestación de Casos de Uso. Define los Puertos (Interfaces) y los implementa.infrastructure: Detalles técnicos. Contiene los Adaptadores (REST, JPA, Configuración de Spring Boot).
- Lenguaje: Java 21 (LTS)
- Framework: Spring Boot 3.3.0
- Build Tool: Gradle 8.x (wrapper incluido)
- Base de Datos Principal: H2 (en memoria, para desarrollo)
- Alternativas Implementadas:
- PostgreSQL (JDBC directo)
- MongoDB (Spring Data MongoDB)
- Redis (Cache)
- ORM: Spring Data JPA / Hibernate
- Framework: JUnit 5
- Mocking: Mockito
- Integración: Spring Boot Test, MockMvc
- Testcontainers: Para tests con PostgreSQL real
- ArchUnit: Para validación de arquitectura
- API Docs: SpringDoc OpenAPI 3.0 (Swagger UI)
- Acceso:
http://localhost:8080/swagger-ui.html
- Lombok: Reducción de boilerplate
- Spotless: Formateo automático de código
Esta tabla relaciona directamente los componentes del código con los capítulos teóricos del ebook:
| Componente de Código | Ubicación | Capítulo del Ebook | Concepto Clave |
|---|---|---|---|
| Domain Entities | domain/model/Task.java |
Cap 5/6 | Objetos con comportamiento (complete()), invariantes, sin setters públicos |
| Value Objects | domain/model/vo/Money.java, TaskId.java, Email.java |
Cap 5 | Objetos inmutables con lógica de negocio |
| Aggregate Root | domain/model/Order.java |
Cap 5 | Agregado con entidades hijas (OrderLine) |
| Domain Service | domain/service/TransferService.java |
Cap 5 | Lógica que involucra múltiples entidades |
| Domain Events | domain/event/TaskCompletedEvent.java |
Cap 16 | Eventos disparados desde el dominio |
| Input Ports | application/port/in/* |
Cap 7 | Interfaces que definen qué puede hacer el sistema |
| Output Ports | application/port/out/* |
Cap 8 | Interfaces que definen qué necesita el sistema |
| Use Cases | application/usecase/* |
Cap 7 | Orquestación pura, sin lógica de dominio compleja |
| REST Adapter | infrastructure/adapter/in/web/TaskController.java |
Cap 9 | Convierte HTTP a comandos, validaciones en DTOs |
| CLI Adapter | infrastructure/adapter/in/cli/TaskCliAdapter.java |
Cap 9 | Adaptador de línea de comandos |
| JPA Adapter | infrastructure/adapter/out/persistence/TaskRepositoryAdapter.java |
Cap 10 | Implementación de repositorio con JPA |
| MongoDB Adapter | infrastructure/adapter/out/persistence/mongodb/ |
Cap 10, 12 | Cambio de base de datos sin afectar dominio |
| Redis Adapter | infrastructure/adapter/out/persistence/redis/ |
Cap 10, 12 | Cache con Redis |
| PostgreSQL Adapter | infrastructure/adapter/out/persistence/postgres/ |
Cap 10, 12 | JDBC directo, cambio de BD |
| Event Listener | infrastructure/adapter/in/event/TaskEventListener.java |
Cap 16 | Consumidor de eventos de dominio |
| Notification Port | application/port/out/NotificationSenderPort.java |
Cap 8, 16 | Puerto para notificaciones |
| Unit Tests | domain/test/* |
Cap 13 | Tests de dominio rápidos (sin Spring) |
| Integration Tests | infrastructure/test/* |
Cap 13 | Tests de adaptadores (con contexto Spring) |
| Contract Tests | application/port/out/TaskRepositoryContractTest.java |
Cap 13 | Tests de contrato para repositorios |
| Testcontainers | infrastructure/test/.../PostgresTaskRepositoryAdapterTest.java |
Cap 13 | Tests con PostgreSQL real |
| Architecture Tests | ArchitectureTest.java |
Cap 18 | Validación automática de fronteras (ArchUnit) |
| Error Handling | infrastructure/adapter/in/web/GlobalExceptionHandler.java |
Cap 17 | Manejo simple y centralizado |
Este proyecto implementa una pirámide de testing completa siguiendo los principios de la Arquitectura Hexagonal (Cap 13). A continuación, cómo ejecutar cada tipo de test:
Los tests unitarios del dominio son rápidos (<100ms) y no requieren frameworks ni infraestructura.
# Ejecutar todos los tests unitarios del dominio
./gradlew :domain:test
# Ejecutar un test específico
./gradlew :domain:test --tests "TaskTest"
./gradlew :domain:test --tests "MoneyTest"
./gradlew :domain:test --tests "OrderTest"
# Ejecutar tests de edge cases
./gradlew :domain:test --tests "*EdgeCases*"Ubicación: domain/src/test/java/
TaskTest.java- Tests básicos de la entidad TaskTaskEdgeCasesTest.java- Tests de casos límiteMoneyTest.java- Tests del Value Object MoneyOrderTest.java- Tests del Aggregate Root OrderAccountTest.java- Tests de la entidad AccountTransferServiceTest.java- Tests del Domain Service
Características:
- ✅ Sin dependencias de Spring
- ✅ Sin base de datos
- ✅ Ejecución muy rápida
- ✅ Foco en lógica de negocio pura
Los tests de casos de uso usan fakes (implementaciones en memoria) en lugar de mocks.
# Ejecutar todos los tests de aplicación
./gradlew :application:test
# Ejecutar un caso de uso específico
./gradlew :application:test --tests "CreateTaskUseCaseImplTest"
./gradlew :application:test --tests "UpdateTaskUseCaseImplTest"
# Ejecutar contract tests
./gradlew :application:test --tests "*Contract*"Ubicación: application/src/test/java/
CreateTaskUseCaseImplTest.java- Test del caso de uso de creaciónUpdateTaskUseCaseImplTest.java- Test del caso de uso de actualizaciónInMemoryTaskRepositoryContractTest.java- Contract test con fake
Características:
- ✅ Usa fakes (InMemoryTaskRepository) en lugar de mocks
- ✅ Valida orquestación de casos de uso
- ✅ Verifica publicación de eventos de dominio
- ✅ Tests rápidos (sin infraestructura real)
Los tests de integración validan los adaptadores contra infraestructura real (Spring, JPA, etc.).
# Ejecutar todos los tests de infraestructura
./gradlew :infrastructure:test
# Ejecutar tests de adaptadores REST
./gradlew :infrastructure:test --tests "*Controller*"
# Ejecutar tests de adaptadores CLI
./gradlew :infrastructure:test --tests "*CliAdapter*"
# Ejecutar tests de persistencia
./gradlew :infrastructure:test --tests "*Repository*"Ubicación: infrastructure/src/test/java/
TaskControllerTest.java- Test del adaptador RESTTaskCliAdapterTest.java- Test del adaptador CLITaskRepositoryAdapterTest.java- Test del adaptador JPA
Características:
- ✅ Usa contexto de Spring Boot
- ✅ Valida integración con frameworks
- ✅ Tests más lentos (requieren contexto Spring)
- ✅ Verifican mapeo entre dominio e infraestructura
Los tests de arquitectura validan que se respeta estrictamente la arquitectura hexagonal.
# Ejecutar tests de arquitectura
./gradlew :infrastructure:test --tests "*ArchitectureTest*"
# O ejecutar todos los tests (incluye arquitectura)
./gradlew testUbicación: infrastructure/src/test/java/ArchitectureTest.java
Reglas validadas:
- ✅ El dominio NO depende de infraestructura
- ✅ El dominio NO depende de aplicación
- ✅ El dominio NO depende de Spring
- ✅ El dominio NO depende de JPA
- ✅ La aplicación NO depende de infraestructura
- ✅ Los adaptadores están en infraestructura
- ✅ Los controladores están en infraestructura
- ✅ Los casos de uso están en aplicación
- ✅ Los puertos son interfaces
- ✅ El dominio solo usa paquetes Java estándar
Ejemplo de violación detectada:
// ❌ Esto fallaría el test de arquitectura:
// En domain/model/Task.java
import org.springframework.stereotype.Component; // VIOLACIÓNLos contract tests validan que todas las implementaciones de un puerto cumplen el mismo contrato.
# Ejecutar contract tests
./gradlew :application:test --tests "*Contract*"
./gradlew :infrastructure:test --tests "*Contract*"
# Ejecutar contract test específico
./gradlew :application:test --tests "InMemoryTaskRepositoryContractTest"
./gradlew :infrastructure:test --tests "JpaTaskRepositoryContractTest"Ubicación:
application/src/test/java/.../InMemoryTaskRepositoryContractTest.java- Contract test con fakeinfrastructure/src/test/java/.../TaskRepositoryContractTestBase.java- Base abstractainfrastructure/src/test/java/.../JpaTaskRepositoryContractTest.java- Contract test con JPA
Características:
- ✅ Test abstracto que define el contrato
- ✅ Cada implementación extiende el test base
- ✅ Garantiza que todas las implementaciones se comportan igual
- ✅ Facilita cambiar de base de datos sin romper tests
Ejemplo:
// El contrato se define una vez
public abstract class TaskRepositoryContractTest {
protected abstract TaskRepositoryPort createRepository();
@Test
void save_shouldPersistTask() {
// Test que todas las implementaciones deben pasar
}
}
// Cada implementación extiende el contrato
class JpaTaskRepositoryContractTest extends TaskRepositoryContractTest {
@Override
protected TaskRepositoryPort createRepository() {
return new TaskRepositoryAdapter(jpaRepository);
}
}Los tests con Testcontainers validan adaptadores contra infraestructura real (PostgreSQL, MongoDB, etc.) usando contenedores Docker.
# IMPORTANTE: Requiere Docker corriendo
# Verificar que Docker está disponible
docker ps
# Ejecutar tests con Testcontainers
./gradlew :infrastructure:test --tests "*PostgresTaskRepositoryAdapterTest*"
# O ejecutar todos los tests (incluye Testcontainers si Docker está disponible)
./gradlew testUbicación: infrastructure/src/test/java/.../PostgresTaskRepositoryAdapterTest.java
Características:
- ✅ Levanta contenedor PostgreSQL automáticamente
- ✅ No requiere PostgreSQL instalado localmente
- ✅ Valida adaptador contra BD real
- ✅ Se deshabilita automáticamente si Docker no está disponible
Requisitos:
- Docker instalado y corriendo
docker psdebe funcionar sin errores
Nota: Este test está deshabilitado por defecto (@Disabled) porque requiere Docker. Para habilitarlo:
- Asegúrate de que Docker esté corriendo
- Elimina la anotación
@DisabledenPostgresTaskRepositoryAdapterTest.java
# Ejecutar TODOS los tests (unitarios + integración + arquitectura + contract + testcontainers)
./gradlew test
# Ejecutar tests y generar reporte
./gradlew test --info
# Ver reporte HTML de tests
# Abre: infrastructure/build/reports/tests/test/index.html| Tipo de Test | Comando | Ubicación | Tiempo |
|---|---|---|---|
| Unitarios (Dominio) | ./gradlew :domain:test |
domain/test/ |
<1s |
| Unitarios (Aplicación) | ./gradlew :application:test |
application/test/ |
<2s |
| Integración | ./gradlew :infrastructure:test |
infrastructure/test/ |
~10s |
| Arquitectura | ./gradlew :infrastructure:test --tests "*Architecture*" |
ArchitectureTest.java |
<1s |
| Contract Tests | ./gradlew :application:test --tests "*Contract*" |
application/test/ |
<1s |
| Testcontainers | ./gradlew :infrastructure:test --tests "*Postgres*" |
infrastructure/test/ |
~30s |
| Todos | ./gradlew test |
- | ~15s |
# Compilar todo
./gradlew clean build
# Formatear código
./gradlew spotlessApply
# Verificar formato
./gradlew spotlessCheck
# Levantar aplicación
./gradlew bootRun
# Generar JAR ejecutable
./gradlew bootJar
# Ver reportes de tests
open infrastructure/build/reports/tests/test/index.html- Lee
domain/model/Task.java- Entidad con comportamiento - Lee
domain/model/vo/Money.java- Value Object con lógica - Lee
domain/model/Order.java- Aggregate Root
- Lee
application/port/in/CreateTaskUseCase.java- Input Port - Lee
application/usecase/CreateTaskUseCaseImpl.java- Implementación
- Lee
infrastructure/adapter/in/web/TaskController.java- Adaptador REST - Lee
infrastructure/adapter/out/persistence/TaskRepositoryAdapter.java- Adaptador JPA
- Lee
domain/test/.../TaskTest.java- Tests de dominio - Lee
application/port/out/TaskRepositoryContractTest.java- Contract Tests
- Ebook: "Arquitectura Hexagonal en Java"
- Resumen de Implementación: Ver
RESUMEN-IMPLEMENTACION-COMPLETA.md - Plan de Implementación: Ver
PLAN-IMPLEMENTACION-PRACTICAS.md
Este proyecto es una guía de referencia. Si encuentras mejoras o quieres agregar más ejemplos:
- Asegúrate de que todos los tests pasen
- Mantén la estructura hexagonal
- Documenta los cambios con referencias al ebook
MIT License - Ver archivo LICENSE para más detalles.
Este proyecto incluye configuración completa para desplegar a AWS Elastic Beanstalk.
# 1. Instalar EB CLI
pip install awsebcli
# 2. Crear entorno económico (single instance, t3.micro)
./scripts/create-eb-environment-economico.sh
# 3. Desplegar
./scripts/deploy-eb.sh💰 Costos estimados:
- Con Free Tier: $0/mes (primeros 12 meses)
- Después de Free Tier: ~$8/mes
- Configuración: Single instance (sin load balancer) + t3.micro + H2 en memoria
Ver DEPLOY-AWS.md para:
- Guía completa de despliegue
- Configuración de base de datos (RDS)
- Despliegue automático con GitHub Actions
- Alternativas (ECS Fargate, App Runner)
- Troubleshooting
- Configuración para costos mínimos (~$0-8/mes)
- Aprovechar AWS Free Tier
- Single instance (sin load balancer)
- H2 en memoria vs RDS
- Estrategias de ahorro adicionales
- ✅ Configuración para Elastic Beanstalk (
.ebextensions/) - ✅ Scripts de despliegue automatizados
- ✅ GitHub Actions para CI/CD
- ✅ Soporte para PostgreSQL en RDS
- ✅ Health checks configurados
- ✅ Dockerfile incluido (para ECS si prefieres)
Este código acompaña al ebook "Arquitectura Hexagonal en Java" para demostrar que la teoría es aplicable, práctica y escalable.