Workshop: Monolito Modular Real con Spring Modulith - BarranquillaJUG
Este es el punto de partida del workshop. Es una aplicación Spring Boot funcional con package-by-layer que tiene problemas de acoplamiento que vas a identificar y refactorizar durante el taller.
com.geovannycode.bookstore/
├── config/
│ ├── GlobalExceptionHandler.java ← conoce todos los dominios
│ └── RabbitMQConfig.java
├── entities/
│ ├── ProductEntity.java ← dominio Catalog
│ ├── OrderEntity.java ← dominio Orders
│ └── InventoryEntity.java ← dominio Inventory
├── exceptions/
│ ├── ProductNotFoundException.java
│ ├── ProductAlreadyExistsException.java
│ ├── OrderNotFoundException.java
│ └── InvalidOrderException.java
├── models/
│ ├── CreateProductRequest.java
│ ├── CreateOrderRequest.java
│ ├── CreateOrderResponse.java
│ ├── OrderCreatedEvent.java
│ ├── OrderStatus.java
│ └── PagedResult.java
├── repositories/
│ ├── ProductRepository.java ← accesible desde cualquier clase
│ ├── OrderRepository.java
│ └── InventoryRepository.java
├── services/
│ ├── ProductService.java
│ ├── OrderService.java ← ⚠️ inyecta ProductRepository e InventoryRepository
│ ├── InventoryService.java
│ └── OrderEventsInventoryHandler.java
└── web/
├── ProductRestController.java
└── OrderRestController.java
Antes de empezar a refactorizar, lee el código y responde:
- ¿Cuántos repositorios inyecta
OrderService? ¿De qué dominios son? - ¿Qué hace
OrderEventsInventoryHandleractualmente? ¿Es letra muerta? - ¿Cuántas excepciones de dominios distintos maneja
GlobalExceptionHandler? - ¿Por qué
OrderCreatedEventestá en el paquetemodels/? - Si extraes
Catalogcomo microservicio, ¿qué clases tendrías que mover?
- Java 25
- Docker Desktop corriendo
- Maven 3.9+ (o usar
./mvnw)
docker compose up -d./mvnw test -Dtest=BookstoreApplicationTests./mvnw spring-boot:run# Listar productos
curl http://localhost:8080/api/catalog/products
# Crear una orden (observa los logs de OrderService)
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
"productCode": "P001",
"quantity": 2,
"customerName": "Geovanny Mendoza",
"customerEmail": "geo@barranquillajug.com",
"customerPhone": "+57 300 1234567",
"deliveryAddress": "Calle 72 #45-10, Barranquilla"
}'- Identificar las dependencias cruzadas en
OrderService - Ver que
OrderEventsInventoryHandleres letra muerta - Contar las responsabilidades de
GlobalExceptionHandler
- Reorganizar a package-by-feature (
catalog/,orders/,inventory/,common/) - Agregar
spring-modulith-starter-corealpom.xml - Crear
ModularityTesty ejecutarlo (va a fallar) - Crear
common/package-info.javacon@ApplicationModule(type = OPEN) - Crear
CatalogApicomo API pública de catalog - Actualizar
OrderServicepara usarCatalogApi - Mover
OrderCreatedEventa la raíz deorders/ -
ModularityTestpasa ✅
- Agregar
orders/package-info.javaconallowedDependencies = {"catalog"} - Demostrar violación circular y resolverla
-
ModularityTestpasa con todas las reglas ✅
- Crear
catalog.products(write) ycatalog.product_views(read) en Flyway - Implementar
ProductEntity(command) yProductView(query) - Implementar
CatalogEventsconsealed interface - Implementar
CatalogEventHandlercon@ApplicationModuleListener
- Agregar
spring-modulith-starter-jdbcyspring-modulith-events-amqp - Agregar
@ExternalizedaOrderCreatedEvent - Actualizar
OrderEventsInventoryHandlera@ApplicationModuleListener - Verificar
event_publicationen PostgreSQL y mensajes en RabbitMQ
- Crear
@ApplicationModuleTestpara catalog - Crear
@ApplicationModuleTest+@MockitoBean CatalogApipara orders - Verificar eventos con
AssertablePublishedEvents - Crear test con
Scenariopara inventory
- Agregar
writesDocumentationSnippets()aModularityTest - Verificar
/actuator/modulith - Ver traza completa en Zipkin
El proyecto final de referencia está en bookstore-modulith/.
Úsalo solo cuando te quedes atascado, construir el camino es el aprendizaje.
Guía completa: geolabs-spring-modulith.github.io