Skip to content

Commit 949584f

Browse files
javier-aliagacicoyleartur-ciocanu
authored
chore: New task execution task id test (dapr#1352)
* chore: New task execution task id test test how taskExecutionTaskId can be used for idempotency Signed-off-by: Javier Aliaga <javier@diagrid.io> * chore: Clean up not used files Signed-off-by: Javier Aliaga <javier@diagrid.io> * docs: Task execution keys Signed-off-by: Javier Aliaga <javier@diagrid.io> * test: Modify unit tests Signed-off-by: Javier Aliaga <javier@diagrid.io> * Remove new lines Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com> --------- Signed-off-by: Javier Aliaga <javier@diagrid.io> Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com> Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com> Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
1 parent a99d286 commit 949584f

File tree

9 files changed

+222
-7
lines changed

9 files changed

+222
-7
lines changed

daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ weight: 20000
66
description: How to get up and running with workflows using the Dapr Java SDK
77
---
88

9-
Lets create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows), you will:
9+
Let's create a Dapr workflow and invoke it using the console. With the [provided workflow example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows), you will:
1010

1111
- Execute the workflow instance using the [Java workflow worker](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java)
1212
- Utilize the Java workflow client and API calls to [start and terminate workflow instances](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java)
@@ -85,11 +85,10 @@ You're up and running! Both Dapr and your app logs will appear here.
8585
== APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001.
8686
```
8787

88-
## Run the `DemoWorkflowClient
88+
## Run the `DemoWorkflowClient`
8989

9090
The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr.
9191

