Getting Started
This guide will walk you through setting up FlowForge and creating your first type-safe workflow.
1. Installation
FlowForge requires Java 17+ and is designed to be used as a library within your application.
Gradle
dependencies {
implementation("org.royada.flowforge:flowforge-spring-boot-starter:1.1.0")
}
Maven
<dependency>
<groupId>org.royada.flowforge</groupId>
<artifactId>flowforge-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
2. Define Task and Flow
Create your tasks and orchestration using annotations. FlowForge will handle the registration during Spring's startup scanning.
@Component
@TaskHandler
public class MyTasks {
@FlowTask(id = "calculate")
public Mono<Integer> calculate(Integer input) {
return Mono.just(input * 2);
}
}
@Component
@FlowWorkflow(id = "order-process")
public class OrderProcessWorkflow implements WorkflowDefinition {
@Override
public WorkflowExecutionPlan define(FlowDsl dsl) {
// High-level orchestration for the E-Commerce pipeline
return dsl.flow(MyTasks::calculate)
.build();
}
}
3. Executing the Workflow
Inject FlowForgeClient and trigger the execution.
@Service
public class MyService {
private final FlowForgeClient client;
public MyService(FlowForgeClient client) {
this.client = client;
}
public Mono<Integer> process() {
return client.executeResult("order-process", null)
.cast(Integer.class);
}
}
You can also execute with a client-side timeout:
client.execute("order-process", null, Duration.ofMillis(500));
Input contract:
- If your root task input type is non-
Void, provide a compatibleinputinexecute/executeResult. - If your roots are
Void, extra input is ignored (legacy-friendly behavior).
5. @TaskHandler Style (No Interface Boilerplate)
This is the primary recommended style in FlowForge.
@TaskHandler
class CustomerTasks {
@FlowTask(id = "producer")
Mono<Integer> producer(Void in) { ... }
@FlowTask(id = "formatter")
Mono<String> formatter(Integer in) { ... }
}
@Bean
WorkflowExecutionPlan plan(FlowDsl dsl) {
return dsl.flow(CustomerTasks::producer)
.then(CustomerTasks::formatter)
.build();
}
Sequential rule: the output type of each task is the input type of the next task in .then(...). If the types do not match, it fails at compile time.