Skip to main content

Level 5: Professional Workflows (Context & Decoupling)

By now, you have a robust parallel workflow with retries and timeouts. But in the real world, not all tasks are equal. Some are critical (like charging a payment), others are "best-effort" (like a marketing notification).

In this final level, we'll learn how to make our workflows truly intelligent using Optional Tasks and the Execution Context.


🛡️ Challenge 1: The Safety Net (Optional Tasks)​

Remember in Level 4 when our slow archiveAuditLog crashed the whole order because of a timeout? If auditing is important but not critical, we shouldn't stop the world.

Mission: Mark the audit task so its failure (or timeout) is ignored by the orchestrator.

  1. Modify your AuditTasks.java:
Update AuditTasks.java
@Component
@TaskHandler("audit")
public class AuditTasks {

@FlowTask(
id = "archiveAuditLog",
optional = true // 🛡️ Lifeboat! Failure won't stop the workflow
)
public Mono<Void> archiveAuditLog(ValidationResult result) {
return Mono.delay(Duration.ofMillis(1000)) // Still slow (1s)
.then();
}
}
  1. Run the demo. Even though the console shows a TimeoutException (because of our 500ms timeout), the workflow will now complete successfully.

🧠 Challenge 2: Deep Context (Decoupled Data Sharing)​

In a traditional Spring application, you might use ThreadLocal to pass data around. But in a Reactive System, threads are shared and reused constantly! You can't rely on ThreadLocal.

What is the ReactiveExecutionContext?​

It is the Shared Memory of your workflow. It follows the execution across different threads and provides:

  1. Thread-Safe Storage: Put and get data without worrying about concurrency.
  2. Implicit Results: When any task (like validateOrder) finishes, its result is automatically stored in the context using its ID as the key.

Type-Safe "Reverse Resolution"​

To get data from the context without using "magic strings" and without forcing producers and consumers to know each other, the Consumer declares its dependency.

Mission: Have the notifyResult task read the result from validateOrder in a 100% type-safe and decoupled way.

  1. Update your NotificationTasks.java:
Update NotificationTasks.java
@Component
@TaskHandler("notifications")
public class NotificationTasks {

// 🎯 DECLARE locally what we expect to find in the context.
// We don't need to import the OrderTasks class! We just define the "contract" we need.
private static final TaskDefinition<Order, ValidationResult> VALIDATION_RESULT =
TaskDefinition.of("validateOrder", Order.class, ValidationResult.class);

@FlowTask(id = "notifyResult")
public Mono<Void> notifyResult(Order order, ReactiveExecutionContext ctx) {

// 🔍 Retrieve the result produced by ANOTHER task using our local contract.
// ctx.get() uses the TaskDefinition to ensure type-safety (no casts needed).
ValidationResult res = ctx.get(VALIDATION_RESULT.outputKey())
.orElseThrow(() -> new IllegalStateException("Validation result not found!"));

if (res.isValid()) {
System.out.println("💌 Order valid! Sending notification...");
}
return Mono.empty();
}
}
💡 Pro Tip: Stop using Raw Strings

In large projects, manually writing "validateOrder" in multiple files is risky. A professional pattern is to use a Shared Enum or a centralized constants class for both the @FlowTask ID and the TaskDefinition identifier. This ensures uniformity and makes refactoring much easier!

  1. Why is this architecture powerful?
  • Zero Coupling: OrderTasks (the producer) has no idea who is consuming its data.
  • Explicit Dependencies: Just by looking at the constants in a task class, you know exactly what input it expects from the context.
  • Compile-time Safety: FlowForge handles the internal mapping based on IDs, but your code remains strictly typed.

🎉 Congratulations!​

You've mastered the core of FlowForge!

  1. Level 1: Bootstrapping.
  2. Level 2: Sequential logic.
  3. Level 3: Concurrency with Parallel/Fork.
  4. Level 4: Resilience with Retries/Timeouts.
  5. Level 5: Advanced Context & Decoupling.

You are now ready to build complex, reliable, and high-performance reactive workflows.


⬅ Level 4: Resilience | Back to Reference