Adding examples to Spring Boot (duration, zoneddatetime and suspend/resume) (#1413)

* implementing createtime with zoneddatetime

Signed-off-by: salaboy <Salaboy@gmail.com>

* adding duration and zoneddatetime examples

Signed-off-by: salaboy <Salaboy@gmail.com>

* using external event wf to test suspend resume

Signed-off-by: salaboy <Salaboy@gmail.com>

---------

Signed-off-by: salaboy <Salaboy@gmail.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>
This commit is contained in:
salaboy 2025-07-14 11:38:06 +01:00 committed by sirivarma
parent 982b2874fe
commit 85f82f2e54
7 changed files with 248 additions and 6 deletions

View File

@ -14,23 +14,19 @@ limitations under the License.
package io.dapr.workflows; package io.dapr.workflows;
import io.dapr.durabletask.CompositeTaskFailedException; import io.dapr.durabletask.CompositeTaskFailedException;
import io.dapr.durabletask.FailureDetails;
import io.dapr.durabletask.RetryContext; import io.dapr.durabletask.RetryContext;
import io.dapr.durabletask.RetryHandler; import io.dapr.durabletask.RetryHandler;
import io.dapr.durabletask.Task; import io.dapr.durabletask.Task;
import io.dapr.durabletask.TaskCanceledException; import io.dapr.durabletask.TaskCanceledException;
import io.dapr.durabletask.TaskOptions; import io.dapr.durabletask.TaskOptions;
import io.dapr.durabletask.TaskOrchestrationContext; import io.dapr.durabletask.TaskOrchestrationContext;
import io.dapr.workflows.runtime.DefaultWorkflowContext; import io.dapr.workflows.runtime.DefaultWorkflowContext;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.slf4j.Logger; import org.slf4j.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;

View File

@ -24,7 +24,8 @@ import io.dapr.springboot.examples.wfp.fanoutin.FanOutInWorkflow;
import io.dapr.springboot.examples.wfp.fanoutin.Result; import io.dapr.springboot.examples.wfp.fanoutin.Result;
import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; import io.dapr.springboot.examples.wfp.remoteendpoint.Payload;
import io.dapr.springboot.examples.wfp.remoteendpoint.RemoteEndpointWorkflow; import io.dapr.springboot.examples.wfp.remoteendpoint.RemoteEndpointWorkflow;
import io.dapr.springboot.examples.wfp.suspendresume.SuspendResumeWorkflow; import io.dapr.springboot.examples.wfp.timer.DurationTimerWorkflow;
import io.dapr.springboot.examples.wfp.timer.ZonedDateTimeTimerWorkflow;
import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus; import io.dapr.workflows.client.WorkflowInstanceStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -156,7 +157,7 @@ public class WorkflowPatternsRestController {
@PostMapping("wfp/suspendresume") @PostMapping("wfp/suspendresume")
public String suspendResume(@RequestParam("orderId") String orderId) { public String suspendResume(@RequestParam("orderId") String orderId) {
String instanceId = daprWorkflowClient.scheduleNewWorkflow(SuspendResumeWorkflow.class); String instanceId = daprWorkflowClient.scheduleNewWorkflow(ExternalEventWorkflow.class);
logger.info("Workflow instance " + instanceId + " started"); logger.info("Workflow instance " + instanceId + " started");
ordersToApprove.put(orderId, instanceId); ordersToApprove.put(orderId, instanceId);
return instanceId; return instanceId;
@ -189,4 +190,16 @@ public class WorkflowPatternsRestController {
.waitForInstanceCompletion(instanceId, null, true); .waitForInstanceCompletion(instanceId, null, true);
return workflowInstanceStatus.readOutputAs(Decision.class); return workflowInstanceStatus.readOutputAs(Decision.class);
} }
@PostMapping("wfp/durationtimer")
public String durationTimerWorkflow() {
return daprWorkflowClient.scheduleNewWorkflow(DurationTimerWorkflow.class);
} }
@PostMapping("wfp/zoneddatetimetimer")
public String zonedDateTimeTimerWorkflow() {
return daprWorkflowClient.scheduleNewWorkflow(ZonedDateTimeTimerWorkflow.class);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.timer;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;
@Component
public class DurationTimerWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: {}, instanceId: {}", ctx.getName(), ctx.getInstanceId());
ctx.getLogger().info("Let's call the first LogActivity at {}", new Date());
ctx.callActivity(LogActivity.class.getName()).await();
ctx.getLogger().info("Let's schedule a 10 seconds timer at {}", new Date());
ctx.createTimer(Duration.ofSeconds(10)).await();
ctx.getLogger().info("Let's call the second LogActivity at {}", new Date());
ctx.callActivity(LogActivity.class.getName()).await();
ctx.complete(true);
ctx.getLogger().info("Workflow completed at {}", new Date());
};
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.timer;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class LogActivity implements WorkflowActivity {
@Autowired
private TimerLogService logService;
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(LogActivity.class);
Date now = new Date();
logger.info("Running Activity: {} at {}", ctx.getName(), now);
logService.logDate(now);
return true;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.timer;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Component
public class TimerLogService {
private final List<Date> logDates = new ArrayList<>();
public void logDate(Date date){
logDates.add(date);
}
public void clearLog(){
logDates.clear();
}
public List<Date> getLogDates(){
return logDates;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.timer;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
import java.time.ZonedDateTime;
import java.util.Date;
@Component
public class ZonedDateTimeTimerWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: {}, instanceId: {}", ctx.getName(), ctx.getInstanceId());
ctx.getLogger().info("Let's call the first LogActivity at {}", new Date());
ctx.callActivity(LogActivity.class.getName()).await();
ZonedDateTime now = ZonedDateTime.now();
//Let's create a ZonedDateTime 10 seconds in the future
ZonedDateTime inTheFuture = now.plusSeconds(10);
ctx.getLogger().info("Creating a timer that due {} at: {}", inTheFuture, new Date());
ctx.createTimer(inTheFuture).await();
ctx.getLogger().info("The timer fired at: {}", new Date());
ctx.getLogger().info("Let's call the second LogActivity at {}", new Date());
ctx.callActivity(LogActivity.class.getName()).await();
ctx.complete(true);
ctx.getLogger().info("Workflow completed at {}", new Date());
};
}
}

View File

@ -16,6 +16,7 @@ package io.dapr.springboot.examples.wfp;
import io.dapr.springboot.DaprAutoConfiguration; import io.dapr.springboot.DaprAutoConfiguration;
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; import io.dapr.springboot.examples.wfp.remoteendpoint.Payload;
import io.dapr.springboot.examples.wfp.timer.TimerLogService;
import io.dapr.workflows.client.WorkflowRuntimeStatus; import io.dapr.workflows.client.WorkflowRuntimeStatus;
import io.github.microcks.testcontainers.MicrocksContainersEnsemble; import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
import io.restassured.RestAssured; import io.restassured.RestAssured;
@ -25,10 +26,13 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.restassured.RestAssured.given; import static io.restassured.RestAssured.given;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -42,10 +46,14 @@ class WorkflowPatternsAppTests {
@Autowired @Autowired
private MicrocksContainersEnsemble ensemble; private MicrocksContainersEnsemble ensemble;
@Autowired
private TimerLogService logService;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
RestAssured.baseURI = "http://localhost:" + 8080; RestAssured.baseURI = "http://localhost:" + 8080;
org.testcontainers.Testcontainers.exposeHostPorts(8080); org.testcontainers.Testcontainers.exposeHostPorts(8080);
logService.clearLog();
} }
@ -201,4 +209,64 @@ class WorkflowPatternsAppTests {
} }
@Test
void testDurationTimer() throws InterruptedException {
String instanceId = given()
.when()
.post("/wfp/durationtimer")
.then()
.statusCode(200).extract().asString();
assertNotNull(instanceId);
// Check that the workflow completed successfully
await().atMost(Duration.ofSeconds(30))
.pollDelay(500, TimeUnit.MILLISECONDS)
.pollInterval(500, TimeUnit.MILLISECONDS)
.until(() -> {
System.out.println("Log Size: " + logService.getLogDates().size());
if( logService.getLogDates().size() == 2 ) {
long diffInMillis = Math.abs(logService.getLogDates().get(1).getTime() - logService.getLogDates().get(0).getTime());
long diff = TimeUnit.SECONDS.convert(diffInMillis, TimeUnit.MILLISECONDS);
System.out.println("First Log at: " + logService.getLogDates().get(0));
System.out.println("Second Log at: " + logService.getLogDates().get(1));
System.out.println("Diff in seconds: " + diff);
// The updated time differences should be between 9 and 11 seconds
return diff >= 9 && diff <= 11;
}
return false;
});
}
@Test
void testZonedDateTimeTimer() throws InterruptedException {
String instanceId = given()
.when()
.post("/wfp/zoneddatetimetimer")
.then()
.statusCode(200).extract().asString();
assertNotNull(instanceId);
// Check that the workflow completed successfully
await().atMost(Duration.ofSeconds(30))
.pollDelay(500, TimeUnit.MILLISECONDS)
.pollInterval(500, TimeUnit.MILLISECONDS)
.until(() -> {
System.out.println("Log Size: " + logService.getLogDates().size());
if( logService.getLogDates().size() == 2 ) {
long diffInMillis = Math.abs(logService.getLogDates().get(1).getTime() - logService.getLogDates().get(0).getTime());
long diff = TimeUnit.SECONDS.convert(diffInMillis, TimeUnit.MILLISECONDS);
System.out.println("First Log at: " + logService.getLogDates().get(0));
System.out.println("Second Log at: " + logService.getLogDates().get(1));
System.out.println("Diff in seconds: " + diff);
// The updated time differences should be between 9 and 11 seconds
return diff >= 9 && diff <= 11;
}
return false;
});
}
} }