|
| 1 | +# CNCF Serverless Workflow SDK Java — Fluent DSL |
| 2 | + |
| 3 | +> A programmatic, type‑safe Java API for building and running Serverless Workflows (and agentic workflows) without writing YAML. |
| 4 | +
|
| 5 | +--- |
| 6 | + |
| 7 | +## 📦 Modules |
| 8 | + |
| 9 | +| Module | Purpose | |
| 10 | +| -------------- | --------------------------------------------------------------------------------------------- | |
| 11 | +| **spec** | Core DSL implementing the [Serverless Workflow Specification](https://github.com/serverlessworkflow/specification). Purely compliant fluent API. | |
| 12 | +| **func** | Java‑centric “functional” DSL on top of **spec**: adds `Function<>`/`Predicate<>` support, `callFn` for Java method calls, and richer flow controls. | |
| 13 | +| **agentic** | **Experimental** proof‑of‑concept DSL built on **func** for LangChain4j agentic workflows: `agent`, `sequence`, `loop`, `parallel`, etc. | |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## 🔧 Getting Started |
| 18 | + |
| 19 | +Add the modules you need to your Maven `pom.xml` (replace versions as appropriate): |
| 20 | + |
| 21 | +```xml |
| 22 | +<!-- |
| 23 | + Replace ${version.io.serverlessworkflow} with the actual released version: |
| 24 | + https://github.com/serverlessworkflow/sdk-java/releases |
| 25 | +--> |
| 26 | +<dependency> |
| 27 | + <groupId>io.serverlessworkflow</groupId> |
| 28 | + <artifactId>serverlessworkflow-fluent-spec</artifactId> |
| 29 | + <version>${version.io.serverlessworkflow}</version> |
| 30 | +</dependency> |
| 31 | +<dependency> |
| 32 | + <groupId>io.serverlessworkflow</groupId> |
| 33 | + <artifactId>serverlessworkflow-fluent-func</artifactId> |
| 34 | + <version>${version.io.serverlessworkflow}</version> |
| 35 | +</dependency> |
| 36 | +<dependency> <!-- optional, experimental --> |
| 37 | + <groupId>io.serverlessworkflow</groupId> |
| 38 | + <artifactId>serverlessworkflow-fluent-agentic</artifactId> |
| 39 | + <version>${version.io.serverlessworkflow}</version> |
| 40 | +</dependency> |
| 41 | +``` |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## 📖 Module Reference |
| 46 | + |
| 47 | +### 1. Spec Fluent |
| 48 | + |
| 49 | +Fully compliant with the CNCF Serverless Workflow spec.\ |
| 50 | +Use it when you want a 1:1 mapping of the YAML DSL in Java. |
| 51 | + |
| 52 | +```java |
| 53 | +import io.serverlessworkflow.api.types.Workflow; |
| 54 | +import io.serverlessworkflow.fluent.spec.WorkflowBuilder; |
| 55 | + |
| 56 | +Workflow wf = WorkflowBuilder |
| 57 | + .workflow("flowDo") |
| 58 | + .tasks(tasks -> |
| 59 | + tasks |
| 60 | + .set("initCtx", "$.foo = 'bar'") |
| 61 | + .forEach("item", f -> f |
| 62 | + .each("item") |
| 63 | + .at("$.list") |
| 64 | + ) |
| 65 | + ) |
| 66 | + .build(); |
| 67 | +``` |
| 68 | + |
| 69 | +> [!NOTE] |
| 70 | +> We rename reserved keywords (`for`, `do`, `if`, `while`, `switch`, `try`) to safe identifiers (`forEach`, `tasks`, `when`, etc.). |
| 71 | +
|
| 72 | +--- |
| 73 | + |
| 74 | +### 2. Func Fluent |
| 75 | + |
| 76 | +A Java‑first DSL that builds on **spec**, adding: |
| 77 | + |
| 78 | +- `callFn`: invoke arbitrary Java `Function<>` handlers |
| 79 | +- `Predicate<>` **guards** via `when(Predicate)` |
| 80 | +- Built‑in `Function`/`Predicate` support instead of JQ expressions |
| 81 | + |
| 82 | +```java |
| 83 | +import io.serverlessworkflow.api.types.Workflow; |
| 84 | +import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder; |
| 85 | + |
| 86 | +Workflow wf = FuncWorkflowBuilder |
| 87 | + .workflow("callJavaFlow") |
| 88 | + .tasks(tasks -> |
| 89 | + tasks.callFn("invokeHandler", call -> call |
| 90 | + // e.g. call.className("com.acme.Handler") |
| 91 | + // .method("handle") |
| 92 | + // .arg("key", "value") |
| 93 | + .function(ctx -> { |
| 94 | + // your code here |
| 95 | + }) |
| 96 | + ) |
| 97 | + ) |
| 98 | + .build(); |
| 99 | +``` |
| 100 | + |
| 101 | +> [!WARNING] |
| 102 | +> The **func** DSL is *not* spec‑compliant. It adds Java‑specific tasks and control‑flow extensions for in‑JVM execution. |
| 103 | +
|
| 104 | +--- |
| 105 | + |
| 106 | +### 3. Agentic Fluent *(Experimental)* |
| 107 | + |
| 108 | +Built on **func** for LangChain4j agentic workflows. Adds: |
| 109 | + |
| 110 | +- `agent(instance)`: invoke a LangChain4j agent |
| 111 | +- `sequence(...)`: run agents in order |
| 112 | +- `loop(cfg)`: retry or repeated agent calls |
| 113 | +- `parallel(...)`: fork agent calls concurrently |
| 114 | + |
| 115 | +```java |
| 116 | +import io.serverlessworkflow.api.types.Workflow; |
| 117 | +import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder; |
| 118 | + |
| 119 | +var scorer = AgentsUtils.newMovieExpert(); |
| 120 | +var editor = AgentsUtils.newMovieExpert(); |
| 121 | + |
| 122 | +Workflow wf = AgentWorkflowBuilder |
| 123 | + .workflow("retryFlow") |
| 124 | + .tasks(tasks -> tasks.loop( |
| 125 | + "reviewLoop", |
| 126 | + loop -> loop |
| 127 | + .maxIterations(5) |
| 128 | + .exitCondition(c -> c.readState("score", 0).doubleValue() > 0.75) |
| 129 | + .subAgents("reviewer", scorer, editor) |
| 130 | + )) |
| 131 | + .build(); |
| 132 | +``` |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +## 🚀 Real‑World Example: Order Fulfillment |
| 137 | + |
| 138 | +```java |
| 139 | +import io.serverlessworkflow.api.types.Workflow; |
| 140 | +import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder; |
| 141 | +import java.util.function.Predicate; |
| 142 | + |
| 143 | +public class OrderFulfillment { |
| 144 | + |
| 145 | + static class InventoryAgent { /* … */ } |
| 146 | + static class NotificationAgent { /* … */ } |
| 147 | + static class ShippingAgent { /* … */ } |
| 148 | + |
| 149 | + public Workflow buildWorkflow() { |
| 150 | + |
| 151 | + Predicate<Object> inventoryOk = state -> |
| 152 | + Boolean.TRUE.equals(((java.util.Map<?,?>) state).get("inventoryAvailable")); |
| 153 | + |
| 154 | + return AgentWorkflowBuilder |
| 155 | + .workflow("OrderFulfillment") |
| 156 | + .tasks(tasks -> tasks |
| 157 | + |
| 158 | + // 1. initialize state |
| 159 | + .set("init", s -> s.expr("$.orderId = '.input.oriderId'")) |
| 160 | + |
| 161 | + // 2. check inventory |
| 162 | + .agent("checkInventory", new InventoryAgent()) |
| 163 | + |
| 164 | + // 3. pull result into a flag |
| 165 | + .set("inventoryAvailable", s -> s.expr("$.checkInventory.available")) |
| 166 | + |
| 167 | + // 4. retry until in stock (max 3 attempts) |
| 168 | + .loop("retryIfOutOfStock", loop -> loop |
| 169 | + .maxIterations(3) |
| 170 | + .exitCondition(inventoryOk) |
| 171 | + .subAgents("inventoryChecker", new InventoryAgent()) |
| 172 | + ) |
| 173 | + |
| 174 | + // 5. notify systems in parallel |
| 175 | + .parallel("notifyAll", |
| 176 | + new NotificationAgent(), |
| 177 | + new ShippingAgent() |
| 178 | + ) |
| 179 | + |
| 180 | + // 6. mark order complete |
| 181 | + .set("complete", s -> s.expr("$.status = 'COMPLETED'")) |
| 182 | + ) |
| 183 | + .build(); |
| 184 | + } |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## 🛠️ Next Steps & Roadmap |
| 191 | + |
| 192 | +- **Error handling**: retries, back‑off, `onError` handlers |
| 193 | +- **Timers & delays**: `wait`, per‑task `timeout` |
| 194 | +- **Sub‑workflows** & composition: call one workflow from another |
| 195 | +- **Event tasks**: `onEvent`, `sendEvent` |
| 196 | +- **Human‑in‑the‑Loop**: approval/notification steps |
| 197 | + |
| 198 | +Contributions welcome! Check out our [CONTRIBUTING.md](../CONTRIBUTING.md) and join the CNCF Slack channel for **Serverless Workflow**. |
| 199 | + |
| 200 | +--- |
| 201 | + |
| 202 | +## 📜 License |
| 203 | + |
| 204 | +Apache 2.0 © Serverless Workflow Authors |
0 commit comments