diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c89b2f9a84..b97c9717d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: distribution: 'temurin' java-version: ${{ env.JDK_VER }} - name: Run tests - run: ./mvnw clean install -B -q + run: ./mvnw clean install -B -q -DskipITs=true - name: Codecov uses: codecov/codecov-action@v5.5.1 - name: Upload test report for sdk diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 0a73978c7a..e73bcf4a1d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -123,8 +123,8 @@ jobs: mm.py README.md env: DOCKER_HOST: ${{steps.setup_docker.outputs.sock}} - - name: Validate Spring Boot Workflow examples - working-directory: ./spring-boot-examples/workflows + - name: Validate Spring Boot Workflow Patterns examples + working-directory: ./spring-boot-examples/workflows/patterns run: | mm.py README.md env: diff --git a/pom.xml b/pom.xml index 0c812dad80..8c9caa57e1 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ true ../spotbugs-exclude.xml --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED - 3.2.2 + 3.5.3 3.2.2 5.11.4 2.0 @@ -665,6 +665,7 @@ integration-tests sdk-tests + spring-boot-examples diff --git a/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTests.java b/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java similarity index 99% rename from spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTests.java rename to spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java index e7e6f0c032..5334f49520 100644 --- a/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTests.java +++ b/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java @@ -37,7 +37,7 @@ @SpringBootTest(classes = {TestConsumerApplication.class, DaprTestContainersConfig.class, ConsumerAppTestConfiguration.class, DaprAutoConfiguration.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -class ConsumerAppTests { +class ConsumerAppIT { private static final String SUBSCRIPTION_MESSAGE_PATTERN = ".*app is subscribed to the following topics.*"; diff --git a/spring-boot-examples/pom.xml b/spring-boot-examples/pom.xml index 8849630b1a..69cbd693e6 100644 --- a/spring-boot-examples/pom.xml +++ b/spring-boot-examples/pom.xml @@ -52,6 +52,23 @@ true + + org.apache.maven.plugins + maven-failsafe-plugin + + ${project.build.outputDirectory} + + + + org.jacoco + jacoco-maven-plugin + + + + **/*io/dapr/springboot/examples/** + + + diff --git a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppTests.java b/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java similarity index 99% rename from spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppTests.java rename to spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java index 47e5eb4862..24fa34c6fb 100644 --- a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppTests.java +++ b/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java @@ -40,7 +40,7 @@ DaprAutoConfiguration.class, CustomerWorkflow.class, CustomerFollowupActivity.class, RegisterCustomerActivity.class, CustomerStore.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -class ProducerAppTests { +class ProducerAppIT { private static final String SUBSCRIPTION_MESSAGE_PATTERN = ".*app is subscribed to the following topics.*"; diff --git a/spring-boot-examples/workflows/multi-app/README.md b/spring-boot-examples/workflows/multi-app/README.md new file mode 100644 index 0000000000..108cdfe7ce --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/README.md @@ -0,0 +1,91 @@ +# Multi App workflow Example + +This example demonstrates how you can create distributed workflows where the orchestrator doesn't host the workflow activities. + +For more documentation about how Multi App Workflows work [check the official documentation here](https://v1-16.docs.dapr.io/developing-applications/building-blocks/workflow/workflow-multi-app/). + +This example is composed by three Spring Boot applications: +- `orchestrator`: The `orchestrator` app contains the Dapr Workflow definition and expose REST endpoints to create and raise events against workflow instances. +- `worker-one`: The `worker-one` app contains the `RegisterCustomerActivity` definition, which will be called by the `orchestrator` app. +- `worker-two`: The `worker-two` app contains the `CustomerFollowupActivity` definition, which will be called by the `orchestrator` app. + +To start the applications you need to run the following commands on separate terminals, starting from the `multi-app` directory. +To start the `orchestrator` app run: +```bash +cd orchestrator/ +mvn -Dspring-boot.run.arguments="--reuse=true" clean spring-boot:test-run +``` + +The `orchestrator` application will run on port `8080`. + +On a separate terminal, to start the `worker-one` app run: +```bash +cd worker-one/ +mvn -Dspring-boot.run.arguments="--reuse=true" clean spring-boot:test-run +``` + +The `worker-one` application will run on port `8081`. + +On a separate terminal, to start the `worker-two` app run: +```bash +cd worker-two/ +mvn -Dspring-boot.run.arguments="--reuse=true" clean spring-boot:test-run +``` + +The `worker-two` application will run on port `8082`. + +You can create new workflow instances of the `CustomerWorkflow` by calling the `/customers` endpoint of the `orchestrator` application. + +```bash +curl -X POST localhost:8080/customers -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }' +``` + +The workflow definition [`CustomerWorkflow`](orchstrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomerWorkflow.java) that you can find inside the `orchestrator` app, +performs the following orchestration when a new workflow instance is created: + +- Call the `RegisterCustomerActivity` activity which can be found inside the `worker-one` application. + - You can find in the workflow definition the configuration to make reference to an Activity that is hosted by a different Dapr application. + ```java + customer = ctx.callActivity("io.dapr.springboot.examples.workerone.RegisterCustomerActivity", + customer, + new WorkflowTaskOptions("worker-one"), + Customer.class). + await(); + ``` +- Wait for an external event of type `CustomerReachOut` with a timeout of 5 minutes: + ```java + ctx.waitForExternalEvent("CustomerReachOut", Duration.ofMinutes(5), Customer.class).await(); + ``` +- You can check the status of the workflow for a given customer by sending the following request: + ```shell + curl -X POST localhost:8080/customers/status -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }' + ``` +- You can call the following endpoint on the `orchestrator` app to raise the external event: + ```shell + curl -X POST localhost:8080/customers/followup -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }' + ``` +- When the event is received, the workflow move forward to the last activity called `CustomerFollowUpActivity`, that can be found on the `worker-two` app. + ```java + customer = ctx.callActivity("io.dapr.springboot.examples.workertwo.CustomerFollowupActivity", + customer, + new WorkflowTaskOptions("worker-two"), + Customer.class). + await(); + ``` +- The workflow completes by handing out the final version of the `Customer` object that has been modified the workflow activities. You can retrieve the `Customer` payload + by running the following command: + ```shell + curl -X POST localhost:8080/customers/output -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }' + ``` + +## Testing Multi App Workflows + +Testing becomes a complex task when you are dealing with multiple Spring Boot applications. For testing this workflow, +we rely on [Testcontainers](https://testcontainers.com) to create the entire setup which enable us to run the workflow end to end. + +You can find the end-to-end test in the [`OrchestratorAppIT.java`](orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/OrchestratorAppIT.java) class inside the `orchestrator` application. +This test interact with the application REST endpoints to validate their correct execution. + +But the magic behind the test can be located in the [`DaprTestContainersConfig.class`](orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/DaprTestContainersConfig.java) which defines the configuration for +all the Dapr containers and the `worker-one` and `worker-two` applications. Check this class to gain a deeper understand how to configure +multiple Dapr-enabled applications. diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/pom.xml b/spring-boot-examples/workflows/multi-app/orchestrator/pom.xml new file mode 100644 index 0000000000..c5907d373c --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + + io.dapr + multi-app + 1.17.0-SNAPSHOT + + + orchestrator + orchestrator + Spring Boot, Testcontainers and Dapr Integration Examples :: Orchestrator App + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + io.dapr.spring + dapr-spring-boot-starter + + + io.dapr.spring + dapr-spring-boot-starter-test + test + + + com.redis + testcontainers-redis + 2.2.2 + test + + + io.rest-assured + rest-assured + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + true + + + + + diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/Customer.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/Customer.java new file mode 100644 index 0000000000..55fd25dc99 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/Customer.java @@ -0,0 +1,59 @@ +/* + * 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.orchestrator; + +public class Customer { + private String customerName; + private String workflowId; + private boolean inCustomerDB = false; + private boolean followUp = false; + + public boolean isFollowUp() { + return followUp; + } + + public void setFollowUp(boolean followUp) { + this.followUp = followUp; + } + + public boolean isInCustomerDB() { + return inCustomerDB; + } + + public void setInCustomerDB(boolean inCustomerDB) { + this.inCustomerDB = inCustomerDB; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + @Override + public String toString() { + return "Customer [customerName=" + customerName + ", workflowId=" + workflowId + ", inCustomerDB=" + + inCustomerDB + ", followUp=" + followUp + "]"; + } +} diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomerWorkflow.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomerWorkflow.java new file mode 100644 index 0000000000..0e124d4a41 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomerWorkflow.java @@ -0,0 +1,52 @@ +/* + * 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.orchestrator; + +import io.dapr.durabletask.TaskCanceledException; +import io.dapr.durabletask.TaskFailedException; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import io.dapr.workflows.WorkflowTaskOptions; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +public class CustomerWorkflow implements Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + String instanceId = ctx.getInstanceId(); + Customer customer = ctx.getInput(Customer.class); + customer.setWorkflowId(instanceId); + ctx.getLogger().info("Let's register the customer: {}", customer.getCustomerName()); + + customer = ctx.callActivity("io.dapr.springboot.examples.workerone.RegisterCustomerActivity", customer, + new WorkflowTaskOptions("worker-one"), Customer.class).await(); + + ctx.getLogger().info("Let's wait for the customer: {} to request a follow up.", customer.getCustomerName()); + ctx.waitForExternalEvent("CustomerReachOut", Duration.ofMinutes(5), Customer.class).await(); + + ctx.getLogger().info("Let's book a follow up for the customer: {}", customer.getCustomerName()); + customer = ctx.callActivity("io.dapr.springboot.examples.workertwo.CustomerFollowupActivity", + customer, new WorkflowTaskOptions("worker-two"), Customer.class).await(); + + ctx.getLogger().info("Congratulations the customer: {} is happy!", customer.getCustomerName()); + + ctx.getLogger().info("Final customer: {} ", customer); + ctx.complete(customer); + }; + } +} diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomersRestController.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomersRestController.java new file mode 100644 index 0000000000..46277ada97 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/CustomersRestController.java @@ -0,0 +1,110 @@ +/* + * 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.orchestrator; + +import io.dapr.spring.workflows.config.EnableDaprWorkflows; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +public class CustomersRestController { + + private final Logger logger = LoggerFactory.getLogger(CustomersRestController.class); + + @Autowired + private DaprWorkflowClient daprWorkflowClient; + + @GetMapping("/") + public String root() { + return "OK"; + } + + private final Map customersWorkflows = new HashMap<>(); + + /** + * Track customer endpoint. + * + * @param customer provided customer to track + * @return confirmation that the workflow instance was created for a given customer + */ + @PostMapping("/customers") + public String trackCustomer(@RequestBody Customer customer) { + String instanceId = daprWorkflowClient.scheduleNewWorkflow(CustomerWorkflow.class, customer); + logger.info("Workflow instance {} started", instanceId); + customersWorkflows.put(customer.getCustomerName(), instanceId); + return "New Workflow Instance created for Customer: " + customer.getCustomerName(); + } + + /** + * Request customer follow-up. + * @param customer associated with a workflow instance + * @return confirmation that the follow-up was requested + */ + @PostMapping("/customers/followup") + public String customerNotification(@RequestBody Customer customer) { + logger.info("Customer follow-up requested: {}", customer.getCustomerName()); + String workflowIdForCustomer = customersWorkflows.get(customer.getCustomerName()); + if (workflowIdForCustomer == null || workflowIdForCustomer.isEmpty()) { + return "There is no workflow associated with customer: " + customer.getCustomerName(); + } + daprWorkflowClient.raiseEvent(workflowIdForCustomer, "CustomerReachOut", customer); + return "Customer Follow-up requested"; + } + + /** + * Request customer workflow instance status. + * @param customer associated with a workflow instance + * @return the workflow instance status for a given customer + */ + @PostMapping("/customers/status") + public String getCustomerStatus(@RequestBody Customer customer) { + logger.info("Customer status requested: {}", customer.getCustomerName()); + String workflowIdForCustomer = customersWorkflows.get(customer.getCustomerName()); + if (workflowIdForCustomer == null || workflowIdForCustomer.isEmpty()) { + return "N/A"; + } + WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(workflowIdForCustomer, true); + assert instanceState != null; + return "Workflow for Customer: " + customer.getCustomerName() + " is " + instanceState.getRuntimeStatus().name(); + } + + /** + * Request customer output. + * @param customer associated with a workflow instance + * @return Customer status after the workflow execution finished + */ + @PostMapping("/customers/output") + public Customer getCustomerOutput(@RequestBody Customer customer) { + logger.info("Customer output requested: {}", customer.getCustomerName()); + String workflowIdForCustomer = customersWorkflows.get(customer.getCustomerName()); + if (workflowIdForCustomer == null || workflowIdForCustomer.isEmpty()) { + return null; + } + WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(workflowIdForCustomer, true); + assert instanceState != null; + return instanceState.readOutputAs(Customer.class); + } + +} + diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/OrchestratorApplication.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/OrchestratorApplication.java new file mode 100644 index 0000000000..8ba4ec897e --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/java/io/dapr/springboot/examples/orchestrator/OrchestratorApplication.java @@ -0,0 +1,29 @@ +/* + * 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.orchestrator; + +import io.dapr.spring.workflows.config.EnableDaprWorkflows; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +@EnableDaprWorkflows +public class OrchestratorApplication { + + public static void main(String[] args) { + SpringApplication.run(OrchestratorApplication.class, args); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/main/resources/application.properties b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/resources/application.properties new file mode 100644 index 0000000000..3d555d71e5 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=orchestrator \ No newline at end of file diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/DaprTestContainersConfig.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/DaprTestContainersConfig.java new file mode 100644 index 0000000000..efdb511c8f --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/DaprTestContainersConfig.java @@ -0,0 +1,214 @@ +/* + * 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.orchestrator; + +import com.redis.testcontainers.RedisContainer; +import io.dapr.testcontainers.*; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +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.testcontainers.DockerClientFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.dapr.testcontainers.DaprContainerConstants.*; + +@TestConfiguration(proxyBeanMethods = false) +public class DaprTestContainersConfig { + + @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(); + } + } + + private Map getRedisProps(){ + Map redisProps = new HashMap<>(); + redisProps.put("redisHost", "redis:6379"); + redisProps.put("redisPassword", ""); + redisProps.put("actorStateStore", String.valueOf(true)); + return redisProps; + } + + @Bean + public DaprPlacementContainer placementContainer(Network daprNetwork, Environment env){ + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new DaprPlacementContainer(DockerImageName.parse(DAPR_PLACEMENT_IMAGE_TAG)) + .withNetwork(daprNetwork) + .withReuse(reuse) + .withNetworkAliases("placement"); + } + + @Bean + public DaprSchedulerContainer schedulerContainer(Network daprNetwork, Environment env){ + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new DaprSchedulerContainer(DockerImageName.parse(DAPR_SCHEDULER_IMAGE_TAG)) + .withNetwork(daprNetwork) + .withReuse(reuse) + .withNetworkAliases("scheduler"); + } + + @Bean("workerOneDapr") + @ConditionalOnProperty(prefix = "tests", name = "workers.enabled", havingValue = "true") + public DaprContainer workerOneDapr(Network daprNetwork, RedisContainer redisContainer, Environment env, + DaprPlacementContainer daprPlacementContainer, + DaprSchedulerContainer daprSchedulerContainer) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("worker-one") + .withNetworkAliases("worker-one") + .withNetwork(daprNetwork) + .withPlacementContainer(daprPlacementContainer) + .withSchedulerContainer(daprSchedulerContainer) + .withComponent(new Component("kvstore", "state.redis", "v1", getRedisProps())) + .dependsOn(daprPlacementContainer) + .dependsOn(daprSchedulerContainer) + .dependsOn(redisContainer); + } + + @Bean + @ConditionalOnProperty(prefix = "tests", name = "workers.enabled", havingValue = "true") + public GenericContainer workerOneContainer(Network daprNetwork, + @Qualifier("workerOneDapr") DaprContainer workerOneDapr, + DaprPlacementContainer daprPlacementContainer, + DaprSchedulerContainer daprSchedulerContainer){ + return new GenericContainer<>("openjdk:17-jdk-slim") + .withCopyFileToContainer(MountableFile.forHostPath("../worker-one/target"), "/app") + .withWorkingDirectory("/app") + .withCommand("java", + "-Ddapr.grpc.endpoint=worker-one:50001", + "-Ddapr.http.endpoint=worker-one:3500", + "-jar", + "worker-one.jar") + .withNetwork(daprNetwork) + .dependsOn(workerOneDapr) + .dependsOn(daprPlacementContainer) + .dependsOn(daprSchedulerContainer) + .waitingFor(Wait.forLogMessage(".*Started WorkerOneApplication.*", 1)) + .withLogConsumer(outputFrame -> System.out.println("WorkerOneApplication: " + outputFrame.getUtf8String())); + } + + @Bean("workerTwoDapr") + @ConditionalOnProperty(prefix = "tests", name = "workers.enabled", havingValue = "true") + public DaprContainer workerTwoDapr(Network daprNetwork, RedisContainer redisContainer, + Environment env, + DaprPlacementContainer daprPlacementContainer, + DaprSchedulerContainer daprSchedulerContainer) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + + return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("worker-two") + .withNetworkAliases("worker-two") + .withNetwork(daprNetwork) + .withPlacementContainer(daprPlacementContainer) + .withSchedulerContainer(daprSchedulerContainer) + .withComponent(new Component("kvstore", "state.redis", "v1", getRedisProps())) + .dependsOn(daprPlacementContainer) + .dependsOn(daprSchedulerContainer) + .dependsOn(redisContainer); + } + + @Bean + @ConditionalOnProperty(prefix = "tests", name = "workers.enabled", havingValue = "true") + public GenericContainer workerTwoContainer(Network daprNetwork, + @Qualifier("workerTwoDapr") DaprContainer workerTwoDapr, + DaprPlacementContainer daprPlacementContainer, + DaprSchedulerContainer daprSchedulerContainer){ + return new GenericContainer<>("openjdk:17-jdk-slim") + .withCopyFileToContainer(MountableFile.forHostPath("../worker-two/target"), "/app") + .withWorkingDirectory("/app") + .withCommand("java", + "-Ddapr.grpc.endpoint=worker-two:50001", + "-Ddapr.http.endpoint=worker-two:3500", + "-jar", + "worker-two.jar") + .withNetwork(daprNetwork) + .dependsOn(workerTwoDapr) + .dependsOn(daprPlacementContainer) + .dependsOn(daprSchedulerContainer) + .waitingFor(Wait.forLogMessage(".*Started WorkerTwoApplication.*", 1)) + .withLogConsumer(outputFrame -> System.out.println("WorkerTwoApplication: " + outputFrame.getUtf8String())); + } + + @Bean + public RedisContainer redisContainer(Network daprNetwork, Environment env){ + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME) + .withNetwork(daprNetwork) + .withReuse(reuse) + .withNetworkAliases("redis"); + } + + @Bean + @ServiceConnection + public DaprContainer daprContainer(Network daprNetwork, RedisContainer redisContainer, + Environment env, + DaprPlacementContainer daprPlacementContainer, + DaprSchedulerContainer daprSchedulerContainer) { + + return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("orchestrator") + .withNetwork(daprNetwork) + .withPlacementContainer(daprPlacementContainer) + .withSchedulerContainer(daprSchedulerContainer) + .withComponent(new Component("kvstore", "state.redis", "v1", getRedisProps())) + .withAppPort(8080) + .withAppHealthCheckPath("/actuator/health") + .withAppChannelAddress("host.testcontainers.internal") + .dependsOn(daprPlacementContainer) + .dependsOn(daprSchedulerContainer) + .dependsOn(redisContainer); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/OrchestratorAppIT.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/OrchestratorAppIT.java new file mode 100644 index 0000000000..5113256d90 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/OrchestratorAppIT.java @@ -0,0 +1,104 @@ +/* + * 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.orchestrator; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.IOException; +import java.time.Duration; + +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(classes = {TestOrchestratorApplication.class, DaprTestContainersConfig.class, CustomersRestController.class}, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = {"reuse=false", "tests.workers.enabled=true"}) +class OrchestratorAppIT { + + + @BeforeEach + void setUp() { + RestAssured.baseURI = "http://localhost:" + 8080; + org.testcontainers.Testcontainers.exposeHostPorts(8080); + + } + + @Test + void testCustomersWorkflows() throws InterruptedException, IOException { + + // Create a new workflow instance for a given customer + given().contentType(ContentType.JSON) + .body("{\"customerName\": \"salaboy\"}") + .when() + .post("/customers") + .then() + .statusCode(200); + + // Wait for the workflow instance to be running by checking the status + await().atMost(Duration.ofSeconds(5)).until(() -> + { + String workflowStatus = given().contentType(ContentType.JSON) + .body("{\"customerName\": \"salaboy\" }") + .when() + .post("/customers/status") + .then() + .statusCode(200) + .extract().asString(); + return workflowStatus.equals("Workflow for Customer: salaboy is RUNNING"); + } + ); + + // Raise an external event to move the workflow forward + given().contentType(ContentType.JSON) + .body("{\"customerName\": \"salaboy\" }") + .when() + .post("/customers/followup") + .then() + .statusCode(200); + + // Wait for the workflow instance to be completed by checking the status + await().atMost(Duration.ofSeconds(5)).until(() -> + { + String workflowStatus = given().contentType(ContentType.JSON) + .body("{\"customerName\": \"salaboy\" }") + .when() + .post("/customers/status") + .then() + .statusCode(200).extract().asString(); + return workflowStatus.equals("Workflow for Customer: salaboy is COMPLETED"); + } + ); + + // Get the customer after running all the workflow activities + Customer customer = given().contentType(ContentType.JSON) + .body("{\"customerName\": \"salaboy\" }") + .when() + .post("/customers/output") + .then() + .statusCode(200).extract().as(Customer.class); + + assertTrue(customer.isInCustomerDB()); + assertTrue(customer.isFollowUp()); + + + } + +} diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/TestOrchestratorApplication.java b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/TestOrchestratorApplication.java new file mode 100644 index 0000000000..9e2b4be77a --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/java/io/dapr/springboot/examples/orchestrator/TestOrchestratorApplication.java @@ -0,0 +1,31 @@ +/* + * 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.orchestrator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class TestOrchestratorApplication { + + public static void main(String[] args) { + + SpringApplication.from(OrchestratorApplication::main) + .with(DaprTestContainersConfig.class) + .run(args); + org.testcontainers.Testcontainers.exposeHostPorts(8080); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/orchestrator/src/test/resources/application.properties b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/resources/application.properties new file mode 100644 index 0000000000..ce86cb5399 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/orchestrator/src/test/resources/application.properties @@ -0,0 +1,2 @@ +dapr.statestore.name=kvstore +server.port=8080 \ No newline at end of file diff --git a/spring-boot-examples/workflows/multi-app/pom.xml b/spring-boot-examples/workflows/multi-app/pom.xml new file mode 100644 index 0000000000..6586459001 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + io.dapr + workflows + 1.17.0-SNAPSHOT + + + multi-app + 1.17.0-SNAPSHOT + pom + + + true + + + + orchestrator + worker-one + worker-two + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + true + + + + + diff --git a/spring-boot-examples/workflows/multi-app/spotbugs-exclude.xml b/spring-boot-examples/workflows/multi-app/spotbugs-exclude.xml new file mode 100644 index 0000000000..264fc79b0a --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/spotbugs-exclude.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/spring-boot-examples/workflows/multi-app/worker-one/pom.xml b/spring-boot-examples/workflows/multi-app/worker-one/pom.xml new file mode 100644 index 0000000000..88c5fec997 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + + io.dapr + multi-app + 1.17.0-SNAPSHOT + + + worker-one + worker-one + Spring Boot, Testcontainers and Dapr Integration Examples :: Worker 1 App + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + io.dapr.spring + dapr-spring-boot-starter + + + io.dapr.spring + dapr-spring-boot-starter-test + test + + + com.redis + testcontainers-redis + 2.2.2 + test + + + io.rest-assured + rest-assured + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + worker-one + + + + + repackage + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + true + + + + + diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/Customer.java b/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/Customer.java new file mode 100644 index 0000000000..88d24783dc --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/Customer.java @@ -0,0 +1,59 @@ +/* + * 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.workerone; + +public class Customer { + private String customerName; + private String workflowId; + private boolean inCustomerDB = false; + private boolean followUp = false; + + public boolean isFollowUp() { + return followUp; + } + + public void setFollowUp(boolean followUp) { + this.followUp = followUp; + } + + public boolean isInCustomerDB() { + return inCustomerDB; + } + + public void setInCustomerDB(boolean inCustomerDB) { + this.inCustomerDB = inCustomerDB; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + @Override + public String toString() { + return "Customer [customerName=" + customerName + ", workflowId=" + workflowId + ", inCustomerDB=" + + inCustomerDB + ", followUp=" + followUp + "]"; + } +} diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/RegisterCustomerActivity.java b/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/RegisterCustomerActivity.java new file mode 100644 index 0000000000..001c9e4489 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/RegisterCustomerActivity.java @@ -0,0 +1,38 @@ +/* + * 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.workerone; + + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class RegisterCustomerActivity implements WorkflowActivity { + + private final Logger logger = LoggerFactory.getLogger(RegisterCustomerActivity.class); + + + @Override + public Object run(WorkflowActivityContext ctx) { + Customer customer = ctx.getInput(Customer.class); + customer.setInCustomerDB(true); + logger.info("Customer: {} registered.", customer.getCustomerName()); + return customer; + } + + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/WorkerOneApplication.java b/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/WorkerOneApplication.java new file mode 100644 index 0000000000..552a5fc816 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/main/java/io/dapr/springboot/examples/workerone/WorkerOneApplication.java @@ -0,0 +1,29 @@ +/* + * 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.workerone; + +import io.dapr.spring.workflows.config.EnableDaprWorkflows; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +@EnableDaprWorkflows +public class WorkerOneApplication { + + public static void main(String[] args) { + SpringApplication.run(WorkerOneApplication.class, args); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/main/resources/application.properties b/spring-boot-examples/workflows/multi-app/worker-one/src/main/resources/application.properties new file mode 100644 index 0000000000..324435fa52 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=worker-one +server.port=8081 \ No newline at end of file diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/DaprTestContainersConfig.java b/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/DaprTestContainersConfig.java new file mode 100644 index 0000000000..e63c89687f --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/DaprTestContainersConfig.java @@ -0,0 +1,102 @@ +/* + * 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.workerone; + +import com.redis.testcontainers.RedisContainer; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +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.testcontainers.DockerClientFactory; +import org.testcontainers.containers.Network; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; + +@TestConfiguration(proxyBeanMethods = false) +public class DaprTestContainersConfig { + + @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(); + } + } + + @Bean + public RedisContainer redisContainer(Network daprNetwork, Environment env){ + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME) + .withNetwork(daprNetwork) + .withReuse(reuse) + .withNetworkAliases("redis"); + } + + @Bean + @ServiceConnection + public DaprContainer daprContainer(Network daprNetwork, RedisContainer redisContainer, Environment env) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + Map redisProps = new HashMap<>(); + redisProps.put("redisHost", "redis:6379"); + redisProps.put("redisPassword", ""); + redisProps.put("actorStateStore", String.valueOf(true)); + + return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("worker-one") + .withNetwork(daprNetwork) + .withReusablePlacement(reuse) + .withReusableScheduler(reuse) + .withComponent(new Component("kvstore", "state.redis", "v1", redisProps)) + .withAppPort(8081) + .withAppHealthCheckPath("/actuator/health") + .withAppChannelAddress("host.testcontainers.internal") + .dependsOn(redisContainer); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/TestWorkerOneApplication.java b/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/TestWorkerOneApplication.java new file mode 100644 index 0000000000..4e285ca0bf --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/TestWorkerOneApplication.java @@ -0,0 +1,31 @@ +/* + * 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.workerone; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class TestWorkerOneApplication { + + public static void main(String[] args) { + + SpringApplication.from(WorkerOneApplication::main) + .with(DaprTestContainersConfig.class) + .run(args); + org.testcontainers.Testcontainers.exposeHostPorts(8081); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/WorkerOneAppIT.java b/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/WorkerOneAppIT.java new file mode 100644 index 0000000000..a4a9cbc4d9 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/test/java/io/dapr/springboot/examples/workerone/WorkerOneAppIT.java @@ -0,0 +1,42 @@ +/* + * 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.workerone; + +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = {TestWorkerOneApplication.class, DaprTestContainersConfig.class}, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +class WorkerOneAppIT { + + @BeforeEach + void setUp() { + RestAssured.baseURI = "http://localhost:" + 8081; + org.testcontainers.Testcontainers.exposeHostPorts(8081); + + } + + @Test + void testWorkerOne() { + //Test the logic of the worker one + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-one/src/test/resources/application.properties b/spring-boot-examples/workflows/multi-app/worker-one/src/test/resources/application.properties new file mode 100644 index 0000000000..f1b6b99450 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-one/src/test/resources/application.properties @@ -0,0 +1,2 @@ +dapr.statestore.name=kvstore +server.port=8081 \ No newline at end of file diff --git a/spring-boot-examples/workflows/multi-app/worker-two/pom.xml b/spring-boot-examples/workflows/multi-app/worker-two/pom.xml new file mode 100644 index 0000000000..508a725ecb --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + + io.dapr + multi-app + 1.17.0-SNAPSHOT + + + worker-two + worker-two + Spring Boot, Testcontainers and Dapr Integration Examples :: Worker 2 App + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + io.dapr.spring + dapr-spring-boot-starter + + + io.dapr.spring + dapr-spring-boot-starter-test + test + + + com.redis + testcontainers-redis + 2.2.2 + test + + + io.rest-assured + rest-assured + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + worker-two + + + + + repackage + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + true + + + + + diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/Customer.java b/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/Customer.java new file mode 100644 index 0000000000..5ba027cc04 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/Customer.java @@ -0,0 +1,59 @@ +/* + * 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.workertwo; + +public class Customer { + private String customerName; + private String workflowId; + private boolean inCustomerDB = false; + private boolean followUp = false; + + public boolean isFollowUp() { + return followUp; + } + + public void setFollowUp(boolean followUp) { + this.followUp = followUp; + } + + public boolean isInCustomerDB() { + return inCustomerDB; + } + + public void setInCustomerDB(boolean inCustomerDB) { + this.inCustomerDB = inCustomerDB; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + @Override + public String toString() { + return "Customer [customerName=" + customerName + ", workflowId=" + workflowId + ", inCustomerDB=" + + inCustomerDB + ", followUp=" + followUp + "]"; + } +} diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/CustomerFollowupActivity.java b/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/CustomerFollowupActivity.java new file mode 100644 index 0000000000..76c042ecd2 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/CustomerFollowupActivity.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.springboot.examples.workertwo; + +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class CustomerFollowupActivity implements WorkflowActivity { + + private final Logger logger = LoggerFactory.getLogger(CustomerFollowupActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + Customer customer = ctx.getInput(Customer.class); + customer.setFollowUp(true); + logger.info("Customer: {} follow up scheduled.", customer.getCustomerName()); + return customer; + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/WorkerTwoApplication.java b/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/WorkerTwoApplication.java new file mode 100644 index 0000000000..3adaaeae64 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/main/java/io/dapr/springboot/examples/workertwo/WorkerTwoApplication.java @@ -0,0 +1,29 @@ +/* + * 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.workertwo; + +import io.dapr.spring.workflows.config.EnableDaprWorkflows; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +@EnableDaprWorkflows +public class WorkerTwoApplication { + + public static void main(String[] args) { + SpringApplication.run(WorkerTwoApplication.class, args); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/main/resources/application.properties b/spring-boot-examples/workflows/multi-app/worker-two/src/main/resources/application.properties new file mode 100644 index 0000000000..7857e2cad1 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=worker-two +server.port=8082 \ No newline at end of file diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/DaprTestContainersConfig.java b/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/DaprTestContainersConfig.java new file mode 100644 index 0000000000..e63ab0cf7e --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/DaprTestContainersConfig.java @@ -0,0 +1,102 @@ +/* + * 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.workertwo; + +import com.redis.testcontainers.RedisContainer; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +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.testcontainers.DockerClientFactory; +import org.testcontainers.containers.Network; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; + +@TestConfiguration(proxyBeanMethods = false) +public class DaprTestContainersConfig { + + @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(); + } + } + + + @Bean + public RedisContainer redisContainer(Network daprNetwork, Environment env){ + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME) + .withNetwork(daprNetwork) + .withReuse(reuse) + .withNetworkAliases("redis"); + } + + @Bean + @ServiceConnection + public DaprContainer daprContainer(Network daprNetwork, RedisContainer redisContainer, Environment env) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + Map redisProps = new HashMap<>(); + redisProps.put("redisHost", "redis:6379"); + redisProps.put("redisPassword", ""); + redisProps.put("actorStateStore", String.valueOf(true)); + + return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("worker-two") + .withNetwork(daprNetwork) + .withReusablePlacement(reuse) + .withReusableScheduler(reuse) + .withComponent(new Component("kvstore", "state.redis", "v1", redisProps)) + .withAppPort(8082) + .withAppHealthCheckPath("/actuator/health") + .withAppChannelAddress("host.testcontainers.internal") + .dependsOn(redisContainer); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/TestWorkerTwoApplication.java b/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/TestWorkerTwoApplication.java new file mode 100644 index 0000000000..8fbdce15e1 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/TestWorkerTwoApplication.java @@ -0,0 +1,31 @@ +/* + * 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.workertwo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class TestWorkerTwoApplication { + + public static void main(String[] args) { + + SpringApplication.from(WorkerTwoApplication::main) + .with(DaprTestContainersConfig.class) + .run(args); + org.testcontainers.Testcontainers.exposeHostPorts(8082); + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/WorkerTwoAppIT.java b/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/WorkerTwoAppIT.java new file mode 100644 index 0000000000..ed17115e8b --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/test/java/io/dapr/springboot/examples/workertwo/WorkerTwoAppIT.java @@ -0,0 +1,42 @@ +/* + * 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.workertwo; + +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = {TestWorkerTwoApplication.class, DaprTestContainersConfig.class}, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +class WorkerTwoAppIT { + + @BeforeEach + void setUp() { + RestAssured.baseURI = "http://localhost:" + 8082; + org.testcontainers.Testcontainers.exposeHostPorts(8082); + + } + + @Test + void testWorkerTwo() { + //Test the logic of the worker two + } + +} diff --git a/spring-boot-examples/workflows/multi-app/worker-two/src/test/resources/application.properties b/spring-boot-examples/workflows/multi-app/worker-two/src/test/resources/application.properties new file mode 100644 index 0000000000..2536d68631 --- /dev/null +++ b/spring-boot-examples/workflows/multi-app/worker-two/src/test/resources/application.properties @@ -0,0 +1,2 @@ +dapr.statestore.name=kvstore +server.port=8082 diff --git a/spring-boot-examples/workflows/README.md b/spring-boot-examples/workflows/patterns/README.md similarity index 99% rename from spring-boot-examples/workflows/README.md rename to spring-boot-examples/workflows/patterns/README.md index d62eded716..b92294bdf7 100644 --- a/spring-boot-examples/workflows/README.md +++ b/spring-boot-examples/workflows/patterns/README.md @@ -29,7 +29,7 @@ timeout_seconds: 180 ```sh -../../mvnw spring-boot:test-run +../../../mvnw spring-boot:test-run ``` diff --git a/spring-boot-examples/workflows/body.json b/spring-boot-examples/workflows/patterns/body.json similarity index 100% rename from spring-boot-examples/workflows/body.json rename to spring-boot-examples/workflows/patterns/body.json diff --git a/spring-boot-examples/workflows/patterns/pom.xml b/spring-boot-examples/workflows/patterns/pom.xml new file mode 100644 index 0000000000..e4742686bc --- /dev/null +++ b/spring-boot-examples/workflows/patterns/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + io.dapr + workflows + 1.17.0-SNAPSHOT + + + patterns + patterns + Spring Boot, Testcontainers and Dapr Integration Examples :: Workflows Patterns + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + io.dapr.spring + dapr-spring-boot-starter + + + io.dapr.spring + dapr-spring-boot-starter-test + test + + + io.rest-assured + rest-assured + test + + + io.github.microcks + microcks-testcontainers + ${microcks.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + true + + + + + diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsApplication.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsApplication.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsApplication.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsApplication.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsConfiguration.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/WorkflowPatternsRestController.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/chain/ChainWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/chain/ChainWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/chain/ChainWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/chain/ChainWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/chain/ToUpperCaseActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/chain/ToUpperCaseActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/chain/ToUpperCaseActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/chain/ToUpperCaseActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/child/ChildWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/child/ChildWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/child/ChildWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/child/ChildWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/child/ParentWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/child/ParentWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/child/ParentWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/child/ParentWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/child/ReverseActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/child/ReverseActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/child/ReverseActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/child/ReverseActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpLog.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpLog.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpLog.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/CleanUpLog.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/ContinueAsNewWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/ContinueAsNewWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/ContinueAsNewWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/continueasnew/ContinueAsNewWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ApproveActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ApproveActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ApproveActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ApproveActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/Decision.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/Decision.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/Decision.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/Decision.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/DenyActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/DenyActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/DenyActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/DenyActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ExternalEventWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ExternalEventWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ExternalEventWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/externalevent/ExternalEventWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/CountWordsActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/CountWordsActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/CountWordsActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/CountWordsActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/FanOutInWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/FanOutInWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/FanOutInWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/FanOutInWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/Result.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/Result.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/Result.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/fanoutin/Result.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/CallRemoteEndpointActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/Payload.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/remoteendpoint/RemoteEndpointWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/PerformTaskActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/suspendresume/SuspendResumeWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/DurationTimerWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/DurationTimerWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/DurationTimerWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/DurationTimerWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/LogActivity.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/LogActivity.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/LogActivity.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/LogActivity.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/TimerLogService.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/TimerLogService.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/TimerLogService.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/TimerLogService.java diff --git a/spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/ZonedDateTimeTimerWorkflow.java b/spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/ZonedDateTimeTimerWorkflow.java similarity index 100% rename from spring-boot-examples/workflows/src/main/java/io/dapr/springboot/examples/wfp/timer/ZonedDateTimeTimerWorkflow.java rename to spring-boot-examples/workflows/patterns/src/main/java/io/dapr/springboot/examples/wfp/timer/ZonedDateTimeTimerWorkflow.java diff --git a/spring-boot-examples/workflows/src/main/resources/application.properties b/spring-boot-examples/workflows/patterns/src/main/resources/application.properties similarity index 100% rename from spring-boot-examples/workflows/src/main/resources/application.properties rename to spring-boot-examples/workflows/patterns/src/main/resources/application.properties diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java b/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java similarity index 100% rename from spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java rename to spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java b/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java similarity index 100% rename from spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java rename to spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java diff --git a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java b/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppIT.java similarity index 99% rename from spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java rename to spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppIT.java index 80a9cda01a..40ef080f5c 100644 --- a/spring-boot-examples/workflows/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppTests.java +++ b/spring-boot-examples/workflows/patterns/src/test/java/io/dapr/springboot/examples/wfp/WorkflowPatternsAppIT.java @@ -58,7 +58,7 @@ @SpringBootTest(classes = {TestWorkflowPatternsApplication.class, DaprTestContainersConfig.class, DaprAutoConfiguration.class, }, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -class WorkflowPatternsAppTests { +class WorkflowPatternsAppIT { @Autowired private MicrocksContainersEnsemble ensemble; diff --git a/spring-boot-examples/workflows/src/test/resources/application.properties b/spring-boot-examples/workflows/patterns/src/test/resources/application.properties similarity index 100% rename from spring-boot-examples/workflows/src/test/resources/application.properties rename to spring-boot-examples/workflows/patterns/src/test/resources/application.properties diff --git a/spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml b/spring-boot-examples/workflows/patterns/src/test/resources/third-parties/remote-http-service.yaml similarity index 100% rename from spring-boot-examples/workflows/src/test/resources/third-parties/remote-http-service.yaml rename to spring-boot-examples/workflows/patterns/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 0411a3e129..db4dc6f485 100644 --- a/spring-boot-examples/workflows/pom.xml +++ b/spring-boot-examples/workflows/pom.xml @@ -1,8 +1,8 @@ - + 4.0.0 - io.dapr spring-boot-examples @@ -10,50 +10,32 @@ workflows - workflows - Spring Boot, Testcontainers and Dapr Integration Examples :: Workflows + 1.17.0-SNAPSHOT + pom + + + true + - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - - - io.dapr.spring - dapr-spring-boot-starter - - - io.dapr.spring - dapr-spring-boot-starter-test - test - - - io.rest-assured - rest-assured - test - - - io.github.microcks - microcks-testcontainers - ${microcks.version} - test - - + + patterns + multi-app + + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + pom + import + + + - - org.springframework.boot - spring-boot-maven-plugin - org.apache.maven.plugins maven-site-plugin diff --git a/spring-boot-examples/workflows/spotbugs-exclude.xml b/spring-boot-examples/workflows/spotbugs-exclude.xml new file mode 100644 index 0000000000..264fc79b0a --- /dev/null +++ b/spring-boot-examples/workflows/spotbugs-exclude.xml @@ -0,0 +1,6 @@ + + + + + +