diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java index 4199811e..8e53c7ad 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java @@ -18,30 +18,183 @@ import io.serverlessworkflow.fluent.spec.spi.DoFluent; import java.util.function.Consumer; +/** + * Builder for creating sequential task execution workflows using the "do" construct. + * + *

The {@code DoTaskBuilder} provides a fluent API for defining a sequence of tasks that execute + * in order. It serves as the primary mechanism for composing workflow logic, supporting all task + * types defined in the Serverless Workflow specification including HTTP calls, event emission, + * conditional branching, loops, and parallel execution. + * + *

This builder implements the "do" task pattern where tasks are executed sequentially unless + * explicitly configured for parallel execution (via {@link #fork(String, Consumer)}). Each task + * method returns the builder instance, enabling fluent method chaining. + * + *

Usage Example

+ *
{@code
+ * DoTaskBuilder tasks = new DoTaskBuilder(0)
+ *     .set("initialize", set -> set
+ *         .set("counter", "0")
+ *         .set("status", "started"))
+ *     .http("fetch-data", http -> http
+ *         .call(call -> call
+ *             .with(endpoint -> endpoint
+ *                 .uri("https://api.example.com/data"))
+ *             .method("GET")))
+ *     .switchCase("process-result", sw -> sw
+ *         .switchItem(item -> item
+ *             .when("${ .response.status == 200 }")
+ *             .then("success-handler"))
+ *         .switchItem(item -> item
+ *             .when("${ .response.status >= 400 }")
+ *             .then("error-handler")))
+ *     .emit("notify", emit -> emit
+ *         .event(event -> event
+ *             .with(props -> props
+ *                 .type("workflow.completed")
+ *                 .source("workflow-engine"))));
+ * }
+ * + *

Supported Task Types

+ * + * + *

Task Naming

+ *

Each task requires a unique name within the workflow. The builder uses an offset mechanism + * to support auto-generated names when tasks are added incrementally across multiple builder + * invocations. + * + *

Thread Safety

+ *

This builder is not thread-safe. Task definitions should be constructed + * using a single thread. For concurrent workflow creation, use separate builder instances. + * + * @see BaseDoTaskBuilder for inherited task configuration methods + * @see TaskItemListBuilder for the underlying task list management + * @see DoFluent for the fluent task definition interface + * @see io.serverlessworkflow.api.types.DoTask for the resulting task model + * @since 1.0.0 + */ public class DoTaskBuilder extends BaseDoTaskBuilder implements DoFluent { + /** + * Constructs a new DoTaskBuilder with the specified task naming offset. + * + *

The offset ensures that auto-generated task names continue sequentially when tasks are + * added across multiple builder invocations. + * + * @param listSizeOffset the current number of tasks in the workflow, used for name generation + */ DoTaskBuilder(int listSizeOffset) { super(new TaskItemListBuilder(listSizeOffset)); } + /** + * Returns this builder instance for method chaining. + * + *

This method is part of the builder pattern implementation, enabling fluent method chaining + * in the type hierarchy. + * + * @return this DoTaskBuilder instance + */ @Override protected DoTaskBuilder self() { return this; } + /** + * Adds an HTTP call task to the workflow. + * + *

Creates a task that makes HTTP/REST API calls with configurable methods, headers, body, + * and authentication. Supports all standard HTTP methods (GET, POST, PUT, DELETE, etc.). + * + *

Example: + *

{@code
+   * builder.http("fetch-user", http -> http
+   *     .call(call -> call
+   *         .with(endpoint -> endpoint
+   *             .uri("https://api.example.com/users/${.userId}"))
+   *         .method("GET")
+   *         .headers(headers -> headers
+   *             .header("Accept", "application/json"))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the HTTP call task + * @return this builder for method chaining + * @see CallHttpTaskBuilder for HTTP task configuration options + */ @Override public DoTaskBuilder http(String name, Consumer itemsConfigurer) { this.listBuilder().http(name, itemsConfigurer); return this; } + /** + * Adds an event emission task to the workflow. + * + *

Creates a task that publishes events to configured event channels. Events can be used + * for workflow communication, notifications, or triggering other workflows. + * + *

Example: + *

{@code
+   * builder.emit("notify-completion", emit -> emit
+   *     .event(event -> event
+   *         .with(props -> props
+   *             .type("order.completed")
+   *             .source("order-service")
+   *             .data("${ .orderResult }"))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the emit task + * @return this builder for method chaining + * @see EmitTaskBuilder for event emission configuration + */ @Override public DoTaskBuilder emit(String name, Consumer itemsConfigurer) { this.listBuilder().emit(name, itemsConfigurer); return this; } + /** + * Adds a for-each iteration task to the workflow. + * + *

Creates a task that iterates over a collection, executing a set of tasks for each item. + * Supports both sequential and parallel iteration modes. + * + *

Example: + *

{@code
+   * builder.forEach("process-items", forEach -> forEach
+   *     .for_("item")
+   *     .in("${ .items }")
+   *     .do_(tasks -> tasks
+   *         .http("process-item", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://api.example.com/process"))
+   *                 .method("POST")
+   *                 .body("${ .item }")))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the for-each task + * @return this builder for method chaining + * @see ForEachTaskBuilder for iteration configuration + */ @Override public DoTaskBuilder forEach( String name, Consumer> itemsConfigurer) { @@ -49,48 +202,220 @@ public DoTaskBuilder forEach( return this; } + /** + * Adds a fork task for parallel execution to the workflow. + * + *

Creates a task that executes multiple branches concurrently. Supports different + * competition modes (wait for all, wait for first, wait for N) and branch configuration. + * + *

Example: + *

{@code
+   * builder.fork("parallel-processing", fork -> fork
+   *     .compete(CompetitionMode.ALL_OF)
+   *     .branch("branch-1", branch -> branch
+   *         .http("call-service-1", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://service1.example.com")))))
+   *     .branch("branch-2", branch -> branch
+   *         .http("call-service-2", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://service2.example.com"))))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the fork task + * @return this builder for method chaining + * @see ForkTaskBuilder for parallel execution configuration + */ @Override public DoTaskBuilder fork(String name, Consumer itemsConfigurer) { this.listBuilder().fork(name, itemsConfigurer); return this; } + /** + * Adds an event listening task to the workflow. + * + *

Creates a task that waits for one or more events before continuing execution. Supports + * various consumption strategies (all, any, one) and event filtering. + * + *

Example: + *

{@code
+   * builder.listen("wait-for-approval", listen -> listen
+   *     .to(to -> to
+   *         .one(one -> one
+   *             .with(props -> props
+   *                 .type("approval.granted")
+   *                 .source("approval-service")))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the listen task + * @return this builder for method chaining + * @see ListenTaskBuilder for event listening configuration + */ @Override public DoTaskBuilder listen(String name, Consumer itemsConfigurer) { this.listBuilder().listen(name, itemsConfigurer); return this; } + /** + * Adds an error raising task to the workflow. + * + *

Creates a task that explicitly raises an error, useful for validation failures or + * exceptional conditions that should halt workflow execution. + * + *

Example: + *

{@code
+   * builder.raise("validation-error", raise -> raise
+   *     .error(error -> error
+   *         .type("ValidationError")
+   *         .status(400)
+   *         .title("Invalid Input")
+   *         .detail("${ .validationMessage }")));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the raise task + * @return this builder for method chaining + * @see RaiseTaskBuilder for error raising configuration + */ @Override public DoTaskBuilder raise(String name, Consumer itemsConfigurer) { this.listBuilder().raise(name, itemsConfigurer); return this; } + /** + * Adds a variable assignment task to the workflow. + * + *

Creates a task that sets one or more workflow variables using expressions. This is the + * primary mechanism for manipulating workflow state. + * + *

Example: + *

{@code
+   * builder.set("calculate-total", set -> set
+   *     .set("subtotal", "${ .items | map(.price) | add }")
+   *     .set("tax", "${ .subtotal * 0.1 }")
+   *     .set("total", "${ .subtotal + .tax }"));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the set task + * @return this builder for method chaining + * @see SetTaskBuilder for variable assignment configuration + */ @Override public DoTaskBuilder set(String name, Consumer itemsConfigurer) { this.listBuilder().set(name, itemsConfigurer); return this; } + /** + * Adds a simple variable assignment task to the workflow. + * + *

Convenience method for setting a single variable with an expression. Equivalent to + * calling {@link #set(String, Consumer)} with a single assignment. + * + *

Example: + *

{@code
+   * builder.set("initialize-counter", "${ 0 }");
+   * }
+ * + * @param name the unique name for this task + * @param expr the expression to evaluate and assign + * @return this builder for method chaining + * @see #set(String, Consumer) for multiple assignments + */ @Override public DoTaskBuilder set(String name, String expr) { this.listBuilder().set(name, expr); return this; } + /** + * Adds a wait/delay task to the workflow. + * + *

Creates a task that pauses workflow execution for a specified duration or until a + * specific time. Useful for implementing delays, rate limiting, or scheduled execution. + * + *

Example: + *

{@code
+   * builder.wait("delay-processing", wait -> wait
+   *     .duration("PT30S")); // Wait 30 seconds
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the wait task + * @return this builder for method chaining + * @see WaitTaskBuilder for wait configuration + */ @Override public DoTaskBuilder wait(String name, Consumer itemsConfigurer) { this.listBuilder().wait(name, itemsConfigurer); return this; } + /** + * Adds a conditional branching task to the workflow. + * + *

Creates a task that evaluates conditions and executes different branches based on the + * results. Supports multiple conditions with optional default case. + * + *

Example: + *

{@code
+   * builder.switchCase("route-by-status", sw -> sw
+   *     .switchItem(item -> item
+   *         .when("${ .status == 'approved' }")
+   *         .then("process-approval"))
+   *     .switchItem(item -> item
+   *         .when("${ .status == 'rejected' }")
+   *         .then("process-rejection"))
+   *     .switchItem(item -> item
+   *         .then("process-pending"))); // Default case
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the switch task + * @return this builder for method chaining + * @see SwitchTaskBuilder for conditional branching configuration + */ @Override public DoTaskBuilder switchCase(String name, Consumer itemsConfigurer) { this.listBuilder().switchCase(name, itemsConfigurer); return this; } + /** + * Adds an error handling task to the workflow. + * + *

Creates a task that wraps other tasks with error handling logic. Supports catching + * specific error types, retry policies, and error recovery actions. + * + *

Example: + *

{@code
+   * builder.tryCatch("safe-http-call", tryCatch -> tryCatch
+   *     .try_(tasks -> tasks
+   *         .http("risky-call", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://api.example.com/data")))))
+   *     .catch_(catch_ -> catch_
+   *         .when(when -> when
+   *             .with(error -> error
+   *                 .type("HttpError")))
+   *         .do_(tasks -> tasks
+   *             .set("error-handled", "${ true }"))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the try-catch task + * @return this builder for method chaining + * @see TryTaskBuilder for error handling configuration + */ @Override public DoTaskBuilder tryCatch( String name, Consumer> itemsConfigurer) { @@ -98,18 +423,81 @@ public DoTaskBuilder tryCatch( return this; } + /** + * Adds an OpenAPI operation call task to the workflow. + * + *

Creates a task that invokes an operation defined in an OpenAPI specification. The + * operation is resolved from the OpenAPI document and called with the provided parameters. + * + *

Example: + *

{@code
+   * builder.openapi("get-user", openapi -> openapi
+   *     .call(call -> call
+   *         .with(endpoint -> endpoint
+   *             .operationId("getUserById"))
+   *         .parameters(params -> params
+   *             .parameter("userId", "${ .userId }"))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the OpenAPI call task + * @return this builder for method chaining + * @see CallOpenAPITaskBuilder for OpenAPI call configuration + */ @Override public DoTaskBuilder openapi(String name, Consumer itemsConfigurer) { this.listBuilder().openapi(name, itemsConfigurer); return this; } + /** + * Adds a gRPC service call task to the workflow. + * + *

Creates a task that invokes a gRPC service method. Supports unary, client streaming, + * server streaming, and bidirectional streaming calls. + * + *

Example: + *

{@code
+   * builder.grpc("call-user-service", grpc -> grpc
+   *     .call(call -> call
+   *         .with(endpoint -> endpoint
+   *             .uri("grpc://user-service:50051")
+   *             .method("GetUser"))
+   *         .arguments(args -> args
+   *             .argument("userId", "${ .userId }"))));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the gRPC call task + * @return this builder for method chaining + * @see CallGrpcTaskBuilder for gRPC call configuration + */ @Override public DoTaskBuilder grpc(String name, Consumer itemsConfigurer) { this.listBuilder().grpc(name, itemsConfigurer); return this; } + /** + * Adds a sub-workflow invocation task to the workflow. + * + *

Creates a task that invokes another workflow as a sub-workflow. The sub-workflow executes + * independently and can return results to the parent workflow. + * + *

Example: + *

{@code
+   * builder.workflow("invoke-validation", workflow -> workflow
+   *     .call(call -> call
+   *         .with(endpoint -> endpoint
+   *             .workflowId("validation-workflow"))
+   *         .input("${ .dataToValidate }")));
+   * }
+ * + * @param name the unique name for this task + * @param itemsConfigurer consumer to configure the workflow call task + * @return this builder for method chaining + * @see WorkflowTaskBuilder for sub-workflow invocation configuration + */ @Override public DoTaskBuilder workflow(String name, Consumer itemsConfigurer) { this.listBuilder().workflow(name, itemsConfigurer); diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java index d277876f..2389bdd1 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java @@ -22,6 +22,120 @@ import java.util.List; import java.util.function.Consumer; +/** + * Builder for creating iteration/loop tasks in workflows. + * + *

The {@code ForEachTaskBuilder} provides a fluent API for defining tasks that iterate over + * collections, executing a set of tasks for each item. This is the primary mechanism for + * implementing loops and batch processing in workflows. + * + *

The builder supports: + *

    + *
  • Iterating over arrays and collections
  • + *
  • Accessing the current item and its index
  • + *
  • Conditional iteration with while expressions
  • + *
  • Sequential or parallel execution modes
  • + *
  • Nested task execution for each iteration
  • + *
+ * + *

Usage Examples

+ * + *

Basic Iteration

+ *
{@code
+ * ForEachTaskBuilder builder =
+ *     new ForEachTaskBuilder<>(new TaskItemListBuilder(0))
+ *     .each("item")
+ *     .in("${ .items }")
+ *     .tasks(tasks -> tasks
+ *         .http("process-item", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://api.example.com/process"))
+ *                 .method("POST")
+ *                 .body("${ .item }"))));
+ * }
+ * + *

Iteration with Index

+ *
{@code
+ * ForEachTaskBuilder builder =
+ *     new ForEachTaskBuilder<>(new TaskItemListBuilder(0))
+ *     .each("user")
+ *     .in("${ .users }")
+ *     .at("index")
+ *     .tasks(tasks -> tasks
+ *         .set("process-user", set -> set
+ *             .put("userId", "${ .user.id }")
+ *             .put("position", "${ .index + 1 }")
+ *             .put("isFirst", "${ .index == 0 }")));
+ * }
+ * + *

Conditional Iteration

+ *
{@code
+ * ForEachTaskBuilder builder =
+ *     new ForEachTaskBuilder<>(new TaskItemListBuilder(0))
+ *     .each("order")
+ *     .in("${ .orders }")
+ *     .whileC("${ .order.status == 'pending' }")
+ *     .tasks(tasks -> tasks
+ *         .http("process-order", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://api.example.com/orders/${.order.id}"))
+ *                 .method("PUT")
+ *                 .body("${ { status: 'processing' } }"))));
+ * }
+ * + *

Batch Processing with Aggregation

+ *
{@code
+ * ForEachTaskBuilder builder =
+ *     new ForEachTaskBuilder<>(new TaskItemListBuilder(0))
+ *     .each("record")
+ *     .in("${ .records }")
+ *     .at("idx")
+ *     .tasks(tasks -> tasks
+ *         .set("validate", set -> set
+ *             .put("isValid", "${ .record.value != null }"))
+ *         .switchCase("check-valid", sw -> sw
+ *             .on("valid", switchCase -> switchCase
+ *                 .when("${ .isValid }")
+ *                 .do_(validTasks -> validTasks
+ *                     .set("accumulate", set -> set
+ *                         .put("validCount", "${ .validCount + 1 }")
+ *                         .put("total", "${ .total + .record.value }"))))
+ *             .on("invalid", switchCase -> switchCase
+ *                 .when("${ !.isValid }")
+ *                 .do_(invalidTasks -> invalidTasks
+ *                     .set("track-error", set -> set
+ *                         .put("invalidCount", "${ .invalidCount + 1 }"))))));
+ * }
+ * + *

Iteration Variables

+ *

During iteration, the following variables are available in the workflow context: + *

    + *
  • Item variable (set by {@link #each(String)}): The current item from the collection
  • + *
  • Index variable (set by {@link #at(String)}): The zero-based index of the current item
  • + *
  • Collection (set by {@link #in(String)}): The expression that evaluates to the collection
  • + *
+ * + *

Execution Modes

+ *

The iteration can be configured for different execution modes through the workflow + * configuration (not directly in this builder). Common modes include: + *

    + *
  • Sequential: Process items one at a time in order
  • + *
  • Parallel: Process all items concurrently
  • + *
+ * + *

Thread Safety

+ *

This builder is not thread-safe. Each task definition should be + * constructed using a single thread. + * + * @param the type of task item list builder used for nested tasks + * @see TaskBaseBuilder for inherited task configuration methods + * @see ForEachTaskFluent for the fluent iteration interface + * @see io.serverlessworkflow.api.types.ForTask for the resulting task model + * @see DoTaskBuilder#forEach(String, Consumer) for usage within workflows + * @since 1.0.0 + */ public class ForEachTaskBuilder> extends TaskBaseBuilder> implements ForEachTaskFluent, T> { @@ -30,6 +144,14 @@ public class ForEachTaskBuilder> private final ForTaskConfiguration forTaskConfiguration; private final T taskItemListBuilder; + /** + * Constructs a new ForEachTaskBuilder with the specified task list builder. + * + *

The task list builder is used to create the nested tasks that will be executed + * for each iteration. + * + * @param taskItemListBuilder the builder for creating nested tasks within the loop + */ public ForEachTaskBuilder(T taskItemListBuilder) { super(); forTask = new ForTask(); @@ -38,30 +160,212 @@ public ForEachTaskBuilder(T taskItemListBuilder) { super.setTask(forTask); } + /** + * Returns this builder instance for method chaining. + * + *

This method is part of the builder pattern implementation, enabling fluent method + * chaining in the type hierarchy. + * + * @return this ForEachTaskBuilder instance + */ protected ForEachTaskBuilder self() { return this; } + /** + * Sets the variable name for the current item in each iteration. + * + *

This variable will be available in the workflow context during task execution and + * can be referenced in expressions using {@code ${ .variableName }}. + * + *

Examples: + *

{@code
+   * // Simple item reference
+   * builder.each("item")
+   *     .in("${ .items }")
+   *     .tasks(tasks -> tasks
+   *         .set("process", set -> set
+   *             .put("value", "${ .item.value }")));
+   *
+   * // Descriptive variable names
+   * builder.each("order")
+   *     .in("${ .orders }")
+   *     .tasks(tasks -> tasks
+   *         .http("process-order", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://api.example.com/orders/${.order.id}")))));
+   * }
+ * + * @param each the variable name for the current item; must be a valid identifier + * @return this builder for method chaining + * @see #in(String) for specifying the collection to iterate + * @see #at(String) for accessing the iteration index + */ public ForEachTaskBuilder each(String each) { forTaskConfiguration.setEach(each); return this; } + /** + * Sets the expression that evaluates to the collection to iterate over. + * + *

The expression should evaluate to an array or collection. Each element will be + * processed in order (or in parallel, depending on configuration). + * + *

Examples: + *

{@code
+   * // Iterate over a workflow variable
+   * builder.in("${ .items }");
+   *
+   * // Iterate over a filtered collection
+   * builder.in("${ .orders | select(.status == 'pending') }");
+   *
+   * // Iterate over a range
+   * builder.in("${ range(1; 11) }"); // Numbers 1 through 10
+   *
+   * // Iterate over transformed data
+   * builder.in("${ .users | map(.id) }");
+   * }
+ * + * @param in the expression that evaluates to the collection to iterate + * @return this builder for method chaining + * @see #each(String) for naming the current item variable + */ public ForEachTaskBuilder in(String in) { this.forTaskConfiguration.setIn(in); return this; } + /** + * Sets the variable name for the current iteration index. + * + *

This optional variable provides access to the zero-based index of the current item + * in the collection. Useful for position-dependent logic or tracking progress. + * + *

Examples: + *

{@code
+   * // Track iteration position
+   * builder.each("item")
+   *     .in("${ .items }")
+   *     .at("index")
+   *     .tasks(tasks -> tasks
+   *         .set("add-position", set -> set
+   *             .put("position", "${ .index + 1 }")
+   *             .put("isFirst", "${ .index == 0 }")
+   *             .put("isLast", "${ .index == (.items | length) - 1 }")));
+   *
+   * // Conditional logic based on position
+   * builder.each("record")
+   *     .in("${ .records }")
+   *     .at("idx")
+   *     .tasks(tasks -> tasks
+   *         .switchCase("check-position", sw -> sw
+   *             .on("first", switchCase -> switchCase
+   *                 .when("${ .idx == 0 }")
+   *                 .then("initialize-batch"))
+   *             .on("last", switchCase -> switchCase
+   *                 .when("${ .idx == (.records | length) - 1 }")
+   *                 .then("finalize-batch"))));
+   * }
+ * + * @param at the variable name for the iteration index; must be a valid identifier + * @return this builder for method chaining + * @see #each(String) for the current item variable + */ public ForEachTaskBuilder at(String at) { this.forTaskConfiguration.setAt(at); return this; } + /** + * Sets a conditional expression that controls iteration continuation. + * + *

The while expression is evaluated before each iteration. If it evaluates to + * {@code false}, the iteration stops even if there are more items in the collection. + * This enables early termination based on runtime conditions. + * + *

Examples: + *

{@code
+   * // Stop when a condition is met
+   * builder.each("item")
+   *     .in("${ .items }")
+   *     .whileC("${ .item.status == 'active' }")
+   *     .tasks(tasks -> tasks
+   *         .http("process", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://api.example.com/process")))));
+   *
+   * // Limit iterations based on accumulated value
+   * builder.each("order")
+   *     .in("${ .orders }")
+   *     .whileC("${ .totalProcessed < 100 }")
+   *     .tasks(tasks -> tasks
+   *         .set("accumulate", set -> set
+   *             .put("totalProcessed", "${ .totalProcessed + .order.amount }")));
+   *
+   * // Time-based termination
+   * builder.each("task")
+   *     .in("${ .tasks }")
+   *     .whileC("${ now() < .deadline }")
+   *     .tasks(tasks -> tasks
+   *         .http("execute-task", http -> http
+   *             .call(call -> call
+   *                 .with(endpoint -> endpoint
+   *                     .uri("https://api.example.com/tasks/${.task.id}")))));
+   * }
+ * + * @param expression the conditional expression; iteration continues while this is true + * @return this builder for method chaining + */ public ForEachTaskBuilder whileC(final String expression) { this.forTask.setWhile(expression); return this; } + /** + * Defines the tasks to execute for each iteration. + * + *

This method accepts a consumer that configures the nested tasks. These tasks have + * access to the iteration variables (item, index) and are executed for each element + * in the collection. + * + *

Multiple calls to this method will accumulate tasks, allowing you to build up + * the iteration logic incrementally. + * + *

Examples: + *

{@code
+   * // Simple processing
+   * builder.tasks(tasks -> tasks
+   *     .http("process-item", http -> http
+   *         .call(call -> call
+   *             .with(endpoint -> endpoint
+   *                 .uri("https://api.example.com/process"))
+   *             .method("POST")
+   *             .body("${ .item }"))));
+   *
+   * // Multiple tasks per iteration
+   * builder.tasks(tasks -> tasks
+   *     .set("validate", set -> set
+   *         .put("isValid", "${ .item.value != null }"))
+   *     .switchCase("check-valid", sw -> sw
+   *         .on("valid", switchCase -> switchCase
+   *             .when("${ .isValid }")
+   *             .then("process-valid"))
+   *         .on("invalid", switchCase -> switchCase
+   *             .when("${ !.isValid }")
+   *             .then("handle-invalid")))
+   *     .emit("notify", emit -> emit
+   *         .event(event -> event
+   *             .with(props -> props
+   *                 .type("item.processed")
+   *                 .data("${ .item }")))));
+   * }
+ * + * @param doBuilderConsumer consumer to configure the tasks for each iteration + * @return this builder for method chaining + */ public ForEachTaskBuilder tasks(Consumer doBuilderConsumer) { List existingTasks = this.forTask.getDo(); @@ -82,6 +386,17 @@ public ForEachTaskBuilder tasks(Consumer doBuilderConsumer) { return this; } + /** + * Builds and returns the configured ForTask. + * + *

This method finalizes the task configuration and creates the immutable ForTask + * instance with all iteration settings and nested tasks. + * + *

Note: After calling this method, the builder should not be reused. + * Create a new builder instance for additional tasks. + * + * @return the configured ForTask ready for execution + */ public ForTask build() { this.forTask.setFor(this.forTaskConfiguration); return this.forTask; diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java index 40ca8224..08a98a04 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java @@ -17,14 +17,180 @@ import io.serverlessworkflow.fluent.spec.spi.ForkTaskFluent; +/** + * Builder for creating parallel execution tasks in workflows. + * + *

The {@code ForkTaskBuilder} provides a fluent API for defining tasks that execute multiple + * branches concurrently. This is the primary mechanism for implementing parallel processing, + * fan-out patterns, and concurrent operations in workflows. + * + *

The builder supports: + *

    + *
  • Multiple parallel branches with independent task sequences
  • + *
  • Competition modes to control when the fork completes
  • + *
  • Branch naming and identification
  • + *
  • Nested task execution within each branch
  • + *
  • Result aggregation from all branches
  • + *
+ * + *

Usage Examples

+ * + *

Basic Parallel Execution

+ *
{@code
+ * ForkTaskBuilder builder = new ForkTaskBuilder()
+ *     .compete(false) // Wait for all branches
+ *     .branch("fetch-user", branch -> branch
+ *         .http("get-user", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://api.example.com/users/${.userId}"))
+ *                 .method("GET"))))
+ *     .branch("fetch-orders", branch -> branch
+ *         .http("get-orders", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://api.example.com/orders?userId=${.userId}"))
+ *                 .method("GET"))))
+ *     .branch("fetch-preferences", branch -> branch
+ *         .http("get-preferences", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://api.example.com/preferences/${.userId}"))
+ *                 .method("GET"))));
+ * }
+ * + *

Race Condition (First to Complete)

+ *
{@code
+ * ForkTaskBuilder builder = new ForkTaskBuilder()
+ *     .compete(true) // Complete when first branch finishes
+ *     .branch("primary-service", branch -> branch
+ *         .http("call-primary", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://primary.example.com/api"))
+ *                 .method("GET"))))
+ *     .branch("backup-service", branch -> branch
+ *         .http("call-backup", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://backup.example.com/api"))
+ *                 .method("GET"))));
+ * }
+ * + *

Complex Branch Processing

+ *
{@code
+ * ForkTaskBuilder builder = new ForkTaskBuilder()
+ *     .compete(false)
+ *     .branch("process-images", branch -> branch
+ *         .forEach("process-image", forEach -> forEach
+ *             .each("image")
+ *             .in("${ .images }")
+ *             .tasks(tasks -> tasks
+ *                 .http("resize-image", http -> http
+ *                     .call(call -> call
+ *                         .with(endpoint -> endpoint
+ *                             .uri("https://image-service.example.com/resize"))
+ *                         .method("POST")
+ *                         .body("${ .image }"))))))
+ *     .branch("process-videos", branch -> branch
+ *         .forEach("process-video", forEach -> forEach
+ *             .each("video")
+ *             .in("${ .videos }")
+ *             .tasks(tasks -> tasks
+ *                 .http("transcode-video", http -> http
+ *                     .call(call -> call
+ *                         .with(endpoint -> endpoint
+ *                             .uri("https://video-service.example.com/transcode"))
+ *                         .method("POST")
+ *                         .body("${ .video }"))))))
+ *     .branch("generate-metadata", branch -> branch
+ *         .set("create-metadata", set -> set
+ *             .put("timestamp", "${ now() }")
+ *             .put("totalFiles", "${ (.images | length) + (.videos | length) }")));
+ * }
+ * + *

Fan-Out with Aggregation

+ *
{@code
+ * ForkTaskBuilder builder = new ForkTaskBuilder()
+ *     .compete(false)
+ *     .branch("validate-email", branch -> branch
+ *         .http("check-email", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://validation.example.com/email"))
+ *                 .method("POST")
+ *                 .body("${ { email: .user.email } }"))))
+ *     .branch("validate-phone", branch -> branch
+ *         .http("check-phone", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://validation.example.com/phone"))
+ *                 .method("POST")
+ *                 .body("${ { phone: .user.phone } }"))))
+ *     .branch("validate-address", branch -> branch
+ *         .http("check-address", http -> http
+ *             .call(call -> call
+ *                 .with(endpoint -> endpoint
+ *                     .uri("https://validation.example.com/address"))
+ *                 .method("POST")
+ *                 .body("${ { address: .user.address } }"))));
+ * // After fork completes, all validation results are available in context
+ * }
+ * + *

Competition Modes

+ *

The fork task supports two competition modes via {@link #compete(boolean)}: + *

    + *
  • Wait for all (compete=false): The fork completes when all branches + * finish. Results from all branches are available. This is the default mode.
  • + *
  • Race (compete=true): The fork completes when the first branch finishes. + * Other branches may be cancelled. Useful for redundancy and failover scenarios.
  • + *
+ * + *

Branch Naming

+ *

Each branch should have a unique name for identification and debugging. If no name is + * provided, an auto-generated name (e.g., "branch-0", "branch-1") will be used. + * + *

Result Handling

+ *

When all branches complete (or the first in compete mode), their results are merged into + * the workflow context. Each branch's output is available under its branch name. + * + *

Thread Safety

+ *

This builder is not thread-safe. Each task definition should be + * constructed using a single thread. However, the branches themselves will execute concurrently + * at runtime. + * + * @see AbstractForkTaskBuilder for inherited fork configuration methods + * @see TaskItemListBuilder for branch task composition + * @see ForkTaskFluent for the fluent fork interface + * @see io.serverlessworkflow.api.types.ForkTask for the resulting task model + * @see DoTaskBuilder#fork(String, Consumer) for usage within workflows + * @since 1.0.0 + */ public class ForkTaskBuilder extends AbstractForkTaskBuilder implements ForkTaskFluent { + /** + * Returns this builder instance for method chaining. + * + *

This method is part of the builder pattern implementation, enabling fluent method + * chaining in the type hierarchy. + * + * @return this ForkTaskBuilder instance + */ @Override protected ForkTaskBuilder self() { return this; } + /** + * Creates a new task item list builder for branch task composition. + * + *

This method is called internally to create builders for each branch's task sequence. + * The offset ensures proper task naming when branches are added incrementally. + * + * @param listOffsetSize the current number of tasks, used for name generation + * @return a new TaskItemListBuilder instance for the branch + */ @Override protected TaskItemListBuilder newTaskItemListBuilder(int listOffsetSize) { return new TaskItemListBuilder(listOffsetSize); diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java index 0ce11ec4..956c6da6 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java @@ -19,32 +19,186 @@ import io.serverlessworkflow.api.types.SetTask; import io.serverlessworkflow.api.types.SetTaskConfiguration; +/** + * Builder for creating variable assignment tasks in workflows. + * + *

The {@code SetTaskBuilder} provides a fluent API for defining tasks that set or modify + * workflow variables. This is the primary mechanism for manipulating workflow state, allowing + * you to assign values using expressions or key-value pairs. + * + *

The builder supports two modes of operation: + *

    + *
  • Expression mode: Use {@link #expr(String)} to set variables using a + * single expression that evaluates to an object
  • + *
  • Key-value mode: Use {@link #put(String, Object)} to set individual + * variables with specific values
  • + *
+ * + *

Usage Examples

+ * + *

Key-Value Assignments

+ *
{@code
+ * SetTaskBuilder builder = new SetTaskBuilder()
+ *     .put("userId", "${ .request.userId }")
+ *     .put("timestamp", "${ now() }")
+ *     .put("status", "processing")
+ *     .put("counter", 0);
+ * }
+ * + *

Expression-Based Assignment

+ *
{@code
+ * SetTaskBuilder builder = new SetTaskBuilder()
+ *     .expr("${ { userId: .request.userId, timestamp: now(), status: 'processing' } }");
+ * }
+ * + *

Complex Calculations

+ *
{@code
+ * SetTaskBuilder builder = new SetTaskBuilder()
+ *     .put("subtotal", "${ .items | map(.price) | add }")
+ *     .put("tax", "${ .subtotal * 0.1 }")
+ *     .put("shipping", "${ .subtotal > 100 ? 0 : 10 }")
+ *     .put("total", "${ .subtotal + .tax + .shipping }");
+ * }
+ * + *

Expression Language

+ *

The builder uses the Serverless Workflow expression language (based on jq) for evaluating + * expressions. Expressions can: + *

    + *
  • Access workflow context using {@code ${ .variableName }}
  • + *
  • Perform calculations and transformations
  • + *
  • Call built-in functions like {@code now()}, {@code uuid()}
  • + *
  • Use conditional logic and filters
  • + *
+ * + *

Variable Scope

+ *

Variables set by this task are added to the workflow context and are available to + * subsequent tasks. Variables persist throughout the workflow execution unless explicitly + * modified or removed. + * + *

Thread Safety

+ *

This builder is not thread-safe. Each task definition should be + * constructed using a single thread. + * + * @see TaskBaseBuilder for inherited task configuration methods + * @see io.serverlessworkflow.api.types.SetTask for the resulting task model + * @see DoTaskBuilder#set(String, Consumer) for usage within workflows + * @since 1.0.0 + */ public class SetTaskBuilder extends TaskBaseBuilder { protected final SetTask task; protected SetTaskConfiguration setTaskConfiguration; + /** + * Constructs a new SetTaskBuilder. + * + *

Initializes the builder with an empty set task configuration, ready to accept + * variable assignments via {@link #put(String, Object)} or {@link #expr(String)}. + */ public SetTaskBuilder() { this.task = new SetTask(); this.setTaskConfiguration = new SetTaskConfiguration(); this.setTask(task); } + /** + * Returns this builder instance for method chaining. + * + *

This method is part of the builder pattern implementation, enabling fluent method + * chaining in the type hierarchy. + * + * @return this SetTaskBuilder instance + */ @Override protected SetTaskBuilder self() { return this; } + /** + * Sets all variables using a single expression. + * + *

This method allows you to define all variable assignments in one expression that + * evaluates to an object. The expression result should be an object whose properties + * become workflow variables. + * + *

Note: Using this method replaces any previous {@link #put(String, Object)} + * calls. You should use either {@code expr()} or {@code put()}, not both. + * + *

Examples: + *

{@code
+   * // Create multiple variables from an object expression
+   * builder.expr("${ { userId: .request.userId, status: 'active', count: 0 } }");
+   *
+   * // Transform and assign the entire context
+   * builder.expr("${ . | { userId: .user.id, email: .user.email } }");
+   *
+   * // Merge with existing context
+   * builder.expr("${ . + { processed: true, timestamp: now() } }");
+   * }
+ * + * @param expression the expression to evaluate; must evaluate to an object whose properties + * will be set as workflow variables + * @return this builder for method chaining + * @see #put(String, Object) for individual variable assignments + */ public SetTaskBuilder expr(String expression) { this.task.setSet(new Set().withString(expression)); return this; } + /** + * Adds a single variable assignment to the task. + * + *

This method allows you to set individual workflow variables with specific values or + * expressions. Multiple calls to this method accumulate assignments that will all be + * executed when the task runs. + * + *

The value can be: + *

    + *
  • A literal value (string, number, boolean, null)
  • + *
  • An expression string starting with {@code ${ } for dynamic evaluation
  • + *
  • A complex object or array
  • + *
+ * + *

Examples: + *

{@code
+   * // Literal values
+   * builder.put("status", "pending")
+   *        .put("retryCount", 0)
+   *        .put("enabled", true);
+   *
+   * // Expression values
+   * builder.put("userId", "${ .request.userId }")
+   *        .put("timestamp", "${ now() }")
+   *        .put("total", "${ .items | map(.price) | add }");
+   *
+   * // Complex values
+   * builder.put("metadata", Map.of("source", "api", "version", "1.0"))
+   *        .put("tags", List.of("important", "urgent"));
+   * }
+ * + * @param key the name of the workflow variable to set + * @param value the value to assign; can be a literal or an expression string + * @return this builder for method chaining + * @see #expr(String) for setting all variables with a single expression + */ public SetTaskBuilder put(String key, Object value) { setTaskConfiguration.withAdditionalProperty(key, value); return this; } + /** + * Builds and returns the configured SetTask. + * + *

This method finalizes the task configuration and creates the immutable SetTask instance. + * If no expression was set via {@link #expr(String)}, the accumulated key-value pairs from + * {@link #put(String, Object)} calls are used. + * + *

Note: After calling this method, the builder should not be reused. + * Create a new builder instance for additional tasks. + * + * @return the configured SetTask ready for execution + */ public SetTask build() { if (this.task.getSet() == null) { this.task.setSet(new Set().withSetTaskConfiguration(setTaskConfiguration)); diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java index 3db8c7a4..e7589f4f 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java @@ -22,12 +22,104 @@ import java.util.List; import java.util.function.Consumer; +/** + * Builder for creating conditional branching tasks in workflows. + * + *

The {@code SwitchTaskBuilder} provides a fluent API for defining switch/case logic that + * evaluates conditions and executes different workflow paths based on the results. This is the + * primary mechanism for implementing conditional branching and decision logic in workflows. + * + *

The builder supports multiple switch cases, each with: + *

    + *
  • A conditional expression that evaluates to true or false
  • + *
  • A target task or workflow path to execute when the condition matches
  • + *
  • Optional default case when no conditions match
  • + *
+ * + *

Usage Examples

+ * + *

Simple Conditional Branching

+ *
{@code
+ * SwitchTaskBuilder builder = new SwitchTaskBuilder()
+ *     .on("approved", switchCase -> switchCase
+ *         .when("${ .status == 'approved' }")
+ *         .then("process-approval"))
+ *     .on("rejected", switchCase -> switchCase
+ *         .when("${ .status == 'rejected' }")
+ *         .then("process-rejection"))
+ *     .on("default", switchCase -> switchCase
+ *         .then("process-pending")); // Default case (no when condition)
+ * }
+ * + *

Multiple Conditions with Complex Logic

+ *
{@code
+ * SwitchTaskBuilder builder = new SwitchTaskBuilder()
+ *     .on("high-priority", switchCase -> switchCase
+ *         .when("${ .priority == 'high' and .amount > 1000 }")
+ *         .then("expedited-processing"))
+ *     .on("medium-priority", switchCase -> switchCase
+ *         .when("${ .priority == 'medium' or (.priority == 'high' and .amount <= 1000) }")
+ *         .then("standard-processing"))
+ *     .on("low-priority", switchCase -> switchCase
+ *         .when("${ .priority == 'low' }")
+ *         .then("batch-processing"));
+ * }
+ * + *

Range-Based Routing

+ *
{@code
+ * SwitchTaskBuilder builder = new SwitchTaskBuilder()
+ *     .on("small-order", switchCase -> switchCase
+ *         .when("${ .total < 100 }")
+ *         .then("small-order-handler"))
+ *     .on("medium-order", switchCase -> switchCase
+ *         .when("${ .total >= 100 and .total < 1000 }")
+ *         .then("medium-order-handler"))
+ *     .on("large-order", switchCase -> switchCase
+ *         .when("${ .total >= 1000 }")
+ *         .then("large-order-handler"));
+ * }
+ * + *

Evaluation Order

+ *

Switch cases are evaluated in the order they are defined. The first case whose condition + * evaluates to {@code true} is executed, and subsequent cases are skipped. This allows for + * priority-based routing where more specific conditions are checked before general ones. + * + *

Default Case

+ *

A default case can be defined by omitting the {@code when()} condition. This case will + * execute if no other conditions match. It's recommended to place the default case last, + * though it can appear anywhere in the switch definition. + * + *

Expression Language

+ *

Conditions use the Serverless Workflow expression language (based on jq) and can: + *

    + *
  • Access workflow context using {@code ${ .variableName }}
  • + *
  • Use comparison operators: {@code ==}, {@code !=}, {@code <}, {@code >}, {@code <=}, {@code >=}
  • + *
  • Use logical operators: {@code and}, {@code or}, {@code not}
  • + *
  • Call functions and perform complex evaluations
  • + *
+ * + *

Thread Safety

+ *

This builder is not thread-safe. Each task definition should be + * constructed using a single thread. + * + * @see TaskBaseBuilder for inherited task configuration methods + * @see SwitchTaskFluent for the fluent switch case interface + * @see io.serverlessworkflow.api.types.SwitchTask for the resulting task model + * @see DoTaskBuilder#switchCase(String, Consumer) for usage within workflows + * @since 1.0.0 + */ public class SwitchTaskBuilder extends TaskBaseBuilder implements SwitchTaskFluent { private final SwitchTask switchTask; private final List switchItems; + /** + * Constructs a new SwitchTaskBuilder. + * + *

Initializes the builder with an empty list of switch cases, ready to accept + * case definitions via {@link #on(String, Consumer)}. + */ SwitchTaskBuilder() { super(); this.switchTask = new SwitchTask(); @@ -35,11 +127,58 @@ public class SwitchTaskBuilder extends TaskBaseBuilder this.setTask(switchTask); } + /** + * Returns this builder instance for method chaining. + * + *

This method is part of the builder pattern implementation, enabling fluent method + * chaining in the type hierarchy. + * + * @return this SwitchTaskBuilder instance + */ @Override protected SwitchTaskBuilder self() { return this; } + /** + * Adds a switch case to the conditional branching logic. + * + *

Each switch case consists of an optional condition and a target. Cases are evaluated + * in the order they are added, and the first matching case is executed. + * + *

The case name is used for identification and debugging purposes. If blank or null, + * a default name will be generated. + * + *

Examples: + *

{@code
+   * // Case with condition
+   * builder.on("approved-case", switchCase -> switchCase
+   *     .when("${ .status == 'approved' }")
+   *     .then("approval-handler"));
+   *
+   * // Default case (no condition)
+   * builder.on("default-case", switchCase -> switchCase
+   *     .then("default-handler"));
+   *
+   * // Complex condition
+   * builder.on("priority-case", switchCase -> switchCase
+   *     .when("${ .priority == 'high' and .amount > 1000 and .customer.vip }")
+   *     .then("vip-processing"));
+   *
+   * // Inline task execution
+   * builder.on("inline-case", switchCase -> switchCase
+   *     .when("${ .needsProcessing }")
+   *     .do_(tasks -> tasks
+   *         .set("processed", set -> set
+   *             .put("timestamp", "${ now() }")
+   *             .put("status", "completed"))));
+   * }
+ * + * @param name the name for this switch case; used for identification and debugging + * @param switchCaseConsumer consumer to configure the switch case with condition and target + * @return this builder for method chaining + * @see SwitchTaskFluent.SwitchCaseBuilder for case configuration options + */ @Override public SwitchTaskBuilder on( String name, Consumer switchCaseConsumer) { @@ -50,11 +189,31 @@ public SwitchTaskBuilder on( return this; } + /** + * Returns the current number of switch cases defined. + * + *

This method is useful for validation or debugging to ensure the expected number + * of cases have been configured. + * + * @return the count of switch cases currently defined in this builder + */ @Override public int switchItemCount() { return this.switchItems.size(); } + /** + * Builds and returns the configured SwitchTask. + * + *

This method finalizes the task configuration and creates the immutable SwitchTask + * instance with all defined switch cases. The cases will be evaluated in the order they + * were added to the builder. + * + *

Note: After calling this method, the builder should not be reused. + * Create a new builder instance for additional tasks. + * + * @return the configured SwitchTask ready for execution + */ public SwitchTask build() { this.switchTask.setSwitch(this.switchItems); return this.switchTask; diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.java index fd74a283..419a66e6 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.java @@ -17,35 +17,191 @@ import java.util.UUID; +/** + * Primary entry point for building Serverless Workflow specifications using a fluent API. + * + *

The {@code WorkflowBuilder} provides a type-safe, chainable interface for constructing + * workflow definitions programmatically. It serves as the main facade for the fluent API, + * offering factory methods to create workflow instances with various levels of detail. + * + *

This builder follows the builder pattern and supports method chaining for a natural, + * readable workflow definition experience. All builder methods return {@code this} to enable + * fluent composition. + * + *

Usage Example

+ *
{@code
+ * Workflow workflow = WorkflowBuilder.workflow("order-processing", "com.example", "1.0.0")
+ *     .document(doc -> doc
+ *         .title("Order Processing Workflow")
+ *         .description("Handles customer order processing"))
+ *     .input(input -> input
+ *         .schema(schema -> schema
+ *             .format("application/json")))
+ *     .tasks(tasks -> tasks
+ *         .set(set -> set
+ *             .name("validate-order")
+ *             .set("isValid", "${ .order.total > 0 }"))
+ *         .switchTask(sw -> sw
+ *             .name("check-validation")
+ *             .switchItem(item -> item
+ *                 .when("${ .isValid }")
+ *                 .then("process-order"))
+ *             .switchItem(item -> item
+ *                 .when("${ !.isValid }")
+ *                 .then("reject-order"))))
+ *     .output(output -> output
+ *         .as("${ .result }"))
+ *     .build();
+ * }
+ * + *

Factory Methods

+ *

The builder provides multiple factory methods to accommodate different use cases: + *

    + *
  • {@link #workflow(String, String, String)} - Full specification with name, namespace, and version
  • + *
  • {@link #workflow(String, String)} - Name and namespace with default version
  • + *
  • {@link #workflow(String)} - Name only with default namespace and version
  • + *
  • {@link #workflow()} - Auto-generated UUID name with defaults
  • + *
+ * + *

Thread Safety

+ *

This builder is not thread-safe. Each workflow definition should be + * constructed using a single thread. For concurrent workflow creation, use separate builder + * instances. + * + * @see BaseWorkflowBuilder for inherited configuration methods + * @see DoTaskBuilder for task composition + * @see io.serverlessworkflow.api.types.Workflow for the resulting workflow model + * @since 1.0.0 + */ public class WorkflowBuilder extends BaseWorkflowBuilder { + /** + * Constructs a new WorkflowBuilder with the specified workflow metadata. + * + * @param name the unique name of the workflow + * @param namespace the namespace for organizing workflows (e.g., "com.example") + * @param version the semantic version of the workflow (e.g., "1.0.0") + */ private WorkflowBuilder(final String name, final String namespace, final String version) { super(name, namespace, version); } + /** + * Creates a new DoTaskBuilder for sequential task execution. + * + *

This method is called internally to create task builders with the correct naming offset, + * ensuring that auto-generated task names continue sequentially across multiple task additions. + * + * @param listSizeOffset the current number of tasks in the workflow's task list + * @return a new DoTaskBuilder instance configured with the specified offset + */ @Override protected DoTaskBuilder newDo(int listSizeOffset) { return new DoTaskBuilder(listSizeOffset); } + /** + * Creates a new workflow builder with fully specified metadata. + * + *

This is the most explicit factory method, allowing complete control over the workflow's + * identity. Use this when you need to specify all three components of the workflow identifier. + * + *

Example: + *

{@code
+   * WorkflowBuilder builder = WorkflowBuilder.workflow(
+   *     "payment-processing",
+   *     "com.example.payments",
+   *     "2.1.0"
+   * );
+   * }
+ * + * @param name the unique name identifying this workflow + * @param namespace the namespace for organizing related workflows (e.g., "com.example") + * @param version the semantic version string (e.g., "1.0.0") + * @return a new WorkflowBuilder instance ready for configuration + * @see #workflow(String, String) for default version + * @see #workflow(String) for default namespace and version + */ public static WorkflowBuilder workflow( final String name, final String namespace, final String version) { return new WorkflowBuilder(name, namespace, version); } + /** + * Creates a new workflow builder with a default version. + * + *

Uses the default version ({@value BaseWorkflowBuilder#DEFAULT_VERSION}) when version + * management is not critical or during development. + * + *

Example: + *

{@code
+   * WorkflowBuilder builder = WorkflowBuilder.workflow(
+   *     "order-fulfillment",
+   *     "com.example.orders"
+   * );
+   * // Version defaults to "0.0.1"
+   * }
+ * + * @param name the unique name identifying this workflow + * @param namespace the namespace for organizing related workflows + * @return a new WorkflowBuilder instance with default version + * @see #workflow(String, String, String) for explicit version control + */ public static WorkflowBuilder workflow(final String name, final String namespace) { return new WorkflowBuilder(name, namespace, DEFAULT_VERSION); } + /** + * Creates a new workflow builder with default namespace and version. + * + *

Uses default namespace ({@value BaseWorkflowBuilder#DEFAULT_NAMESPACE}) and version + * ({@value BaseWorkflowBuilder#DEFAULT_VERSION}). Suitable for simple workflows or prototyping. + * + *

Example: + *

{@code
+   * WorkflowBuilder builder = WorkflowBuilder.workflow("hello-world");
+   * // Namespace defaults to "org.acme"
+   * // Version defaults to "0.0.1"
+   * }
+ * + * @param name the unique name identifying this workflow + * @return a new WorkflowBuilder instance with default namespace and version + * @see #workflow(String, String) for custom namespace + */ public static WorkflowBuilder workflow(final String name) { return new WorkflowBuilder(name, DEFAULT_NAMESPACE, DEFAULT_VERSION); } + /** + * Creates a new workflow builder with auto-generated name and default metadata. + * + *

Generates a random UUID as the workflow name, useful for temporary workflows, testing, + * or when the workflow name will be set later through other means. + * + *

Example: + *

{@code
+   * WorkflowBuilder builder = WorkflowBuilder.workflow();
+   * // Name is auto-generated UUID
+   * // Namespace defaults to "org.acme"
+   * // Version defaults to "0.0.1"
+   * }
+ * + * @return a new WorkflowBuilder instance with auto-generated UUID name + * @see #workflow(String) for explicit naming + */ public static WorkflowBuilder workflow() { return new WorkflowBuilder(UUID.randomUUID().toString(), DEFAULT_NAMESPACE, DEFAULT_VERSION); } + /** + * Returns this builder instance for method chaining. + * + *

This method is part of the builder pattern implementation, enabling fluent method chaining + * in the type hierarchy. + * + * @return this WorkflowBuilder instance + */ @Override protected WorkflowBuilder self() { return this;