Skip to content

[NO-ISSUE] Add wait to DSL#1294

Open
mcruzdev wants to merge 7 commits intoserverlessworkflow:mainfrom
mcruzdev:wait
Open

[NO-ISSUE] Add wait to DSL#1294
mcruzdev wants to merge 7 commits intoserverlessworkflow:mainfrom
mcruzdev:wait

Conversation

@mcruzdev
Copy link
Copy Markdown
Collaborator

@mcruzdev mcruzdev commented Apr 8, 2026

This pull request introduces a new "wait" task type to the Serverless Workflow Java Fluent API, enabling workflows to pause execution for a specified duration.

New "wait" task support:

  • Added WaitTaskBuilder for configuring wait tasks with duration expressions, inline durations, or timeout builders. (fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WaitTaskBuilder.java)
  • Extended the builder interfaces (TaskItemListBuilder, DoTaskBuilder, DoFluent, and new WaitFluent) to support adding wait tasks fluently, including auto-naming and configuration. (fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java, DoTaskBuilder.java, spi/DoFluent.java, spi/WaitFluent.java) [1] [2] [3] [4]
  • Enhanced the DSL with helper methods for creating wait tasks with various duration specifications. (fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java)

Testing and validation:

  • Added unit tests to verify wait task auto-naming, duration parsing, and DSL integration. (TaskItemDefaultNamingTest.java, DSLTest.java) [1] [2]
  • Introduced integration tests to ensure workflows wait the expected amount of time at runtime. (impl/test/src/test/java/io/serverlessworkflow/impl/test/WaitTest.java)

Bug fix:

  • Fixed a bug in WaitExecutor where duration components were not being added correctly, ensuring accurate wait times. (impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java)

Dependencies:

  • Added the Awaitility library as a test dependency to support timing assertions in wait-related tests. (impl/test/pom.xml)

Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
@mcruzdev mcruzdev requested a review from fjtirado as a code owner April 8, 2026 21:16
Copilot AI review requested due to automatic review settings April 8, 2026 21:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class wait task support to the Serverless Workflow Java Fluent API/DSL, along with runtime and test coverage, and fixes a duration-aggregation bug in the wait executor.

Changes:

  • Introduces WaitTaskBuilder and fluent interfaces to add wait tasks (including auto-naming support).
  • Adds DSL helper methods for constructing wait tasks from duration expressions or timeout builders.
  • Fixes WaitExecutor duration composition and adds new unit/integration tests for wait behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
impl/test/src/test/java/io/serverlessworkflow/impl/test/WaitTest.java New runtime/integration-style tests validating that workflows actually pause for a duration.
impl/test/pom.xml Adds Awaitility dependency for timing assertions (currently duplicated).
impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java Fixes duration accumulation to correctly add seconds/minutes/hours/days.
fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/TaskItemDefaultNamingTest.java Adds coverage for wait task auto-naming and inline-duration mapping.
fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java Adds DSL helper coverage for wait tasks.
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WaitTaskBuilder.java New builder for configuring wait tasks via expression, Duration, or TimeoutBuilder.
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java Adds fluent wait(...) task insertion into task lists.
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/WaitFluent.java New SPI fluent interface for wait(...) task addition.
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java Extends the fluent task API to include wait(...) (SPI surface change).
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java Adds DSL helper overloads for building wait tasks.
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java Wires wait(...) into the top-level do builder.
fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java Adds TYPE_WAIT for deterministic auto-naming.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Collaborator

@fjtirado fjtirado Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait functionality is already covered here

What I think should be done is:

  1. delete this file (which will susbtantially increase the time running test in case of failure and I made a huge effort to keep the mvn clean install time short)
  2. In LifeCycleEventTest class, to generate, using DSL, a workflow equivalen to wait-set.yaml.
  3. Refactor the code to run the same test with the workflow loaded from the yaml and the workflow generated with the fluent

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same approach should be taken for fork-wait.yaml (which will also verify fork dsl is working)
See https://github.com/serverlessworkflow/sdk-java/blob/main/impl/test/src/test/java/io/serverlessworkflow/impl/test/ForkWaitTest.java#L52-L76

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the two test mentioned above, indirectly verify that wait is working without explicit testing, so the test suite still run fast
The target of this PR should be to ensure that we can generate the same type of workflow using fluent that we are generating writing YAML

mcruzdev added 2 commits April 9, 2026 09:22
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
@mcruzdev
Copy link
Copy Markdown
Collaborator Author

mcruzdev commented Apr 9, 2026

cc: @domhanak

