Skip to content

Commit 823ad9a

Browse files
committed
adding duration and zoneddatetime examples
1 parent 96c8763 commit 823ad9a

File tree

6 files changed

+328
-0
lines changed

6 files changed

+328
-0
lines changed

spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
import io.dapr.springboot.examples.wfp.fanoutin.Result;
2525
import io.dapr.springboot.examples.wfp.remoteendpoint.Payload;
2626
import io.dapr.springboot.examples.wfp.remoteendpoint.RemoteEndpointWorkflow;
27+
//import io.dapr.springboot.examples.wfp.suspendresume.SuspendResumeWorkflow;
28+
import io.dapr.springboot.examples.wfp.timer.DurationTimerWorkflow;
29+
import io.dapr.springboot.examples.wfp.timer.ZonedDateTimeTimerWorkflow;
30+
2731
import io.dapr.workflows.client.DaprWorkflowClient;
2832
import io.dapr.workflows.client.WorkflowInstanceStatus;
2933
import org.slf4j.Logger;
@@ -153,4 +157,50 @@ public Payload remoteEndpoint(@RequestBody Payload payload)
153157
return workflowInstanceStatus.readOutputAs(Payload.class);
154158
}
155159

160+
@PostMapping("wfp/suspendresume")
161+
public String suspendResume(@RequestParam("orderId") String orderId) {
162+
String instanceId = daprWorkflowClient.scheduleNewWorkflow(SuspendResumeWorkflow.class);
163+
logger.info("Workflow instance " + instanceId + " started");
164+
ordersToApprove.put(orderId, instanceId);
165+
return instanceId;
166+
}
167+
168+
@PostMapping("wfp/suspendresume/suspend")
169+
public String suspendResumeExecuteSuspend(@RequestParam("orderId") String orderId) {
170+
String instanceId = ordersToApprove.get(orderId);
171+
daprWorkflowClient.suspendWorkflow(instanceId, "testing suspend");
172+
WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(instanceId, false);
173+
return instanceState.getRuntimeStatus().name();
174+
}
175+
176+
@PostMapping("wfp/suspendresume/resume")
177+
public String suspendResumeExecuteResume(@RequestParam("orderId") String orderId) {
178+
String instanceId = ordersToApprove.get(orderId);
179+
daprWorkflowClient.resumeWorkflow(instanceId, "testing resume");
180+
WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(instanceId, false);
181+
return instanceState.getRuntimeStatus().name();
182+
}
183+
184+
185+
@PostMapping("wfp/suspendresume/continue")
186+
public Decision suspendResumeContinue(@RequestParam("orderId") String orderId, @RequestParam("decision") Boolean decision)
187+
throws TimeoutException {
188+
String instanceId = ordersToApprove.get(orderId);
189+
logger.info("Workflow instance " + instanceId + " continue");
190+
daprWorkflowClient.raiseEvent(instanceId, "Approval", decision);
191+
WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient
192+
.waitForInstanceCompletion(instanceId, null, true);
193+
return workflowInstanceStatus.readOutputAs(Decision.class);
194+
}
195+
196+
@PostMapping("wfp/durationtimer")
197+
public String durationTimerWorkflow() {
198+
return daprWorkflowClient.scheduleNewWorkflow(DurationTimerWorkflow.class);
199+
}
200+
201+
@PostMapping("wfp/zoneddatetimetimer")
202+
public String zonedDateTimeTimerWorkflow() {
203+
return daprWorkflowClient.scheduleNewWorkflow(ZonedDateTimeTimerWorkflow.class);
204+
}
205+
156206
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2025 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.springboot.examples.wfp.timer;
15+
16+
import io.dapr.workflows.Workflow;
17+
import io.dapr.workflows.WorkflowStub;
18+
import org.springframework.stereotype.Component;
19+
20+
import java.time.Duration;
21+
import java.util.Date;
22+
23+
@Component
24+
public class DurationTimerWorkflow implements Workflow {
25+
@Override
26+
public WorkflowStub create() {
27+
return ctx -> {
28+
ctx.getLogger().info("Starting Workflow: {}, instanceId: {}", ctx.getName(), ctx.getInstanceId());
29+
30+
ctx.getLogger().info("Let's call the first LogActivity at {}", new Date());
31+
ctx.callActivity(LogActivity.class.getName()).await();
32+
33+
ctx.getLogger().info("Let's schedule a 10 seconds timer at {}", new Date());
34+
ctx.createTimer(Duration.ofSeconds(10)).await();
35+
36+
ctx.getLogger().info("Let's call the second LogActivity at {}", new Date());
37+
ctx.callActivity(LogActivity.class.getName()).await();
38+
39+
ctx.complete(true);
40+
ctx.getLogger().info("Workflow completed at {}", new Date());
41+
};
42+
}
43+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.springboot.examples.wfp.timer;
15+
16+
import io.dapr.workflows.WorkflowActivity;
17+
import io.dapr.workflows.WorkflowActivityContext;
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.stereotype.Component;
22+
23+
import java.util.Date;
24+
25+
@Component
26+
public class LogActivity implements WorkflowActivity {
27+
28+
@Autowired
29+
private TimerLogService logService;
30+
31+
@Override
32+
public Object run(WorkflowActivityContext ctx) {
33+
Logger logger = LoggerFactory.getLogger(LogActivity.class);
34+
Date now = new Date();
35+
logger.info("Running Activity: {} at {}", ctx.getName(), now);
36+
logService.logDate(now);
37+
return true;
38+
}
39+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2025 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.dapr.springboot.examples.wfp.timer;
14+
15+
import org.springframework.stereotype.Component;
16+
17+
import java.util.ArrayList;
18+
import java.util.Date;
19+
import java.util.List;
20+
21+
@Component
22+
public class TimerLogService {
23+
private final List<Date> logDates = new ArrayList<>();
24+
25+
public void logDate(Date date){
26+
logDates.add(date);
27+
}
28+
29+
public void clearLog(){
30+
logDates.clear();
31+
}
32+
33+
public List<Date> getLogDates(){
34+
return logDates;
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2025 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.springboot.examples.wfp.timer;
15+
16+
import io.dapr.workflows.Workflow;
17+
import io.dapr.workflows.WorkflowStub;
18+
import org.springframework.stereotype.Component;
19+
20+
import java.time.ZonedDateTime;
21+
import java.util.Date;
22+
23+
@Component
24+
public class ZonedDateTimeTimerWorkflow implements Workflow {
25+
@Override
26+
public WorkflowStub create() {
27+
return ctx -> {
28+
ctx.getLogger().info("Starting Workflow: {}, instanceId: {}", ctx.getName(), ctx.getInstanceId());
29+
30+
ctx.getLogger().info("Let's call the first LogActivity at {}", new Date());
31+
ctx.callActivity(LogActivity.class.getName()).await();
32+
33+
ZonedDateTime now = ZonedDateTime.now();
34+
//Let's create a ZonedDateTime 10 seconds in the future
35+
ZonedDateTime inTheFuture = now.plusSeconds(10);
36+
ctx.getLogger().info("Creating a timer that due {} at: {}", inTheFuture, new Date());
37+
ctx.createTimer(inTheFuture).await();
38+
ctx.getLogger().info("The timer fired at: {}", new Date());
39+
40+
ctx.getLogger().info("Let's call the second LogActivity at {}", new Date());
41+
ctx.callActivity(LogActivity.class.getName()).await();
42+
43+
ctx.complete(true);
44+
ctx.getLogger().info("Workflow completed at {}", new Date());
45+
};
46+
}
47+
}

spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import io.dapr.springboot.DaprAutoConfiguration;
1818
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
1919
import io.dapr.springboot.examples.wfp.remoteendpoint.Payload;
20+
import io.dapr.springboot.examples.wfp.timer.TimerLogService;
21+
import io.dapr.workflows.client.WorkflowRuntimeStatus;
2022
import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
2123
import io.restassured.RestAssured;
2224
import io.restassured.http.ContentType;
@@ -25,13 +27,17 @@
2527
import org.springframework.beans.factory.annotation.Autowired;
2628
import org.springframework.boot.test.context.SpringBootTest;
2729

30+
import java.time.Duration;
2831
import java.util.Arrays;
2932
import java.util.List;
33+
import java.util.concurrent.TimeUnit;
3034

3135
import static io.restassured.RestAssured.given;
36+
import static org.awaitility.Awaitility.await;
3237
import static org.hamcrest.CoreMatchers.containsString;
3338
import static org.hamcrest.Matchers.equalTo;
3439
import static org.junit.jupiter.api.Assertions.assertEquals;
40+
import static org.junit.jupiter.api.Assertions.assertNotNull;
3541

3642
@SpringBootTest(classes = {TestWorkflowPatternsApplication.class, DaprTestContainersConfig.class,
3743
DaprAutoConfiguration.class, },
@@ -44,10 +50,14 @@ class WorkflowPatternsAppTests {
4450
@Autowired
4551
private MicrocksContainersEnsemble ensemble;
4652

53+
@Autowired
54+
private TimerLogService logService;
55+
4756
@BeforeEach
4857
void setUp() {
4958
RestAssured.baseURI = "http://localhost:" + 8080;
5059
org.testcontainers.Testcontainers.exposeHostPorts(8080);
60+
logService.clearLog();
5161
}
5262

5363

@@ -160,4 +170,107 @@ void testRemoteEndpoint() {
160170
.getServiceInvocationsCount("API Payload Processor", "1.0.0"));
161171
}
162172

173+
@Test
174+
void testSuspendResume() {
175+
176+
String instanceId = given()
177+
.queryParam("orderId", "123")
178+
.when()
179+
.post("/wfp/suspendresume")
180+
.then()
181+
.statusCode(200).extract().asString();
182+
183+
assertNotNull(instanceId);
184+
185+
// The workflow is waiting on an event, let's suspend the workflow
186+
String state = given()
187+
.queryParam("orderId", "123")
188+
.when()
189+
.post("/wfp/suspendresume/suspend")
190+
.then()
191+
.statusCode(200).extract().asString();
192+
193+
assertEquals(WorkflowRuntimeStatus.SUSPENDED.name(), state);
194+
195+
// The let's resume the suspended workflow and check the state
196+
state = given()
197+
.queryParam("orderId", "123")
198+
.when()
199+
.post("/wfp/suspendresume/resume")
200+
.then()
201+
.statusCode(200).extract().asString();
202+
203+
assertEquals(WorkflowRuntimeStatus.RUNNING.name(), state);
204+
205+
// Now complete the workflow by sending an event
206+
given()
207+
.queryParam("orderId", "123")
208+
.queryParam("decision", false)
209+
.when()
210+
.post("/wfp/suspendresume/continue")
211+
.then()
212+
.statusCode(200).body("approved", equalTo(false));
213+
214+
}
215+
216+
@Test
217+
void testDurationTimer() throws InterruptedException {
218+
219+
String instanceId = given()
220+
.when()
221+
.post("/wfp/durationtimer")
222+
.then()
223+
.statusCode(200).extract().asString();
224+
225+
assertNotNull(instanceId);
226+
227+
// Check that the workflow completed successfully
228+
await().atMost(Duration.ofSeconds(30))
229+
.pollDelay(500, TimeUnit.MILLISECONDS)
230+
.pollInterval(500, TimeUnit.MILLISECONDS)
231+
.until(() -> {
232+
System.out.println("Log Size: " + logService.getLogDates().size());
233+
if( logService.getLogDates().size() == 2 ) {
234+
long diffInMillis = Math.abs(logService.getLogDates().get(1).getTime() - logService.getLogDates().get(0).getTime());
235+
long diff = TimeUnit.SECONDS.convert(diffInMillis, TimeUnit.MILLISECONDS);
236+
System.out.println("First Log at: " + logService.getLogDates().get(0));
237+
System.out.println("Second Log at: " + logService.getLogDates().get(1));
238+
System.out.println("Diff in seconds: " + diff);
239+
// The updated time differences should be between 9 and 11 seconds
240+
return diff >= 9 && diff <= 11;
241+
}
242+
return false;
243+
});
244+
}
245+
246+
@Test
247+
void testZonedDateTimeTimer() throws InterruptedException {
248+
249+
String instanceId = given()
250+
.when()
251+
.post("/wfp/zoneddatetimetimer")
252+
.then()
253+
.statusCode(200).extract().asString();
254+
255+
assertNotNull(instanceId);
256+
257+
// Check that the workflow completed successfully
258+
await().atMost(Duration.ofSeconds(30))
259+
.pollDelay(500, TimeUnit.MILLISECONDS)
260+
.pollInterval(500, TimeUnit.MILLISECONDS)
261+
.until(() -> {
262+
System.out.println("Log Size: " + logService.getLogDates().size());
263+
if( logService.getLogDates().size() == 2 ) {
264+
long diffInMillis = Math.abs(logService.getLogDates().get(1).getTime() - logService.getLogDates().get(0).getTime());
265+
long diff = TimeUnit.SECONDS.convert(diffInMillis, TimeUnit.MILLISECONDS);
266+
System.out.println("First Log at: " + logService.getLogDates().get(0));
267+
System.out.println("Second Log at: " + logService.getLogDates().get(1));
268+
System.out.println("Diff in seconds: " + diff);
269+
// The updated time differences should be between 9 and 11 seconds
270+
return diff >= 9 && diff <= 11;
271+
}
272+
return false;
273+
});
274+
}
275+
163276
}

0 commit comments

Comments
 (0)