Real-World Examples
The following examples demonstrate how to use FlowForge in production scenarios.
1. User Onboarding Flow (Annotation-First)
Context: When a user signs up, we need to create their account, send a welcome email, and provision a free tier resource.
@Configuration
public class OnboardingFlow {
@TaskHandler("onboarding")
static class OnboardingTasks {
@FlowTask(id = "createAccount")
Mono<User> createAccount(Void in) { ... }
@FlowTask(id = "sendWelcomeEmail")
Mono<Void> sendWelcomeEmail(User in) { ... }
@FlowTask(id = "provisionResource")
Mono<Void> provisionResource(User in) { ... }
@FlowTask(id = "notifySlackAdmin")
Mono<Void> notifySlackAdmin(Object in, ReactiveExecutionContext ctx) { ... }
}
@FlowWorkflow(id = "user-onboarding")
@Bean
public WorkflowExecutionPlan plan(FlowDsl dsl) {
return dsl.flow(OnboardingTasks::createAccount)
.fork(
branch -> branch.then(OnboardingTasks::sendWelcomeEmail),
branch -> branch.then(OnboardingTasks::provisionResource)
)
.join(OnboardingTasks::notifySlackAdmin)
.build();
}
}
Behavior:
CREATE_ACCOUNTruns first.- Once finished,
SEND_WELCOME_EMAILandPROVISION_RESOURCErun in parallel. NOTIFY_SLACK_ADMINonly runs after both parallel branches succeed.ReactiveExecutionContextis injected only in tasks that need it (notifySlackAdminin this example).
3. Data Transformation Pipeline
Context: Ingesting a raw JSON string, validating it, transforming it to a canonical format, and saving it.
dsl.startTyped(VALIDATE_JSON)
.then(MAP_TO_CANONICAL)
.then(PERSIST)
.build();
5. Retry Exhausted Example
Context: A downstream dependency remains unavailable beyond the retry budget.
TaskDescriptor descriptor = new TaskDescriptor(task, RetryPolicy.fixed(3));
Observed behavior:
- The task runs once and then retries up to 3 additional attempts.
- If all attempts fail, the final error is propagated and the branch fails.
- Dependent tasks are not executed unless modeled as optional/fallback paths.
7. Optional Tasks Example
Context: A best-effort notification should not block order finalization.
@TaskHandler
class CheckoutTasks {
@FlowTask(id = "chargeCard")
Mono<String> chargeCard(Void in) { ... }
@FlowTask(id = "sendReceipt", optional = true)
Mono<Void> sendReceipt(String paymentId) { ... }
@FlowTask(id = "finalizeOrder")
Mono<String> finalizeOrder(String paymentId) { ... }
}
dsl.flow(CheckoutTasks::chargeCard)
.then(CheckoutTasks::sendReceipt)
.then(CheckoutTasks::finalizeOrder)
.build();
Observed behavior:
- If
sendReceiptfails, it can be skipped because it is optional. - Critical tasks continue according to dependency constraints.
- Use optional tasks for non-critical side effects such as notifications/analytics.
-
-
🚀 Full Sample Application
- For a complete, runnable application demonstrating all these features together, check out our official Basic Demo:
- FlowForge Samples on GitHub