Comment on lines +255 to +263
private static WorkflowInstance waitSetInstance(WorkflowSource source) throws IOException {
return switch (source) {
case DSL -> appl.workflowDefinition(waitTestWorkflow()).instance(Map.of());
case YAML ->
appl.workflowDefinition(
WorkflowReader.readWorkflowFromClasspath("workflows-samples/wait-set.yaml"))
.instance(Map.of());
};
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmmm,there should be a different way to do this, the only thing that change here is the Workflow object

Comment on lines +87 to +90
if (source == WorkflowSource.DSL) {
return forkWaitWorkflow();
}
return readWorkflowFromClasspath("workflows-samples/fork-wait.yaml");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (source == WorkflowSource.DSL) {
return forkWaitWorkflow();
}
return readWorkflowFromClasspath("workflows-samples/fork-wait.yaml");
return source == WorkflowSource.DSL ? forkWaitWorkflow() : readWorkflowFromClasspath("workflows-samples/fork-wait.yaml");

Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Copilot AI review requested due to automatic review settings April 9, 2026 16:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +40 to +45
final TimeoutBuilder timeoutBuilder = new TimeoutBuilder();
waitConsumer.accept(timeoutBuilder);
this.waitTask.setWait(timeoutBuilder.build().getAfter());
return this;
}

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WaitTaskBuilder.wait(Consumer) does not null-check the consumer and does not validate that the provided TimeoutBuilder actually sets a duration (inline or expression). As written, callers can build a wait task with wait=null or with a TimeoutAfter missing both durationInline and durationExpression, which will later cause an NPE in WaitExecutor when it calls Duration.parse(...). Consider requiring a non-null consumer and throwing an IllegalStateException when no duration is configured.

Suggested change
final TimeoutBuilder timeoutBuilder = new TimeoutBuilder();
waitConsumer.accept(timeoutBuilder);
this.waitTask.setWait(timeoutBuilder.build().getAfter());
return this;
}
Objects.requireNonNull(waitConsumer, "waitConsumer must not be null");
final TimeoutBuilder timeoutBuilder = new TimeoutBuilder();
waitConsumer.accept(timeoutBuilder);
final TimeoutAfter after = timeoutBuilder.build().getAfter();
validateTimeoutAfter(after);
this.waitTask.setWait(after);
return this;
}
private static void validateTimeoutAfter(TimeoutAfter after) {
if (after == null
|| (after.getDurationInline() == null && after.getDurationExpression() == null)) {
throw new IllegalStateException(
"wait timeout must configure either durationInline or durationExpression");
}
}

Copilot uses AI. Check for mistakes.
return this;
}

public WaitTaskBuilder wait(String durationExpression) {
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WaitTaskBuilder.wait(String durationExpression) allows null/blank input, which can build an invalid wait task and later trigger an NPE at runtime when WaitExecutor parses the duration. Consider validating durationExpression is non-null/non-blank (and possibly giving a clearer error message for invalid ISO-8601 durations).

Suggested change
public WaitTaskBuilder wait(String durationExpression) {
public WaitTaskBuilder wait(String durationExpression) {
Objects.requireNonNull(durationExpression, "durationExpression must not be null");
if (durationExpression.isBlank()) {
throw new IllegalArgumentException("durationExpression must not be blank");
}

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +267

private enum WorkflowSource {
DSL,
YAML
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WorkflowSource enum is unused in this test class. Consider removing it to avoid dead code and keep the test focused on the actual workflow sources provided by waitSetWorkflowSources().

Suggested change
private enum WorkflowSource {
DSL,
YAML
}

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +261
private static Workflow waitTestWorkflow() {
return WorkflowBuilder.workflow("wait-test-java-dsl", "test", "0.1.0")
.tasks(
// wait 500 ms
DSL.wait(
"waitABit",
timeoutBuilder ->
timeoutBuilder.duration(durationBuilder -> durationBuilder.milliseconds(500))),
DSL.set("useExpression", setTaskBuilder -> setTaskBuilder.put("name", "Javierito")))
.build();
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions adding a new integration test impl/test/src/test/java/io/serverlessworkflow/impl/test/WaitTest.java, but that file is not present in the change set; instead, wait behavior seems covered via the new DSL-built workflow in this class and parameterized tests. Please update the PR description to match the actual tests being added/modified (or add the missing test if it was intended).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to +35
SELF branches(Consumer<L> branchesConsumer);

default SELF branch(Consumer<L> branchConsumer) {
return branch(null, branchConsumer);
}

SELF branch(String name, Consumer<L> branchConsumer);

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces a new ForkTaskFluent.branch(...) / ForkTaskBuilder.branch(...) API (wrapping branch tasks in a DoTask) in addition to the advertised wait-task DSL work. Please call this out in the PR description/release notes, since it expands the public fluent API beyond wait support.

Copilot uses AI. Check for mistakes.
mcruzdev added 2 commits April 9, 2026 15:37
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Copilot AI review requested due to automatic review settings April 9, 2026 19:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (name == null || name.isBlank()) {
name = "branch-" + this.items.size();
}
final FuncTaskItemListBuilder branchItems = new FuncTaskItemListBuilder(this.items);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new branch(…, Consumer<…>) implementation ignores the provided branchConsumer and builds the branch's DoTask from a FuncTaskItemListBuilder backed by this.items. Because branchItems.build() returns an unmodifiable view of the same underlying list, the created branch will effectively reference the fork’s full branches list (and can even end up self-referential once the new TaskItem is added), producing an invalid workflow structure. Create a fresh FuncTaskItemListBuilder for the branch body (not backed by this.items), invoke branchConsumer.accept(...) to populate it, then wrap that built list in the DoTask before appending the branch TaskItem to this.items.

Suggested change
final FuncTaskItemListBuilder branchItems = new FuncTaskItemListBuilder(this.items);
final FuncTaskItemListBuilder branchItems = new FuncTaskItemListBuilder(this.items.size());
if (branchConsumer != null) {
branchConsumer.accept(branchItems);
}

Copilot uses AI. Check for mistakes.
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants