Material de demostración para JConf 2025
Este repositorio contiene material de demostración para una presentación sobre Structured Concurrency y Scoped Values en Java 25.
Contenido:
- Aplicación Java con demos comparativos de diferentes enfoques de concurrencia
- Presentación LaTeX con transparencias de la charla, ver pdf
Propósito: Comparar y contrastar diferentes enfoques para manejar concurrencia en Java, desde programación reactiva tradicional (CompletableFuture) hasta las nuevas características de Java 25 (Structured Concurrency y Scoped Values).
Para efectos de demostración, supongamos un caso simplificado de procesamiento de compras.
Para validar la transacción hay varias cosas que se pueden hacer en paralelo, por ejemplo, la validación de los datos del comercio se pueden hacer en paralelo con la validación del consumidor.
En este flujo, asumimos que se necesita validar la tarjeta para obtener la cuenta que se usará para validar que tiene el saldo suficiente, el PIN asociado y la fecha de expiración. Cada parte la valida un servicio distinto.
Desafío: ¿Cómo coordinamos estas validaciones paralelas de forma clara, segura y eficiente?
Cada validación del flujo está implementada por un/pl servicio especializado. Estos servicios simulan operaciones de red con retrasos definidos en constantes para poder observar el comportamiento de los diferentes enfoques de concurrencia.
ℹ️ Nota: Si bien todos los servicios de validación del consumidor usan el mismo repositorio para simplificar el ejemplo. En la realidad es razonable que estén implementados por servicios diferentes, por ejemplo, uno de contabilidad para el saldo y un HSM para el PIN. Lo más forzado es la fecha de vencimiento, que normalmente se guardaría en la misma entidad que la info de la tarjeta. Un ejemplo más apropiado sería la validación de CVV, que involucra también al HSM pero se puede ejecutar en paralelo con el PIN, pues es un comando separado.
-
MerchantValidationService
- Propósito: Valida el comercio, en el ejemplo, que no contenga
BLOCKED - Retraso simulado: 500ms
- Implementación:
MerchantValidationService.java
- Propósito: Valida el comercio, en el ejemplo, que no contenga
-
CardValidationService
- Propósito: Valida el número de tarjeta y recupera los datos de la cuenta
- Retraso simulado: 100ms
- Retorna: Objeto
Cardcon saldo, PIN y fecha de vencimiento - Implementación:
CardValidationService.java
-
BalanceService
- Propósito: Valida saldo disponible e implementa bloqueo de fondos (two-phase commit)
- Retraso simulado: 600ms (el más lento)
- Mecanismo especial:
validate()- Valida el saldo y si tiene fondos, bloquea fondos agregándolos a transacciones pendientesreleaseAmount()- Libera fondos si otra validación falla, este método no tiene ningún efecto si no se bloqueó el fondo en la transacción actualtransfer()- Debita el saldo si todas las validaciones pasan
- Implementación:
BalanceService.java
-
ExpirationService
- Propósito: Valida que la tarjeta no esté vencida
- Retraso simulado: 200ms
- Implementación:
ExpirationService.java
-
PinValidationService
- Propósito: Valida que el PIN sea correcto
- Retraso simulado: 300ms
- Implementación:
PinValidationService.java
El modelo de datos está organizado en packages:
- DTOs: Request del cliente y su respuesta
- Domain Model:
Cardtiene la info necesaria para validar los datos de la tarjeta - Validation Results: Sealed interfaces para resultados (ValidationResult y CardValidationResult), en el caso de validación exitosa del número tarjeta, se devuelve la entidad con los datos de la tarjeta.
Los servicios de validación están organizados por interfaces:
- ValidationService: Para validaciones simples
- CardAwareValidationService: Para validaciones que requieren datos de tarjeta, se pasa la tarjeta obtenida por
CardValidationService
Este proyecto implementa el flujo anterior usando diferentes paradigmas de concurrencia. Los procesadores están organizados desde los más simples hasta los más sofisticados.
Todos los procesadores implementan el mismo flujo de validación pero con diferentes paradigmas de concurrencia:
Características de cada tipo:
-
Reactive Processors (CompletableFuture):
BasicReactivePaymentProcessor: Espera todas las validaciones conallOf()ReactiveWithExceptionsPaymentProcessor: Muestra que la idea de fallo temprano automático deStructuredPaymentProcessorno es tan simple para programación reactiva..FixedReactiveFailFastPaymentProcessor: Intenta implementar fallo temprano con programación reactiva, código más complejo.
-
Structured Processors (StructuredTaskScope):
StructuredPaymentProcessor: Espera todas con gestión automática del ciclo de vidaFailFastStructuredPaymentProcessor: Fail-fast automático al primer fallo, usando excepciones cuando los servicios no validan.
-
Scoped Processors (Scoped Values):
ScopedPaymentProcessor: Usa ScopedValue para propagación de contexto- Servicios scoped acceden al contexto sin necesidad de recibir todos los parámetros.
Estos procesadores esperan a que todas las validaciones terminen, independientemente de si alguna de las paralelas falla antes.
Enfoque: CompletableFuture (programación reactiva tradicional)
- Archivo:
BasicReactivePaymentProcessor.java - Qué hace: Usa
CompletableFuture.allOf()para ejecutar validaciones en paralelo - Demo CLI:
./gradlew demoReactive - Endpoint REST:
/api/reactive/basic
Ejemplo de código:
// BasicReactivePaymentProcessor.java (líneas 54-90)
CompletableFuture<ValidationResult> merchantValidation =
CompletableFuture.supplyAsync(() ->
merchantValidationService.validate(request));
CompletableFuture<Card> cardValidation =
CompletableFuture.supplyAsync(() ->
cardValidationService.validate(request))
.thenCompose(cardResult -> {
// Validaciones paralelas anidadas...
});
CompletableFuture.allOf(merchantValidation, cardValidation).join();Enfoque: Structured Concurrency (Java 25)
- Archivo:
StructuredPaymentProcessor.java - Qué hace: Usa
StructuredTaskScope.open()para organizar tareas en jerarquía - Demo CLI:
./gradlew demoStructured - Endpoint REST:
/api/structured/normal - Líneas clave: 63-97
Ejemplo de código:
// StructuredPaymentProcessor.java (líneas 63-97)
try (var globalScope = StructuredTaskScope.open()) {
Subtask<ValidationResult> merchantValidation =
globalScope.fork(() ->
merchantValidationService.validate(request));
Subtask<CardValidationResult> cardValidation =
globalScope.fork(() -> {
// Scope anidado para validaciones del consumidor...
});
globalScope.join(); // Espera a TODAS las subtareas
}Estos procesadores cancelan todas las tareas restantes tan pronto como detectan un fallo.
El procesador ReactiveWithExceptionsPaymentProcessor.java añade manejo de excepciones a la versión reactiva básica, pero no soluciona el problema fundamental: cuando una tarea lanza una excepción, las demás tareas continúan ejecutándose hasta completarse. No hay cancelación automática.
- Demo CLI:
./gradlew demoReactiveExceptions - Endpoint REST:
/api/reactive/with-exceptions
Enfoque: CompletableFuture con fail-fast MANUAL
- Archivo:
FixedReactiveFailFastPaymentProcessor.java - Qué hace: Implementa fail-fast manualmente con CompletableFuture
- Complejidad: Requiere ~80 líneas de código de coordinación para lograr cancelación al primer fallo
- Demo CLI:
./gradlew demoFixedReactiveFailFast - Endpoint REST:
/api/reactive/fail-fast
Enfoque: Structured Concurrency con fail-fast AUTOMÁTICO
- Archivo:
FailFastStructuredPaymentProcessor.java - Qué hace: Cancela automáticamente todas las tareas restantes al primer fallo
- Ventaja: La cancelación es automática, sin código de coordinación manual
- Demo CLI:
./gradlew demoStructuredFailFast - Endpoint REST:
/api/structured/fail-fast - Líneas clave: 67-103
Ejemplo de código:
// FailFastStructuredPaymentProcessor.java (líneas 67-103)
try (var globalScope = StructuredTaskScope.open()) {
globalScope.fork(() -> {
ValidationResult result = merchantValidationService.validate(request);
if (result instanceof ValidationResult.Failure(String msg)) {
throw new ValidationException(msg); // → Cancela automáticamente el resto
}
});
globalScope.join();
} catch (StructuredTaskScope.FailedException e) {
// Manejo automático de fallo
}Comparación de demos:
# Comparar comportamiento await-all vs fail-fast
./gradlew demoCompare
# Comparar específicamente el comportamiento en caso de fallo
./gradlew demoCompareFailureUbicación: demo-structured-concurrency/src/main/java/com/example/scopedvalues/
Los ScopedValue permiten propagar contexto a través de hilos virtuales sin necesidad de pasar parámetros explícitamente, evitando el "parameter drilling" (pasar el mismo parámetro a través de múltiples capas de llamadas).
- Archivo:
ScopedPaymentProcessor.java - Qué hace: Usa
ScopedValuepara establecer contexto que los servicios pueden acceder sin recibir parámetros - Demo CLI:
./gradlew demoScopedValues - Endpoint REST:
/api/scoped/fail-fast - Líneas clave: 21-22 (definición de ScopedValues), 44-111 (uso)
Los siguientes servicios acceden al contexto vía ScopedValue.get():
ScopedCardValidationService.javaScopedBalanceService.javaScopedExpirationService.javaScopedPinValidationService.javaScopedMerchantValidationService.java
// ScopedPaymentProcessor.java (líneas 21-22)
// Definición de los ScopedValues
public static final ScopedValue<TransactionRequest> TRANSACTION_REQUEST =
ScopedValue.newInstance();
public static final ScopedValue<Card> CARD =
ScopedValue.newInstance();
// Establecer contexto (líneas 44-68)
ScopedValue.where(TRANSACTION_REQUEST, request).call(() -> {
try (var globalScope = StructuredTaskScope.open()) {
// Los servicios acceden vía TRANSACTION_REQUEST.get()
createValidationTask(merchantValidationService, globalScope);
// ...
}
});
// ScopedBalanceService.java - Acceso al contexto sin parámetros
ValidationResult validate() {
return super.validate(
ScopedPaymentProcessor.TRANSACTION_REQUEST.get(),
ScopedPaymentProcessor.CARD.get()
);
}cd demo-structured-concurrency
# Reactive Programming
./gradlew demoReactive
./gradlew demoReactiveExceptions
./gradlew demoFixedReactiveFailFast
# Structured Concurrency
./gradlew demoStructured
./gradlew demoStructuredFailFast
# Scoped Values
./gradlew demoScopedValues
# Comparaciones
./gradlew demoCompare
./gradlew demoCompareFailurecd demo-structured-concurrency
./gradlew quarkusDev
# Abrir en navegador: http://localhost:8080curl -X POST http://localhost:8080/api/structured/fail-fast \
-H "Content-Type: application/json" \
-d '{
"cardNumber": "1234-5678-9012-3456",
"expirationDate": "1225",
"pin": "1234",
"amount": 100.00,
"merchant": "Demo Store"
}'Durante la presentación se utilizó una interfaz web interactiva para demostrar las diferencias entre los enfoques de concurrencia en tiempo real.
Para reproducir las demos: http://localhost:8080 (después de ejecutar ./gradlew quarkusDev)
La interfaz consta de dos paneles principales:
Panel Izquierdo - Tarjetas de Prueba:
- Lista de tarjetas precargadas con diferentes escenarios
- Cada tarjeta muestra: número parcial, balance, descripción
- Botones para usar, clonar o borrar tarjetas
Panel Derecho - Formulario de Transacción:
- Campos para datos de la transacción
- Dos selectores independientes de procesadores
- Permite comparar cualquier combinación de enfoques
Tarjetas disponibles:
4532...9012- Válida ($5000) para transacciones exitosas4111...1111- Para demo de Scoped Values ($2000)5555...2222- Expirada ($1000) para demo de fail-fast9876...7654- Balance insuficiente ($500)
Esta fue la demostración principal de la presentación, mostrando cómo structured concurrency cancela automáticamente tareas cuando detecta un fallo.
Configuración utilizada:
- Tarjeta
5555...2222(expirada diciembre 2023) - Procesador 1: "Reactive con Excepciones"
- Procesador 2: "Structured Fail-Fast"
Resultados observados:
- Reactive: 707ms - Esperó a que todas las validaciones completen
- Structured: 322ms - Canceló automáticamente al detectar expiración
- Mejora: 54% más rápido (385ms ahorro)
¿Por qué la diferencia?
El procesador reactive usa CompletableFuture.allOf() que espera a que todas las tareas completen:
- Expiración falla a ~200ms
- Pero continúa ejecutando: balance (600ms), PIN (300ms), merchant (500ms)
- Total: ~707ms
El procesador structured usa StructuredTaskScope con cancelación automática:
- Expiración falla a ~200ms
- Cancela inmediatamente las tareas restantes
- Total: ~322ms
Conclusión: Cancelación automática sin código adicional
Esta demostración mostró que structured concurrency mantiene el rendimiento cuando todo funciona correctamente.
Configuración:
- Tarjeta válida
4532...9012 - Monto: $100
- Procesadores: Basic (reactive) vs Normal (structured)
Resultados:
- Reactive: 704ms
- Structured: 710ms
- Diferencia: 1% (variación normal)
Conclusión: Structured concurrency ofrece código más simple sin sacrificar rendimiento en el caso exitoso.
Durante la presentación se demostró cómo Scoped Values permite propagar contexto automáticamente sin necesidad de pasar parámetros explícitos ("parameter drilling").
Característica destacada: Scoped Values es ESTABLE en Java 25 (a diferencia de Structured Concurrency que está en 5ta preview).
Para el demo fail-fast:
- Ejecutar
./gradlew quarkusDev - Abrir http://localhost:8080
- Hacer click en tarjeta
5555...2222→ botón "Usar" - Seleccionar procesadores: "Con Excepciones" vs "Fail-Fast Automático"
- Click en "⚖️ Comparar Procesadores"
- Observar la diferencia de tiempo y el banner de mejora
Para el demo exitoso:
- Usar tarjeta
4532...9012 - Seleccionar procesadores: "Basic" vs "Normal"
- Observar rendimiento comparable
Para Scoped Values:
- Usar cualquier tarjeta válida
- Seleccionar "Fail-Fast con Scoped Values"
- Click en "⚡ Ejecutar Único"
- Observar logs en terminal mostrando propagación de contexto
⚠️ Nota ImportanteEl código de soporte (controladores REST, gestión de tarjetas, interfaz web) fue desarrollado mayormente con asistencia de IA para acelerar la creación de las demos. Este código no representa una recomendación de arquitectura para sistemas de producción.
Se priorizó la simplicidad y velocidad de desarrollo sobre las mejores prácticas empresariales. Para sistemas financieros reales se requerirían patrones más robustos: persistencia real, seguridad, auditoría, manejo de errores exhaustivo, etc.
El código relevante para la presentación son los procesadores (
reactive/,structured/,scopedvalues/) y los servicios de validación.
La aplicación expone endpoints REST que conectan la interfaz web con los procesadores:
- StructuredPaymentResource - Endpoints
/api/structured/*(normal y fail-fast) - ReactivePaymentResource - Endpoints
/api/reactive/*(basic, con excepciones, fail-fast) - ScopedPaymentResource - Endpoint
/api/scoped/fail-fast - ComparisonResource - Endpoint
/api/compare(comparación lado a lado) - CardResource - CRUD de tarjetas
/api/cards/* - BalanceResource - Consulta de saldos
/api/balance/*
Los controladores actúan como thin facade: delegación directa a procesadores sin lógica de negocio en la capa REST.
Repositorio y Fixtures:
- CardRepository - Almacenamiento en memoria (ConcurrentHashMap), no es base de datos real
- DemoCards - Tarjetas precargadas para escenarios de demo
Utilidades:
- DemoUtil - Simulación de latencia de red (
simulateNetworkDelay()) - JacksonConfig - Configuración JSON pretty-print
Interfaz Web (index.html)
↓ (fetch API)
REST Controllers
↓ (delegación)
Payment Processors (reactive/structured/scoped)
↓ (orquestación)
Validation Services
↓ (consulta)
CardRepository (in-memory)
Todos los modelos son records de Java (inmutables, thread-safe):
- TransactionRequest - Request del cliente
- TransactionResult - Response con métricas de rendimiento
- Card - Entidad de tarjeta
- ValidationResult - Sealed interface para resultados
- CardValidationResult - Resultado de validación de tarjeta
- Java 25 con
--enable-previewhabilitado - Gradle (wrapper incluido en el proyecto)
El proyecto ya está configurado para habilitar las preview features automáticamente en build.gradle.
jconf-structured-concurrency/
├── README.md # Este archivo
│
├── demo-structured-concurrency/ # Aplicación Java
│ ├── src/main/java/com/example/
│ │ ├── reactive/ # Procesadores CompletableFuture
│ │ │ ├── BasicReactivePaymentProcessor.java
│ │ │ ├── ReactiveWithExceptionsPaymentProcessor.java
│ │ │ └── FixedReactiveFailFastPaymentProcessor.java
│ │ │
│ │ ├── structured/ # Procesadores StructuredTaskScope
│ │ │ ├── StructuredPaymentProcessor.java
│ │ │ └── FailFastStructuredPaymentProcessor.java
│ │ │
│ │ ├── scopedvalues/ # Procesadores ScopedValue
│ │ │ ├── ScopedPaymentProcessor.java
│ │ │ └── Scoped*Service.java
│ │ │
│ │ ├── services/ # Servicios de validación
│ │ ├── rest/ # REST controllers
│ │ ├── model/ # Records (TransactionRequest, etc)
│ │ └── demos/ # Clases ejecutables para demos CLI
│ │
│ ├── src/main/resources/
│ │ └── META-INF/resources/
│ │ └── index.html # Interfaz websi
│ │
│ └── build.gradle
│
└── presentacion/ # Presentación LaTeX
├── structured-concurrency-presentation.tex
├── Makefile
└── sections/
🚧 Sección a desarrollar si hay interés
El caso de uso presentado simplifica el procesamiento de transacciones de tarjetas para fines didácticos. Esta sección explicará superficialmente cómo funcionan las transacciones reales y cómo se mapean los conceptos de la demo.
Temas planeados:
- Actores principales: POS (Terminal punto de venta), Adquiriente, Red de tarjetas (Visa/Mastercard), Emisor (banco)
- Mensajería ISO 8583: Formato estándar para transacciones financieras
- Flujo simplificado: POS → Adquiriente → Red → Emisor → respuesta reversa
- Mapeo con la demo:
- Cómo los servicios de validación se corresponden con verificaciones reales del emisor
- Dónde encajan las validaciones de comercio (adquiriente)
- Qué representa el balance service en el contexto del emisor
- Cómo el two-phase commit se relaciona con reversiones reales
Pendiente de completar en una próxima actualización.
Material educativo para JConf 2025.
Autor: Andrés Alcarraz Contacto: [email protected] LinkedIn: https://www.linkedin.com/in/andresalcarraz/