92-
9392
```java
9493
public class DemoWorkflowClient {
9594

@@ -246,4 +245,40 @@ Exiting DemoWorkflowClient.
246245

247246
## Next steps
248247
- [Learn more about Dapr workflow]({{< ref workflow-overview.md >}})
249-
- [Workflow API reference]({{< ref workflow_api.md >}})
248+
- [Workflow API reference]({{< ref workflow_api.md >}})
249+
250+
## Advanced features
251+
252+
### Task Execution Keys
253+
254+
Task execution keys are unique identifiers generated by the durabletask-java library. They are stored in the `WorkflowActivityContext` and can be used to track and manage the execution of workflow activities. They are particularly useful for:
255+
256+
1. **Idempotency**: Ensuring activities are not executed multiple times for the same task
257+
2. **State Management**: Tracking the state of activity execution
258+
3. **Error Handling**: Managing retries and failures in a controlled manner
259+
260+
Here's an example of how to use task execution keys in your workflow activities:
261+
262+
```java
263+
public class TaskExecutionKeyActivity implements WorkflowActivity {
264+
@Override
265+
public Object run(WorkflowActivityContext ctx) {
266+
// Get the task execution key for this activity
267+
String taskExecutionKey = ctx.getTaskExecutionKey();
268+
269+
// Use the key to implement idempotency or state management
270+
// For example, check if this task has already been executed
271+
if (isTaskAlreadyExecuted(taskExecutionKey)) {
272+
return getPreviousResult(taskExecutionKey);
273+
}
274+
275+
// Execute the activity logic
276+
Object result = executeActivityLogic();
277+
278+
// Store the result with the task execution key
279+
storeResult(taskExecutionKey, result);
280+
281+
return result;
282+
}
283+
}
284+
```

sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import com.fasterxml.jackson.core.JsonProcessingException;
1717
import com.fasterxml.jackson.databind.ObjectMapper;
18+
1819
import io.dapr.testcontainers.Component;
1920
import io.dapr.testcontainers.DaprContainer;
2021
import io.dapr.testcontainers.DaprLogLevel;
@@ -40,6 +41,7 @@
4041
import java.util.Map;
4142

4243
import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
44+
import static org.junit.Assert.assertTrue;
4345
import static org.junit.jupiter.api.Assertions.assertEquals;
4446
import static org.junit.jupiter.api.Assertions.assertNotNull;
4547

@@ -117,6 +119,29 @@ public void testWorkflows() throws Exception {
117119
assertEquals(instanceId, workflowOutput.getWorkflowId());
118120
}
119121

122+
@Test
123+
public void testExecutionKeyWorkflows() throws Exception {
124+
TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
125+
String instanceId = workflowClient.scheduleNewWorkflow(TestExecutionKeysWorkflow.class, payload);
126+
127+
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(100), false);
128+
129+
Duration timeout = Duration.ofSeconds(1000);
130+
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true);
131+
132+
assertNotNull(workflowStatus);
133+
134+
TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput());
135+
136+
assertEquals(1, workflowOutput.getPayloads().size());
137+
assertEquals("Execution key found", workflowOutput.getPayloads().get(0));
138+
139+
String executionKey = workflowOutput.getWorkflowId() +"-"+"io.dapr.it.testcontainers.TaskExecutionKeyActivity";
140+
assertTrue(KeyStore.getInstance().getKey(executionKey));
141+
142+
assertEquals(instanceId, workflowOutput.getWorkflowId());
143+
}
144+
120145
private TestWorkflowPayload deserialize(String value) throws JsonProcessingException {
121146
return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class);
122147
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.it.testcontainers;
14+
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
public class KeyStore {
19+
20+
private final Map<String, Boolean> keyStore = new HashMap<>();
21+
22+
private static KeyStore instance;
23+
24+
private KeyStore() {
25+
}
26+
27+
public static KeyStore getInstance() {
28+
if (instance == null) {
29+
synchronized (KeyStore.class) {
30+
if (instance == null) {
31+
instance = new KeyStore();
32+
}
33+
}
34+
}
35+
return instance;
36+
}
37+
38+
39+
public void addKey(String key, Boolean value) {
40+
keyStore.put(key, value);
41+
}
42+
43+
public Boolean getKey(String key) {
44+
return keyStore.get(key);
45+
}
46+
47+
public void removeKey(String key) {
48+
keyStore.remove(key);
49+
}
50+
51+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.it.testcontainers;
15+
16+
import io.dapr.workflows.WorkflowActivity;
17+
import io.dapr.workflows.WorkflowActivityContext;
18+
19+
public class TaskExecutionKeyActivity implements WorkflowActivity {
20+
21+
@Override
22+
public Object run(WorkflowActivityContext ctx) {
23+
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
24+
KeyStore keyStore = KeyStore.getInstance();
25+
Boolean exists = keyStore.getKey(ctx.getTaskExecutionKey());
26+
if (!Boolean.TRUE.equals(exists)) {
27+
keyStore.addKey(ctx.getTaskExecutionKey(), true);
28+
workflowPayload.getPayloads().add("Execution key not found");
29+
throw new IllegalStateException("Task execution key not found");
30+
}
31+
workflowPayload.getPayloads().add("Execution key found");
32+
return workflowPayload;
33+
}
34+
35+
}

sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ public WorkflowRuntimeBuilder workflowRuntimeBuilder(
5656
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides));
5757

5858
builder.registerWorkflow(TestWorkflow.class);
59+
builder.registerWorkflow(TestExecutionKeysWorkflow.class);
5960
builder.registerActivity(FirstActivity.class);
6061
builder.registerActivity(SecondActivity.class);
61-
62+
builder.registerActivity(TaskExecutionKeyActivity.class);
63+
64+
6265
return builder;
6366
}
6467
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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.it.testcontainers;
15+
16+
import io.dapr.durabletask.Task;
17+
import io.dapr.workflows.Workflow;
18+
import io.dapr.workflows.WorkflowStub;
19+
import io.dapr.workflows.WorkflowTaskOptions;
20+
import io.dapr.workflows.WorkflowTaskRetryPolicy;
21+
22+
import java.time.Duration;
23+
24+
import org.slf4j.Logger;
25+
26+
public class TestExecutionKeysWorkflow implements Workflow {
27+
28+
@Override
29+
public WorkflowStub create() {
30+
return ctx -> {
31+
32+
Logger logger = ctx.getLogger();
33+
String instanceId = ctx.getInstanceId();
34+
logger.info("Starting Workflow: " + ctx.getName());
35+
logger.info("Instance ID: " + instanceId);
36+
logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());
37+
38+
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
39+
workflowPayload.setWorkflowId(instanceId);
40+
41+
WorkflowTaskOptions options = new WorkflowTaskOptions(WorkflowTaskRetryPolicy.newBuilder()
42+
.setMaxNumberOfAttempts(3)
43+
.setFirstRetryInterval(Duration.ofSeconds(1))
44+
.setMaxRetryInterval(Duration.ofSeconds(10))
45+
.setBackoffCoefficient(2.0)
46+
.setRetryTimeout(Duration.ofSeconds(50))
47+
.build());
48+
49+
50+
Task<TestWorkflowPayload> t = ctx.callActivity(TaskExecutionKeyActivity.class.getName(), workflowPayload, options,TestWorkflowPayload.class);
51+
52+
TestWorkflowPayload payloadAfterExecution = t.await();
53+
54+
ctx.complete(payloadAfterExecution);
55+
};
56+
}
57+
58+
}

sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public interface WorkflowActivityContext {
1717

1818
String getName();
1919

20+
String getTaskExecutionKey();
21+
2022
<T> T getInput(Class<T> targetType);
2123

2224
}

sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ public String getName() {
5656
public <T> T getInput(Class<T> targetType) {
5757
return this.innerContext.getInput(targetType);
5858
}
59+
60+
@Override
61+
public String getTaskExecutionKey() {
62+
return this.innerContext.getTaskExecutionKey();
63+
}
5964
}

sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static class TestActivity implements WorkflowActivity {
1616
@Override
1717
public Object run(WorkflowActivityContext ctx) {
1818
String activityContextName = ctx.getName();
19-
return ctx.getInput(String.class) + " world! from " + activityContextName;
19+
return ctx.getInput(String.class) + " world! from " + activityContextName + " with task execution key " + ctx.getTaskExecutionKey();
2020
}
2121
}
2222

@@ -37,10 +37,11 @@ public void createWithClass() {
3737

3838
when(mockContext.getInput(String.class)).thenReturn("Hello");
3939
when(mockContext.getName()).thenReturn("TestActivityContext");
40+
when(mockContext.getTaskExecutionKey()).thenReturn("123");
4041

4142
Object result = wrapper.create().run(mockContext);
4243

4344
verify(mockContext, times(1)).getInput(String.class);
44-
assertEquals("Hello world! from TestActivityContext", result);
45+
assertEquals("Hello world! from TestActivityContext with task execution key 123", result);
4546
}
4647
}

0 commit comments

Comments
 (0)