Este proyecto muestra la implementacion de dos microservicios independientes utilizando Arquitectura Hexagonal, destacando el uso de programacion reactiva y trazabilidad distribuida con Spring Boot 3.x.
El sistema se divide en dos servicios que utilizan diferentes enfoques tecnologicos para demostrar versatilidad, ambos corriendo sobre Java 17:
- Enfoque: Alto rendimiento y manejo de flujos de datos no bloqueantes.
- Stack: Java 17, Spring WebFlux, R2DBC (SQL Reactivo) y Gradle.
- Enfoque: Simplicidad y robustez en logica de negocio.
- Stack: Java 17, Spring MVC, JPA (Tradicional) y Maven.
| Caracteristica | Product Service | Order Service |
|---|---|---|
| Java Version | 17 | 17 |
| Gestor/Build | Gradle | Maven |
| Framework Web | WebFlux (Reactivo) | MVC (Imperativo) |
| Acceso a Datos | R2DBC | Spring Data JPA |
| Cliente HTTP | WebClient | RestClient + @HttpExchange |
| Arquitectura | Hexagonal | Hexagonal |
- Arquitectura Hexagonal: Separacion total de la logica de negocio del framework y la infraestructura (Puertos y Adaptadores).
- Trazabilidad Distribuida: Integracion con Zipkin y Micrometer Tracing para rastrear peticiones entre servicios.
- Programacion Moderna: Uso de Java Records para inmutabilidad, Streams API y Programacion Funcional.
- Validacion y Errores: Manejo global de excepciones con respuestas estandarizadas y validacion de tipos.
- Integracion Reactiva con APIs Externas: Uso de WebClient reactivo para consumir servicios externos como FakeStore API.
El Product Service incluye una integracion reactiva con la FakeStore API para demostrar el consumo de APIs externas utilizando Spring WebFlux.
- Cliente HTTP Reactivo: Utiliza
WebClientde Spring WebFlux para llamadas no bloqueantes. - Sincronizacion Programada: Job programado que ejecuta la sincronizacion cada 3 minutos (configurable).
- Procesamiento de Flujo: Maneja respuestas como
Flux<FakeStoreProductResponse>y limita el procesamiento a los primeros 5 productos. - Logging Estructurado: Registra informacion detallada de productos incluyendo ID, nombre, precio, categoria y rating.
- Manejo de Errores: Gestion apropiada de errores durante la sincronizacion.
La integracion se configura automaticamente al iniciar el servicio:
fakestore:
sync:
interval: 180000 # Intervalo en milisegundos (3 minutos)Interfaz de puerto de salida que define el contrato para sincronización de productos desde fuentes externas.
Adaptador de infraestructura que implementa ProductSyncPort:
- Utiliza WebClient para llamadas HTTP reactivas
- Realiza peticiones GET a
/products - Procesa el flujo reactivo de productos
- Registra información de productos en logs
Caso de uso de aplicacion que coordina la sincronizacion:
- Depende del puerto
ProductSyncPort(no de infraestructura concreta) - Maneja la logica de negocio de sincronizacion
- Puede ser reutilizado con diferentes implementaciones del puerto
Adaptador de entrada programado que ejecuta la sincronización de forma automática:
- Ubicado en
infrastructure/adapter/input/scheduler - Dispara el servicio de aplicación de forma programada
Configura el cliente HTTP reactivo con la URL base de FakeStore API.
Record que representa la estructura de respuesta de la API externa, incluyendo rating anidado.
Starting synchronization with Fake Store API
📦 Product ID: 1
📦 Name: Fjallraven - Foldsack No. 1 Backpack
📦 Price: $109.95
📦 Category: men's clothing
📦 Rating: 3.9 (120 reviews)
---
✅ Synchronization completed - fetched and displayed 5 products from Fake Store API
La integracion incluye pruebas unitarias completas que cubren:
- Sincronizacion exitosa
- Respuestas vacias
- Manejo de errores del cliente HTTP
- Productos sin rating
- Limitacion de procesamiento a 5 productos
product-service/
├── src/
│ ├── main/
│ │ ├── java/com/cardenascode/product/
│ │ │ ├── ProductServiceApplication.java
│ │ │ ├── domain/
│ │ │ │ ├── exception/
│ │ │ │ │ ├── DomainException.java
│ │ │ │ │ └── ProductNotFoundException.java
│ │ │ │ ├── model/
│ │ │ │ │ └── Product.java
│ │ │ │ └── port/
│ │ │ │ ├── input/
│ │ │ │ │ └── ProductUseCase.java
│ │ │ │ └── output/
│ │ │ │ ├── ProductRepository.java
│ │ │ │ └── ProductSyncPort.java
│ │ │ ├── application/
│ │ │ │ ├── dto/
│ │ │ │ │ ├── CreateProductRequest.java
│ │ │ │ │ ├── UpdateProductRequest.java
│ │ │ │ │ └── ProductResponse.java
│ │ │ │ ├── mapper/
│ │ │ │ │ └── ProductMapper.java
│ │ │ │ └── usecase/
│ │ │ │ ├── FakeStoreSyncUseCase.java
│ │ │ │ └── ProductUseCaseImpl.java
│ │ │ └── infrastructure/
│ │ │ ├── adapter/
│ │ │ │ ├── input/
│ │ │ │ │ ├── scheduler/
│ │ │ │ │ │ └── FakeStoreSyncScheduler.java
│ │ │ │ │ └── web/
│ │ │ │ │ ├── FakeStoreProductResponse.java
│ │ │ │ │ └── ProductController.java
│ │ │ │ └── output/
│ │ │ │ ├── client/
│ │ │ │ │ └── FakeStoreProductSyncAdapter.java
│ │ │ │ └── persistence/
│ │ │ │ ├── entity/
│ │ │ │ │ └── ProductEntity.java
│ │ │ │ ├── ProductEntityMapper.java
│ │ │ │ ├── ProductRepositoryAdapter.java
│ │ │ │ └── R2dbcProductRepository.java
│ │ │ ├── config/
│ │ │ │ ├── DatabaseConfig.java
│ │ │ │ ├── SchedulingConfig.java
│ │ │ │ ├── SwaggerConfig.java
│ │ │ │ └── WebClientConfig.java
│ │ │ └── exception/
│ │ │ ├── ErrorResponse.java
│ │ │ ├── GlobalExceptionHandler.java
│ │ │ └── ProductNotFoundException.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── data.sql
│ │ └── schema.sql
│ └── test/
│ └── java/com/cardenascode/product/
│ ├── ProductIntegrationTest.java
│ ├── adapter/
│ │ ├── ProductEntityMapperTest.java
│ │ └── ProductRepositoryAdapterTest.java
│ ├── controller/
│ ├── mapper/
│ │ └── ProductMapperTest.java
│ └── usecase/
│ ├── FakeStoreProductResponseTest.java
│ ├── FakeStoreSyncSchedulerTest.java
│ ├── FakeStoreSyncUseCaseTest.java
│ ├── ProductUseCaseImplTest.java
│ └── WebClientConfigTest.java
├── build.gradle
├── gradlew
├── gradlew.bat
├── settings.gradle
├── Dockerfile
├── bin/
└── gradle/
order-service/
├── src/
│ ├── main/
│ │ ├── java/com/cardenascode/order/
│ │ │ ├── OrderServiceApplication.java
│ │ │ ├── domain/
│ │ │ │ ├── exception/
│ │ │ │ │ ├── DomainException.java
│ │ │ │ │ ├── InsufficientStockException.java
│ │ │ │ │ ├── OrderNotFoundException.java
│ │ │ │ │ └── ProductNotFoundException.java
│ │ │ │ ├── model/
│ │ │ │ │ ├── Order.java
│ │ │ │ │ ├── OrderItem.java
│ │ │ │ │ └── OrderStatus.java
│ │ │ │ ├── port/
│ │ │ │ │ ├── input/
│ │ │ │ │ │ └── OrderUseCase.java
│ │ │ │ │ └── output/
│ │ │ │ │ ├── OrderRepository.java
│ │ │ │ │ └── ProductClient.java
│ │ │ │ └── service/
│ │ │ │ └── OrderCalculationService.java
│ │ │ ├── application/
│ │ │ │ ├── dto/
│ │ │ │ │ ├── CreateOrderRequest.java
│ │ │ │ │ ├── OrderItemRequest.java
│ │ │ │ │ ├── OrderItemResponse.java
│ │ │ │ │ ├── OrderResponse.java
│ │ │ │ │ └── ProductDTO.java
│ │ │ │ ├── mapper/
│ │ │ │ │ └── OrderMapper.java
│ │ │ │ └── usecase/
│ │ │ │ └── OrderUseCaseImpl.java
│ │ │ └── infrastructure/
│ │ │ ├── adapter/
│ │ │ │ ├── input/web/
│ │ │ │ │ └── OrderController.java
│ │ │ │ └── output/
│ │ │ │ ├── client/
│ │ │ │ │ ├── ProductClientAdapter.java
│ │ │ │ │ └── ProductServiceClient.java
│ │ │ │ └── persistence/
│ │ │ │ ├── entity/
│ │ │ │ │ ├── OrderEntity.java
│ │ │ │ │ └── OrderItemEntity.java
│ │ │ │ ├── OrderEntityMapper.java
│ │ │ │ ├── OrderRepositoryAdapter.java
│ │ │ │ └── JpaOrderRepository.java
│ │ │ ├── config/
│ │ │ │ ├── DomainConfig.java
│ │ │ │ ├── RestClientConfig.java
│ │ │ │ └── SwaggerConfig.java
│ │ │ └── exception/
│ │ │ ├── ErrorResponse.java
│ │ │ ├── ExternalServiceException.java
│ │ │ └── GlobalExceptionHandler.java
│ │ └── resources/
│ │ └── application.yml
│ └── test/
│ └── java/com/cardenascode/order/
│ ├── OrderIntegrationTest.java
│ ├── controller/
│ │ └── OrderControllerTest.java
│ ├── service/
│ │ └── OrderCalculationServiceTest.java
│ └── usecase/
│ └── OrderUseCaseImplTest.java
├── pom.xml
├── Dockerfile
└── target/
config/
├── checkstyle/
│ ├── checkstyle.xml
│ └── suppressions.xml
└── spotbugs/
└── spotbugs-exclude.xml
Una vez que los servicios esten en ejecucion, puedes acceder a la documentacion interactiva de las APIs:
Product Service:
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI JSON: http://localhost:8080/v3/api-docs
Order Service:
- Swagger UI: http://localhost:8081/swagger-ui.html
- OpenAPI JSON: http://localhost:8081/v3/api-docs
El proyecto incluye una coleccion de Postman con casos de prueba completos:
📄 TECH.postman_collection.json
Importa esta coleccion en Postman para probar todos los endpoints con ejemplos predefinidos.
Para instrucciones detalladas sobre como compilar, ejecutar y probar el proyecto, consulta la guia rapida: