From 7e318b075aa7a67a8f2eef33e28c040e8d7ed54f Mon Sep 17 00:00:00 2001 From: salaboy Date: Tue, 27 May 2025 12:33:18 +0100 Subject: [PATCH 1/9] adding remote endpoint request from inside activity with retry Signed-off-by: salaboy --- spring-boot-examples/workflows/pom.xml | 6 ++ .../wfp/WorkflowPatternsConfiguration.java | 13 ++++ .../wfp/WorkflowPatternsRestController.java | 16 +++++ .../CallRemoteEndpointActivity.java | 51 +++++++++++++++ .../examples/wfp/remoteendpoint/Payload.java | 62 ++++++++++++++++++ .../RemoteEndpointWorkflow.java | 42 +++++++++++++ .../wfp/DaprTestContainersConfig.java | 63 ++++++++++++++++++- .../wfp/WorkflowPatternsAppTests.java | 14 +++++ .../third-parties/remote-http-service.yaml | 63 +++++++++++++++++++ 9 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java create mode 100644 spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java create mode 100644 spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java create mode 100644 spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml diff --git a/spring-boot-examples/workflows/pom.xml b/spring-boot-examples/workflows/pom.xml index 4711b799b..90acdc54d 100644 --- a/spring-boot-examples/workflows/pom.xml +++ b/spring-boot-examples/workflows/pom.xml @@ -40,6 +40,12 @@ rest-assured test + + io.github.microcks + microcks-testcontainers + 0.3.1 + test + diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java index 5ff98f886..45f08ca17 100644 --- a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java @@ -13,9 +13,12 @@ package io.dapr.springboot.examples.wfp; +import com.fasterxml.jackson.databind.ObjectMapper; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; @Configuration public class WorkflowPatternsConfiguration { @@ -23,4 +26,14 @@ public class WorkflowPatternsConfiguration { public CleanUpLog cleanUpLog(){ return new CleanUpLog(); } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplateBuilder().build(); + } + + @Bean + public ObjectMapper mapper() { + return new ObjectMapper(); + } } diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java index 55695821d..ddffdb018 100644 --- a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java @@ -22,6 +22,8 @@ import io.dapr.springboot.examples.wfp.externalevent.ExternalEventWorkflow; import io.dapr.springboot.examples.wfp.fanoutin.FanOutInWorkflow; import io.dapr.springboot.examples.wfp.fanoutin.Result; +import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; +import io.dapr.springboot.examples.wfp.remoteendpoint.RemoteEndpointWorkflow; import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.WorkflowInstanceStatus; import org.slf4j.Logger; @@ -53,6 +55,7 @@ public class WorkflowPatternsRestController { private Map ordersToApprove = new HashMap<>(); + /** * Run Chain Demo Workflow * @return the output of the ChainWorkflow execution @@ -137,4 +140,17 @@ public CleanUpLog continueAsNew() return workflowInstanceStatus.readOutputAs(CleanUpLog.class); } + @PostMapping("wfp/remote-endpoint") + public Payload remoteEndpoint(@RequestBody Payload payload) + throws TimeoutException { + + String instanceId = daprWorkflowClient.scheduleNewWorkflow(RemoteEndpointWorkflow.class, payload); + logger.info("Workflow instance " + instanceId + " started"); + + WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient + .waitForInstanceCompletion(instanceId, null, true); + System.out.printf("workflow instance with ID: %s completed.", instanceId); + return workflowInstanceStatus.readOutputAs(Payload.class); + } + } diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java new file mode 100644 index 000000000..a0636f550 --- /dev/null +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 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.remoteendpoint; + +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.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class CallRemoteEndpointActivity implements WorkflowActivity { + + private Logger logger = LoggerFactory.getLogger(CallRemoteEndpointActivity.class); + + @Value("${application.process-base-url:}") + private String processBaseURL; + + @Autowired + private RestTemplate restTemplate; + + + @Override + public Object run(WorkflowActivityContext ctx) { + logger.info("Starting Activity: " + ctx.getName()); + var payload = ctx.getInput(Payload.class); + + HttpEntity request = + new HttpEntity<>(payload); + payload = restTemplate.postForObject(processBaseURL + "/process", request, Payload.class); + + logger.info("Payload from the remote service: " + payload); + + return payload; + } +} diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java new file mode 100644 index 000000000..fc1b6511d --- /dev/null +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java @@ -0,0 +1,62 @@ +/* + * 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.remoteendpoint; + +public class Payload { + private String id; + private String content; + private Boolean processed = false; + + public Payload(String id, String content) { + this.id = id; + this.content = content; + } + + public Payload() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Boolean getProcessed() { + return processed; + } + + public void setProcessed(Boolean processed) { + this.processed = processed; + } + + @Override + public String toString() { + return "Payload{" + + "id='" + id + '\'' + + ", content='" + content + '\'' + + ", processed=" + processed + + '}'; + } +} diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java new file mode 100644 index 000000000..24bbee672 --- /dev/null +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 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.remoteendpoint; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import io.dapr.workflows.WorkflowTaskOptions; +import io.dapr.workflows.WorkflowTaskRetryPolicy; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +public class RemoteEndpointWorkflow implements Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + + Payload payload = ctx.getInput(Payload.class); + payload = ctx.callActivity(CallRemoteEndpointActivity.class.getName(), payload , + new WorkflowTaskOptions(new WorkflowTaskRetryPolicy(5, + Duration.ofSeconds(2), 1.0, Duration.ofSeconds(10), Duration.ofSeconds(20))) + ,Payload.class).await(); + + ctx.getLogger().info("Workflow finished with result: " + payload); + ctx.complete(payload); + }; + } +} diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java index a0e3a087c..9c0951002 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java @@ -15,11 +15,20 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; +import io.github.microcks.testcontainers.MicrocksContainersEnsemble; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.test.context.DynamicPropertyRegistrar; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.Network; +import org.testcontainers.utility.DockerImageName; import java.util.Collections; +import java.util.List; import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; @@ -28,16 +37,68 @@ public class DaprTestContainersConfig { @Bean @ServiceConnection - public DaprContainer daprContainer() { + public DaprContainer daprContainer(Network network) { return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) .withAppName("workflow-patterns-app") .withComponent(new Component("kvstore", "state.in-memory", "v1", Collections.singletonMap("actorStateStore", String.valueOf(true)))) .withAppPort(8080) + .withNetwork(network) .withAppHealthCheckPath("/actuator/health") .withAppChannelAddress("host.testcontainers.internal"); } + @Bean + MicrocksContainersEnsemble microcksEnsemble(Network network) { + DockerImageName nativeImage = DockerImageName.parse("quay.io/microcks/microcks-uber:1.11.2-native") + .asCompatibleSubstituteFor("quay.io/microcks/microcks-uber:1.9.0"); + return new MicrocksContainersEnsemble(network, nativeImage) + .withAccessToHost(true) // We need this to access our webapp while it runs + .withMainArtifacts("third-parties/remote-http-service.yaml"); + } + + @Bean + public DynamicPropertyRegistrar endpointsProperties(MicrocksContainersEnsemble ensemble) { + // We need to replace the default endpoints with those provided by Microcks. + return (properties) -> { + properties.add("application.process-base-url", () -> ensemble.getMicrocksContainer() + .getRestMockEndpoint("API Payload Processor", "1.0.0")); + }; + } + + @Bean + public Network getDaprNetwork(Environment env) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + if (reuse) { + Network defaultDaprNetwork = new Network() { + @Override + public String getId() { + return "dapr-network"; + } + + @Override + public void close() { + + } + + @Override + public Statement apply(Statement base, Description description) { + return null; + } + }; + + List networks = DockerClientFactory.instance().client().listNetworksCmd() + .withNameFilter("dapr-network").exec(); + if (networks.isEmpty()) { + Network.builder().createNetworkCmdModifier(cmd -> cmd.withName("dapr-network")).build().getId(); + return defaultDaprNetwork; + } else { + return defaultDaprNetwork; + } + } else { + return Network.newNetwork(); + } + } } diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java index 4ca36cb58..87096e464 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java @@ -16,6 +16,7 @@ import io.dapr.client.DaprClient; import io.dapr.springboot.DaprAutoConfiguration; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; +import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; import io.restassured.RestAssured; import io.restassured.http.ContentType; import org.junit.jupiter.api.BeforeEach; @@ -139,4 +140,17 @@ void testContinueAsNew() { assertEquals(5, cleanUpLog.getCleanUpTimes()); } + @Test + void testRemoteEndpoint() { + + Payload payload = given().contentType(ContentType.JSON) + .body(new Payload("123", "content goes here")) + .when() + .post("/wfp/remote-endpoint") + .then() + .statusCode(200).extract().as(Payload.class); + + assertEquals(true, payload.getProcessed()); + } + } diff --git a/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml b/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml new file mode 100644 index 000000000..fe0612e6f --- /dev/null +++ b/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml @@ -0,0 +1,63 @@ +--- +openapi: 3.0.2 +info: + title: API Payload Processor + version: 1.0.0 + description: API definition of API Payload Processor sample app + contact: + name: Salaboy + url: http://github.com/salaboy + email: salaboy@gmail.com + license: + name: MIT License + url: https://opensource.org/licenses/MIT +paths: + /process: + summary: Process payload + post: + tags: + - process + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Payload' + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Payload' + examples: + Payload: + value: + id: 123 + content: payload content here + processed: true + description: Process payload + operationId: Process + summary: Process incoming payload +components: + schemas: + Payload: + title: Payload to be processed + description: Payload to be processed following the Payload type's schema. + type: object + properties: + id: + description: Payload Id + type: string + content: + description: Payload Content + type: string + processed: + description: Is the Payload processed + type: boolean + required: + - id + - content + additionalProperties: false +tags: + - name: payload + description: Payload resource \ No newline at end of file From 64e87af8621a0fa0be3d6b11281c19f485a333e9 Mon Sep 17 00:00:00 2001 From: salaboy Date: Tue, 27 May 2025 15:52:18 +0100 Subject: [PATCH 2/9] adding retry with Microcks payloads Signed-off-by: salaboy --- .../wfp/DaprTestContainersConfig.java | 11 +++++--- .../wfp/WorkflowPatternsAppTests.java | 7 +++++ .../third-parties/remote-http-service.yaml | 26 ++++++++++++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java index 9c0951002..81f404dd2 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java @@ -25,7 +25,6 @@ import org.springframework.test.context.DynamicPropertyRegistrar; import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.Network; -import org.testcontainers.utility.DockerImageName; import java.util.Collections; import java.util.List; @@ -51,11 +50,15 @@ public DaprContainer daprContainer(Network network) { @Bean MicrocksContainersEnsemble microcksEnsemble(Network network) { - DockerImageName nativeImage = DockerImageName.parse("quay.io/microcks/microcks-uber:1.11.2-native") - .asCompatibleSubstituteFor("quay.io/microcks/microcks-uber:1.9.0"); - return new MicrocksContainersEnsemble(network, nativeImage) +// DockerImageName nativeImage = DockerImageName.parse("quay.io/microcks/microcks-uber:1.11.2-native") +// .asCompatibleSubstituteFor("quay.io/microcks/microcks-uber:1.9.0"); + + //new MicrocksContainersEnsemble(network, nativeImage) + MicrocksContainersEnsemble ensemble = new MicrocksContainersEnsemble(network, "quay.io/microcks/microcks-uber:1.11.2") .withAccessToHost(true) // We need this to access our webapp while it runs .withMainArtifacts("third-parties/remote-http-service.yaml"); + return ensemble; + } @Bean diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java index 87096e464..625a621a6 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java @@ -17,6 +17,7 @@ import io.dapr.springboot.DaprAutoConfiguration; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; import io.dapr.springboot.examples.wfp.remoteendpoint.Payload; +import io.github.microcks.testcontainers.MicrocksContainersEnsemble; import io.restassured.RestAssured; import io.restassured.http.ContentType; import org.junit.jupiter.api.BeforeEach; @@ -40,6 +41,9 @@ class WorkflowPatternsAppTests { @Autowired private DaprClient daprClient; + @Autowired + private MicrocksContainersEnsemble ensemble; + @BeforeEach void setUp() { RestAssured.baseURI = "http://localhost:" + 8080; @@ -151,6 +155,9 @@ void testRemoteEndpoint() { .statusCode(200).extract().as(Payload.class); assertEquals(true, payload.getProcessed()); + + assertEquals(2, ensemble.getMicrocksContainer() + .getServiceInvocationsCount("API Payload Processor", "1.0.0")); } } diff --git a/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml b/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml index fe0612e6f..59819a282 100644 --- a/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml +++ b/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml @@ -17,6 +17,16 @@ paths: post: tags: - process + x-microcks-operation: + dispatcher: SCRIPT + dispatcherRules: | + def retries = store.get("retries") ?:"first" + if (retries == "first") { + store.put("retries", "second", 60) + return "Error" + } + store.delete("retries") + return "Payload" requestBody: content: application/json: @@ -36,6 +46,20 @@ paths: content: payload content here processed: true description: Process payload + "500": + content: + application/json: + schema: + type: object + properties: + message: + type: string + description: Error message + examples: + Error: + value: + message: Something unexpected happened + description: Error payload operationId: Process summary: Process incoming payload components: @@ -60,4 +84,4 @@ components: additionalProperties: false tags: - name: payload - description: Payload resource \ No newline at end of file + description: Payload resource From 4beeda4a306129741985a169b87d38fa85756d1e Mon Sep 17 00:00:00 2001 From: salaboy Date: Wed, 28 May 2025 15:25:30 +0100 Subject: [PATCH 3/9] fixing review comments Signed-off-by: salaboy --- pom.xml | 1 + spring-boot-examples/workflows/pom.xml | 2 +- .../RemoteEndpointWorkflow.java | 4 +-- .../wfp/DaprTestContainersConfig.java | 26 ++----------------- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/pom.xml b/pom.xml index c0c67a89d..0c0a92182 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ 1.9.0 2.14.0 3.4.0 + 0.3.1 diff --git a/spring-boot-examples/workflows/pom.xml b/spring-boot-examples/workflows/pom.xml index 90acdc54d..f28c48c0f 100644 --- a/spring-boot-examples/workflows/pom.xml +++ b/spring-boot-examples/workflows/pom.xml @@ -43,7 +43,7 @@ io.github.microcks microcks-testcontainers - 0.3.1 + ${microcks.version} test diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java index 24bbee672..75934e789 100644 --- a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java +++ b/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java @@ -32,8 +32,8 @@ public WorkflowStub create() { Payload payload = ctx.getInput(Payload.class); payload = ctx.callActivity(CallRemoteEndpointActivity.class.getName(), payload , new WorkflowTaskOptions(new WorkflowTaskRetryPolicy(5, - Duration.ofSeconds(2), 1.0, Duration.ofSeconds(10), Duration.ofSeconds(20))) - ,Payload.class).await(); + Duration.ofSeconds(2), 1.0, Duration.ofSeconds(10), Duration.ofSeconds(20))), + Payload.class).await(); ctx.getLogger().info("Workflow finished with result: " + payload); ctx.complete(payload); diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java index 81f404dd2..2df866089 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java @@ -23,11 +23,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.test.context.DynamicPropertyRegistrar; -import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.Network; import java.util.Collections; -import java.util.List; import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; @@ -50,15 +48,9 @@ public DaprContainer daprContainer(Network network) { @Bean MicrocksContainersEnsemble microcksEnsemble(Network network) { -// DockerImageName nativeImage = DockerImageName.parse("quay.io/microcks/microcks-uber:1.11.2-native") -// .asCompatibleSubstituteFor("quay.io/microcks/microcks-uber:1.9.0"); - - //new MicrocksContainersEnsemble(network, nativeImage) - MicrocksContainersEnsemble ensemble = new MicrocksContainersEnsemble(network, "quay.io/microcks/microcks-uber:1.11.2") + return new MicrocksContainersEnsemble(network, "quay.io/microcks/microcks-uber:1.11.2") .withAccessToHost(true) // We need this to access our webapp while it runs .withMainArtifacts("third-parties/remote-http-service.yaml"); - return ensemble; - } @Bean @@ -72,9 +64,7 @@ public DynamicPropertyRegistrar endpointsProperties(MicrocksContainersEnsemble e @Bean public Network getDaprNetwork(Environment env) { - boolean reuse = env.getProperty("reuse", Boolean.class, false); - if (reuse) { - Network defaultDaprNetwork = new Network() { + return new Network() { @Override public String getId() { return "dapr-network"; @@ -90,18 +80,6 @@ public Statement apply(Statement base, Description description) { return null; } }; - - List networks = DockerClientFactory.instance().client().listNetworksCmd() - .withNameFilter("dapr-network").exec(); - if (networks.isEmpty()) { - Network.builder().createNetworkCmdModifier(cmd -> cmd.withName("dapr-network")).build().getId(); - return defaultDaprNetwork; - } else { - return defaultDaprNetwork; - } - } else { - return Network.newNetwork(); - } } } From 69975b7df30073d1743cf5a2ffe2670c49d0d346 Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Wed, 28 May 2025 16:43:48 +0200 Subject: [PATCH 4/9] chore: New task execution task id test (#1352) * chore: New task execution task id test test how taskExecutionTaskId can be used for idempotency Signed-off-by: Javier Aliaga * chore: Clean up not used files Signed-off-by: Javier Aliaga * docs: Task execution keys Signed-off-by: Javier Aliaga * test: Modify unit tests Signed-off-by: Javier Aliaga * Remove new lines Signed-off-by: artur-ciocanu --------- Signed-off-by: Javier Aliaga Signed-off-by: artur-ciocanu Co-authored-by: Cassie Coyle Co-authored-by: artur-ciocanu Signed-off-by: salaboy --- .../java-workflow/java-workflow-howto.md | 43 ++++++++++++-- .../it/testcontainers/DaprWorkflowsIT.java | 25 ++++++++ .../io/dapr/it/testcontainers/KeyStore.java | 51 ++++++++++++++++ .../TaskExecutionKeyActivity.java | 35 +++++++++++ .../TestDaprWorkflowsConfiguration.java | 5 +- .../TestExecutionKeysWorkflow.java | 58 +++++++++++++++++++ .../workflows/WorkflowActivityContext.java | 2 + .../DefaultWorkflowActivityContext.java | 5 ++ .../WorkflowActivityClassWrapperTest.java | 5 +- 9 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java diff --git a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md index 2acf11252..f9afd4813 100644 --- a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md +++ b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md @@ -6,7 +6,7 @@ weight: 20000 description: How to get up and running with workflows using the Dapr Java SDK --- -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: +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: - 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) - 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. == APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001. ``` -## Run the `DemoWorkflowClient +## Run the `DemoWorkflowClient` The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr. - ```java public class DemoWorkflowClient { @@ -246,4 +245,40 @@ Exiting DemoWorkflowClient. ## Next steps - [Learn more about Dapr workflow]({{< ref workflow-overview.md >}}) -- [Workflow API reference]({{< ref workflow_api.md >}}) \ No newline at end of file +- [Workflow API reference]({{< ref workflow_api.md >}}) + +## Advanced features + +### Task Execution Keys + +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: + +1. **Idempotency**: Ensuring activities are not executed multiple times for the same task +2. **State Management**: Tracking the state of activity execution +3. **Error Handling**: Managing retries and failures in a controlled manner + +Here's an example of how to use task execution keys in your workflow activities: + +```java +public class TaskExecutionKeyActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext ctx) { + // Get the task execution key for this activity + String taskExecutionKey = ctx.getTaskExecutionKey(); + + // Use the key to implement idempotency or state management + // For example, check if this task has already been executed + if (isTaskAlreadyExecuted(taskExecutionKey)) { + return getPreviousResult(taskExecutionKey); + } + + // Execute the activity logic + Object result = executeActivityLogic(); + + // Store the result with the task execution key + storeResult(taskExecutionKey, result); + + return result; + } +} +``` diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java index 5c6a360c8..bb1f1c768 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; @@ -40,6 +41,7 @@ import java.util.Map; import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -117,6 +119,29 @@ public void testWorkflows() throws Exception { assertEquals(instanceId, workflowOutput.getWorkflowId()); } + @Test + public void testExecutionKeyWorkflows() throws Exception { + TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>()); + String instanceId = workflowClient.scheduleNewWorkflow(TestExecutionKeysWorkflow.class, payload); + + workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(100), false); + + Duration timeout = Duration.ofSeconds(1000); + WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true); + + assertNotNull(workflowStatus); + + TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput()); + + assertEquals(1, workflowOutput.getPayloads().size()); + assertEquals("Execution key found", workflowOutput.getPayloads().get(0)); + + String executionKey = workflowOutput.getWorkflowId() +"-"+"io.dapr.it.testcontainers.TaskExecutionKeyActivity"; + assertTrue(KeyStore.getInstance().getKey(executionKey)); + + assertEquals(instanceId, workflowOutput.getWorkflowId()); + } + private TestWorkflowPayload deserialize(String value) throws JsonProcessingException { return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class); } diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java new file mode 100644 index 000000000..017e1c50b --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java @@ -0,0 +1,51 @@ +/* + * 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.it.testcontainers; + +import java.util.HashMap; +import java.util.Map; + +public class KeyStore { + + private final Map keyStore = new HashMap<>(); + + private static KeyStore instance; + + private KeyStore() { + } + + public static KeyStore getInstance() { + if (instance == null) { + synchronized (KeyStore.class) { + if (instance == null) { + instance = new KeyStore(); + } + } + } + return instance; + } + + + public void addKey(String key, Boolean value) { + keyStore.put(key, value); + } + + public Boolean getKey(String key) { + return keyStore.get(key); + } + + public void removeKey(String key) { + keyStore.remove(key); + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java new file mode 100644 index 000000000..c1a5b5038 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java @@ -0,0 +1,35 @@ +/* + * 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.it.testcontainers; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; + +public class TaskExecutionKeyActivity implements WorkflowActivity { + + @Override + public Object run(WorkflowActivityContext ctx) { + TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); + KeyStore keyStore = KeyStore.getInstance(); + Boolean exists = keyStore.getKey(ctx.getTaskExecutionKey()); + if (!Boolean.TRUE.equals(exists)) { + keyStore.addKey(ctx.getTaskExecutionKey(), true); + workflowPayload.getPayloads().add("Execution key not found"); + throw new IllegalStateException("Task execution key not found"); + } + workflowPayload.getPayloads().add("Execution key found"); + return workflowPayload; + } + +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java index 0a2487b70..e868b1887 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java @@ -56,9 +56,12 @@ public WorkflowRuntimeBuilder workflowRuntimeBuilder( WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides)); builder.registerWorkflow(TestWorkflow.class); + builder.registerWorkflow(TestExecutionKeysWorkflow.class); builder.registerActivity(FirstActivity.class); builder.registerActivity(SecondActivity.class); - + builder.registerActivity(TaskExecutionKeyActivity.class); + + return builder; } } diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java new file mode 100644 index 000000000..30a9ea33f --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java @@ -0,0 +1,58 @@ +/* + * 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.it.testcontainers; + +import io.dapr.durabletask.Task; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import io.dapr.workflows.WorkflowTaskOptions; +import io.dapr.workflows.WorkflowTaskRetryPolicy; + +import java.time.Duration; + +import org.slf4j.Logger; + +public class TestExecutionKeysWorkflow implements Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + + Logger logger = ctx.getLogger(); + String instanceId = ctx.getInstanceId(); + logger.info("Starting Workflow: " + ctx.getName()); + logger.info("Instance ID: " + instanceId); + logger.info("Current Orchestration Time: " + ctx.getCurrentInstant()); + + TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); + workflowPayload.setWorkflowId(instanceId); + + WorkflowTaskOptions options = new WorkflowTaskOptions(WorkflowTaskRetryPolicy.newBuilder() + .setMaxNumberOfAttempts(3) + .setFirstRetryInterval(Duration.ofSeconds(1)) + .setMaxRetryInterval(Duration.ofSeconds(10)) + .setBackoffCoefficient(2.0) + .setRetryTimeout(Duration.ofSeconds(50)) + .build()); + + + Task t = ctx.callActivity(TaskExecutionKeyActivity.class.getName(), workflowPayload, options,TestWorkflowPayload.class); + + TestWorkflowPayload payloadAfterExecution = t.await(); + + ctx.complete(payloadAfterExecution); + }; + } + +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java index 3fe5d88a2..90a2c41a5 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java @@ -17,6 +17,8 @@ public interface WorkflowActivityContext { String getName(); + String getTaskExecutionKey(); + T getInput(Class targetType); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java index 551c21a37..217c3cd18 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java @@ -56,4 +56,9 @@ public String getName() { public T getInput(Class targetType) { return this.innerContext.getInput(targetType); } + + @Override + public String getTaskExecutionKey() { + return this.innerContext.getTaskExecutionKey(); + } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java index 76a7e07af..81ac492e0 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java @@ -16,7 +16,7 @@ public static class TestActivity implements WorkflowActivity { @Override public Object run(WorkflowActivityContext ctx) { String activityContextName = ctx.getName(); - return ctx.getInput(String.class) + " world! from " + activityContextName; + return ctx.getInput(String.class) + " world! from " + activityContextName + " with task execution key " + ctx.getTaskExecutionKey(); } } @@ -37,10 +37,11 @@ public void createWithClass() { when(mockContext.getInput(String.class)).thenReturn("Hello"); when(mockContext.getName()).thenReturn("TestActivityContext"); + when(mockContext.getTaskExecutionKey()).thenReturn("123"); Object result = wrapper.create().run(mockContext); verify(mockContext, times(1)).getInput(String.class); - assertEquals("Hello world! from TestActivityContext", result); + assertEquals("Hello world! from TestActivityContext with task execution key 123", result); } } From 47b89d32d106dae80188663f0eb019fae8db3f9f Mon Sep 17 00:00:00 2001 From: Javier Aliaga Date: Wed, 28 May 2025 20:14:59 +0200 Subject: [PATCH 5/9] Revert "chore: New task execution task id test (#1352)" (#1389) This reverts commit 949584f69f4bc78ca59abe6ad000f1f2ace21768. Signed-off-by: Javier Aliaga Signed-off-by: salaboy --- .../java-workflow/java-workflow-howto.md | 43 ++------------ .../it/testcontainers/DaprWorkflowsIT.java | 25 -------- .../io/dapr/it/testcontainers/KeyStore.java | 51 ---------------- .../TaskExecutionKeyActivity.java | 35 ----------- .../TestDaprWorkflowsConfiguration.java | 5 +- .../TestExecutionKeysWorkflow.java | 58 ------------------- .../workflows/WorkflowActivityContext.java | 2 - .../DefaultWorkflowActivityContext.java | 5 -- .../WorkflowActivityClassWrapperTest.java | 5 +- 9 files changed, 7 insertions(+), 222 deletions(-) delete mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java delete mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java delete mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java diff --git a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md index f9afd4813..2acf11252 100644 --- a/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md +++ b/daprdocs/content/en/java-sdk-docs/java-workflow/java-workflow-howto.md @@ -6,7 +6,7 @@ weight: 20000 description: How to get up and running with workflows using the Dapr Java SDK --- -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: +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: - 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) - 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,10 +85,11 @@ You're up and running! Both Dapr and your app logs will appear here. == APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001. ``` -## Run the `DemoWorkflowClient` +## Run the `DemoWorkflowClient The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr. + ```java public class DemoWorkflowClient { @@ -245,40 +246,4 @@ Exiting DemoWorkflowClient. ## Next steps - [Learn more about Dapr workflow]({{< ref workflow-overview.md >}}) -- [Workflow API reference]({{< ref workflow_api.md >}}) - -## Advanced features - -### Task Execution Keys - -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: - -1. **Idempotency**: Ensuring activities are not executed multiple times for the same task -2. **State Management**: Tracking the state of activity execution -3. **Error Handling**: Managing retries and failures in a controlled manner - -Here's an example of how to use task execution keys in your workflow activities: - -```java -public class TaskExecutionKeyActivity implements WorkflowActivity { - @Override - public Object run(WorkflowActivityContext ctx) { - // Get the task execution key for this activity - String taskExecutionKey = ctx.getTaskExecutionKey(); - - // Use the key to implement idempotency or state management - // For example, check if this task has already been executed - if (isTaskAlreadyExecuted(taskExecutionKey)) { - return getPreviousResult(taskExecutionKey); - } - - // Execute the activity logic - Object result = executeActivityLogic(); - - // Store the result with the task execution key - storeResult(taskExecutionKey, result); - - return result; - } -} -``` +- [Workflow API reference]({{< ref workflow_api.md >}}) \ No newline at end of file diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java index bb1f1c768..5c6a360c8 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java @@ -15,7 +15,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; - import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; @@ -41,7 +40,6 @@ import java.util.Map; import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG; -import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -119,29 +117,6 @@ public void testWorkflows() throws Exception { assertEquals(instanceId, workflowOutput.getWorkflowId()); } - @Test - public void testExecutionKeyWorkflows() throws Exception { - TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>()); - String instanceId = workflowClient.scheduleNewWorkflow(TestExecutionKeysWorkflow.class, payload); - - workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(100), false); - - Duration timeout = Duration.ofSeconds(1000); - WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true); - - assertNotNull(workflowStatus); - - TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput()); - - assertEquals(1, workflowOutput.getPayloads().size()); - assertEquals("Execution key found", workflowOutput.getPayloads().get(0)); - - String executionKey = workflowOutput.getWorkflowId() +"-"+"io.dapr.it.testcontainers.TaskExecutionKeyActivity"; - assertTrue(KeyStore.getInstance().getKey(executionKey)); - - assertEquals(instanceId, workflowOutput.getWorkflowId()); - } - private TestWorkflowPayload deserialize(String value) throws JsonProcessingException { return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class); } diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java deleted file mode 100644 index 017e1c50b..000000000 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/KeyStore.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.it.testcontainers; - -import java.util.HashMap; -import java.util.Map; - -public class KeyStore { - - private final Map keyStore = new HashMap<>(); - - private static KeyStore instance; - - private KeyStore() { - } - - public static KeyStore getInstance() { - if (instance == null) { - synchronized (KeyStore.class) { - if (instance == null) { - instance = new KeyStore(); - } - } - } - return instance; - } - - - public void addKey(String key, Boolean value) { - keyStore.put(key, value); - } - - public Boolean getKey(String key) { - return keyStore.get(key); - } - - public void removeKey(String key) { - keyStore.remove(key); - } - -} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java deleted file mode 100644 index c1a5b5038..000000000 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TaskExecutionKeyActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.it.testcontainers; - -import io.dapr.workflows.WorkflowActivity; -import io.dapr.workflows.WorkflowActivityContext; - -public class TaskExecutionKeyActivity implements WorkflowActivity { - - @Override - public Object run(WorkflowActivityContext ctx) { - TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); - KeyStore keyStore = KeyStore.getInstance(); - Boolean exists = keyStore.getKey(ctx.getTaskExecutionKey()); - if (!Boolean.TRUE.equals(exists)) { - keyStore.addKey(ctx.getTaskExecutionKey(), true); - workflowPayload.getPayloads().add("Execution key not found"); - throw new IllegalStateException("Task execution key not found"); - } - workflowPayload.getPayloads().add("Execution key found"); - return workflowPayload; - } - -} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java index e868b1887..0a2487b70 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java @@ -56,12 +56,9 @@ public WorkflowRuntimeBuilder workflowRuntimeBuilder( WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides)); builder.registerWorkflow(TestWorkflow.class); - builder.registerWorkflow(TestExecutionKeysWorkflow.class); builder.registerActivity(FirstActivity.class); builder.registerActivity(SecondActivity.class); - builder.registerActivity(TaskExecutionKeyActivity.class); - - + return builder; } } diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java deleted file mode 100644 index 30a9ea33f..000000000 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestExecutionKeysWorkflow.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.it.testcontainers; - -import io.dapr.durabletask.Task; -import io.dapr.workflows.Workflow; -import io.dapr.workflows.WorkflowStub; -import io.dapr.workflows.WorkflowTaskOptions; -import io.dapr.workflows.WorkflowTaskRetryPolicy; - -import java.time.Duration; - -import org.slf4j.Logger; - -public class TestExecutionKeysWorkflow implements Workflow { - - @Override - public WorkflowStub create() { - return ctx -> { - - Logger logger = ctx.getLogger(); - String instanceId = ctx.getInstanceId(); - logger.info("Starting Workflow: " + ctx.getName()); - logger.info("Instance ID: " + instanceId); - logger.info("Current Orchestration Time: " + ctx.getCurrentInstant()); - - TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class); - workflowPayload.setWorkflowId(instanceId); - - WorkflowTaskOptions options = new WorkflowTaskOptions(WorkflowTaskRetryPolicy.newBuilder() - .setMaxNumberOfAttempts(3) - .setFirstRetryInterval(Duration.ofSeconds(1)) - .setMaxRetryInterval(Duration.ofSeconds(10)) - .setBackoffCoefficient(2.0) - .setRetryTimeout(Duration.ofSeconds(50)) - .build()); - - - Task t = ctx.callActivity(TaskExecutionKeyActivity.class.getName(), workflowPayload, options,TestWorkflowPayload.class); - - TestWorkflowPayload payloadAfterExecution = t.await(); - - ctx.complete(payloadAfterExecution); - }; - } - -} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java index 90a2c41a5..3fe5d88a2 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java @@ -17,8 +17,6 @@ public interface WorkflowActivityContext { String getName(); - String getTaskExecutionKey(); - T getInput(Class targetType); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java index 217c3cd18..551c21a37 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowActivityContext.java @@ -56,9 +56,4 @@ public String getName() { public T getInput(Class targetType) { return this.innerContext.getInput(targetType); } - - @Override - public String getTaskExecutionKey() { - return this.innerContext.getTaskExecutionKey(); - } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java index 81ac492e0..76a7e07af 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java @@ -16,7 +16,7 @@ public static class TestActivity implements WorkflowActivity { @Override public Object run(WorkflowActivityContext ctx) { String activityContextName = ctx.getName(); - return ctx.getInput(String.class) + " world! from " + activityContextName + " with task execution key " + ctx.getTaskExecutionKey(); + return ctx.getInput(String.class) + " world! from " + activityContextName; } } @@ -37,11 +37,10 @@ public void createWithClass() { when(mockContext.getInput(String.class)).thenReturn("Hello"); when(mockContext.getName()).thenReturn("TestActivityContext"); - when(mockContext.getTaskExecutionKey()).thenReturn("123"); Object result = wrapper.create().run(mockContext); verify(mockContext, times(1)).getInput(String.class); - assertEquals("Hello world! from TestActivityContext with task execution key 123", result); + assertEquals("Hello world! from TestActivityContext", result); } } From db54d327510f142f006f4b07b59fbb122cb4a315 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Wed, 28 May 2025 15:01:04 -0500 Subject: [PATCH 6/9] 1.5.5 (#1390) Signed-off-by: Cassandra Coyle Signed-off-by: salaboy --- sdk-workflows/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 1b4c704d9..582715467 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -47,7 +47,7 @@ io.dapr durabletask-client - 1.5.4 + 1.5.5 + + org.springframework + spring-web + ${springframework.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${springboot.version} + + + org.springframework.boot + spring-boot-starter + ${springboot.version} + + + org.springframework.boot + spring-boot-autoconfigure-processor + ${springboot.version} + + + io.dapr.spring + dapr-spring-boot-tests + ${dapr.spring.version} + + + org.junit.jupiter junit-jupiter-api ${junit.version} + test + + + org.springframework.boot + spring-boot-starter-test + ${springboot.version} + test org.junit.jupiter junit-jupiter-params ${junit.version} + test org.junit.jupiter junit-jupiter-engine ${junit.version} + test org.junit.jupiter junit-jupiter ${junit.version} + test org.testcontainers junit-jupiter ${testcontainers.version} + test ch.qos.logback @@ -79,46 +170,6 @@ - - - - io.dapr - dapr-sdk - ${dapr.sdk.version} - - - io.dapr - dapr-sdk-actors - ${dapr.sdk.version} - - - - - org.springframework - spring-web - true - - - org.springframework - spring-context - true - - - org.springframework.boot - spring-boot-configuration-processor - ${springboot.version} - true - - - - - org.springframework.boot - spring-boot-starter-test - ${springboot.version} - test - - - diff --git a/pom.xml b/pom.xml index 0c0a92182..9408ad500 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 io.dapr @@ -130,11 +130,11 @@ ${grpc.version} test - - org.jetbrains.kotlin - kotlin-stdlib - 2.1.0 - + + org.jetbrains.kotlin + kotlin-stdlib + 2.1.0 + org.yaml snakeyaml @@ -242,6 +242,11 @@ spring-boot-testcontainers ${springboot.version} + + io.dapr + testcontainers-dapr + ${dapr.sdk.alpha.version} + org.testcontainers toxiproxy diff --git a/spring-boot-examples/consumer-app/pom.xml b/spring-boot-examples/consumer-app/pom.xml index ad64eb49a..8bb5e715d 100644 --- a/spring-boot-examples/consumer-app/pom.xml +++ b/spring-boot-examples/consumer-app/pom.xml @@ -55,6 +55,15 @@ rest-assured test + + org.junit.jupiter + junit-jupiter-api + test + + + org.springframework.boot + spring-boot-starter-test + From 35e7b3a88f89aed42d9cbda8ab06eb1dc777e346 Mon Sep 17 00:00:00 2001 From: salaboy Date: Thu, 29 May 2025 10:05:27 +0100 Subject: [PATCH 9/9] network is needed Signed-off-by: salaboy --- .../examples/wfp/DaprTestContainersConfig.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java index 2df866089..b27900652 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java +++ b/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java @@ -23,9 +23,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.test.context.DynamicPropertyRegistrar; +import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.Network; import java.util.Collections; +import java.util.List; import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; @@ -64,7 +66,9 @@ public DynamicPropertyRegistrar endpointsProperties(MicrocksContainersEnsemble e @Bean public Network getDaprNetwork(Environment env) { - return new Network() { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + if (reuse) { + Network defaultDaprNetwork = new Network() { @Override public String getId() { return "dapr-network"; @@ -80,6 +84,18 @@ public Statement apply(Statement base, Description description) { return null; } }; + + List networks = DockerClientFactory.instance().client().listNetworksCmd() + .withNameFilter("dapr-network").exec(); + if (networks.isEmpty()) { + Network.builder().createNetworkCmdModifier(cmd -> cmd.withName("dapr-network")).build().getId(); + return defaultDaprNetwork; + } else { + return defaultDaprNetwork; + } + } else { + return Network.newNetwork(); + } } }