From c50b065ba03ecba17561041d103e37cae05d07e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Andr=C3=A9?= Date: Fri, 2 Oct 2026 22:16:20 -0300 Subject: [PATCH] Modified Dapr Pub/Sub Example with Producer and Consumer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus André --- .github/workflows/validate.yml | 2 +- .../java/io/dapr/workflows/WorkflowTest.java | 2 +- spring-boot-examples/README.md | 182 +----------------- spring-boot-examples/pom.xml | 3 +- .../examples/producer/Customer.java | 59 ------ .../examples/producer/CustomerStore.java | 38 ---- .../producer/CustomersRestController.java | 89 --------- .../workflow/CustomerFollowupActivity.java | 46 ----- .../producer/workflow/CustomerWorkflow.java | 42 ---- .../workflow/RegisterCustomerActivity.java | 45 ----- spring-boot-examples/pubsub/README.md | 113 +++++++++++ .../{ => pubsub}/consumer-app/pom.xml | 2 +- .../consumer/ConsumerApplication.java | 0 .../springboot/examples/consumer/Order.java | 0 .../consumer/SubscriberRestController.java | 0 .../src/main/resources/application.properties | 0 .../examples/consumer/ConsumerAppIT.java | 0 .../ConsumerAppTestConfiguration.java | 0 .../consumer/DaprTestContainersConfig.java | 0 .../consumer/TestConsumerApplication.java | 0 .../src/test/resources/application.properties | 0 .../{ => pubsub}/kubernetes/README.md | 6 +- .../{ => pubsub}/kubernetes/consumer-app.yaml | 0 .../kubernetes/kind-with-registry.sh | 0 .../{ => pubsub}/kubernetes/kvbinding.yaml | 0 .../{ => pubsub}/kubernetes/kvstore.yaml | 0 .../{ => pubsub}/kubernetes/producer-app.yaml | 0 .../{ => pubsub}/kubernetes/pubsub.yaml | 0 spring-boot-examples/pubsub/pom.xml | 44 +++++ .../{ => pubsub}/producer-app/pom.xml | 2 +- .../Jackson3SerializerConfiguration.java | 0 .../springboot/examples/producer/Order.java | 0 .../examples/producer/OrderDTO.java | 0 .../examples/producer/OrderRepository.java | 0 .../producer/OrdersRestController.java | 0 .../producer/ProducerAppConfiguration.java | 0 .../producer/ProducerApplication.java | 0 .../src/main/resources/application.properties | 0 .../producer/DaprTestContainersConfig.java | 0 .../examples/producer/ProducerAppIT.java | 44 +---- .../producer/TestProducerApplication.java | 0 .../TestSubscriberRestController.java | 0 .../src/test/resources/application.properties | 0 .../pubsub/spotbugs-exclude.xml | 6 + 44 files changed, 180 insertions(+), 545 deletions(-) delete mode 100644 spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Customer.java delete mode 100644 spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomerStore.java delete mode 100644 spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomersRestController.java delete mode 100644 spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerFollowupActivity.java delete mode 100644 spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerWorkflow.java delete mode 100644 spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/RegisterCustomerActivity.java create mode 100644 spring-boot-examples/pubsub/README.md rename spring-boot-examples/{ => pubsub}/consumer-app/pom.xml (98%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/ConsumerApplication.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/Order.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/SubscriberRestController.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/main/resources/application.properties (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTestConfiguration.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/DaprTestContainersConfig.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/TestConsumerApplication.java (100%) rename spring-boot-examples/{ => pubsub}/consumer-app/src/test/resources/application.properties (100%) rename spring-boot-examples/{ => pubsub}/kubernetes/README.md (92%) rename spring-boot-examples/{ => pubsub}/kubernetes/consumer-app.yaml (100%) rename spring-boot-examples/{ => pubsub}/kubernetes/kind-with-registry.sh (100%) rename spring-boot-examples/{ => pubsub}/kubernetes/kvbinding.yaml (100%) rename spring-boot-examples/{ => pubsub}/kubernetes/kvstore.yaml (100%) rename spring-boot-examples/{ => pubsub}/kubernetes/producer-app.yaml (100%) rename spring-boot-examples/{ => pubsub}/kubernetes/pubsub.yaml (100%) create mode 100644 spring-boot-examples/pubsub/pom.xml rename spring-boot-examples/{ => pubsub}/producer-app/pom.xml (97%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/Jackson3SerializerConfiguration.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/Order.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderDTO.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderRepository.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrdersRestController.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerAppConfiguration.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerApplication.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/main/resources/application.properties (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/test/java/io/dapr/springboot/examples/producer/DaprTestContainersConfig.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java (76%) rename spring-boot-examples/{ => pubsub}/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestProducerApplication.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestSubscriberRestController.java (100%) rename spring-boot-examples/{ => pubsub}/producer-app/src/test/resources/application.properties (100%) create mode 100644 spring-boot-examples/pubsub/spotbugs-exclude.xml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 818b53c124..b5c0e59bc6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -123,7 +123,7 @@ jobs: set -e (cd examples && mm.py ./src/main/java/io/dapr/examples/crypto/README.md) (cd examples && mm.py ./src/main/java/io/dapr/examples/workflows/README.md) - (cd spring-boot-examples && mm.py README.md) + (cd spring-boot-examples/pubsub && mm.py README.md) (cd spring-boot-examples/workflows/patterns && mm.py README.md) (cd examples && mm.py ./src/main/java/io/dapr/examples/jobs/README.md) (cd examples && mm.py ./src/main/java/io/dapr/examples/conversation/README.md) diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java index 3c8a7048cc..70904540f3 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -public class WorkflowTest { +public class WorkflowTest { @Test public void testWorkflow() { diff --git a/spring-boot-examples/README.md b/spring-boot-examples/README.md index 3cc88610de..ca03bf9bc8 100644 --- a/spring-boot-examples/README.md +++ b/spring-boot-examples/README.md @@ -1,176 +1,10 @@ -# Dapr Spring Boot and Testcontainers integration Example +# Dapr Spring Boot Examples -This example consists of two applications: -- Producer App: - - Publish messages using a Spring Messaging approach - - Store and retrieve information using Spring Data CrudRepository - - Implements a Workflow with Dapr Workflows -- Consumer App: - - Subscribe to messages +These examples show how to use Dapr with Spring Boot and [Testcontainers](https://testcontainers.com), with one +folder per Dapr API: -## Running these examples from source code - -To run these examples you will need: -- Java SDK -- Maven -- Docker or a container runtime such as Podman - -From the `spring-boot-examples/` directory you can start each service using the test configuration that uses -[Testcontainers](https://testcontainers.com) to boostrap [Dapr](https://dapr.io) by running the following command: - - - - -```sh -cd producer-app/ -../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run -``` - - - -This will start the `producer-app` with Dapr services and the infrastructure needed by the application to run, -in this case RabbitMQ and PostgreSQL. The `producer-app` starts on port `8080` by default. - -The `-Dspring-boot.run.arguments="--reuse=true"` flag helps the application to connect to an existing shared -infrastructure if it already exists. For development purposes, and to connect both applications we will set the flag -in both. For more details check the `DaprTestContainersConfig.java` classes in both, the `producer-app` and the `consumer-app`. - -Then run in a different terminal: - - - - -```sh -cd consumer-app/ -../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run -``` - - -The `consumer-app` starts in port `8081` by default. - -## Interacting with the applications - -Now that both applications are up you can place an order by sending a POST request to `:8080/orders/` -You can use `curl` to send a POST request to the `producer-app`: - - - - - -```sh -curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{ "item": "the mars volta EP", "amount": 1 }' -``` - - - - -If you check the `producer-app` logs you should see the following lines: - -```bash -... -Storing Order: Order{id='null', item='the mars volta EP', amount=1} -Publishing Order Event: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1} - -``` - -If you check the `consumer-app` logs you should see the following lines, showing that the message -published by the `producer-app` was correctly consumed by the `consumer-app`: - -```bash -Order Event Received: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1} -``` - -Next, you can create a new customer to trigger the customer's tracking workflow: - - - - -```sh -curl -X POST localhost:8080/customers -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }' -``` - - - - -A new Workflow Instance was created to track the customers interactions. Now, the workflow instance -is waiting for the customer to request a follow-up. - -You should see in the `producer-app` logs: - -```bash -Workflow instance started -Let's register the customer: salaboy -Customer: salaboy registered. -Let's wait for the customer: salaboy to request a follow up. -``` - -Send an event simulating the customer request for a follow-up: - - - - -```sh -curl -X POST localhost:8080/customers/followup -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }' -``` - - - -In the `producer-app` logs you should see that the workflow instance id moved forward to the Customer Follow Up activity: - -```bash -Customer follow-up requested: salaboy -Let's book a follow up for the customer: salaboy -Customer: salaboy follow-up done. -Congratulations the customer: salaboy is happy! -``` - -## Running on Kubernetes - -You can run the same example on a Kubernetes cluster. [Check the Kubernetes tutorial here](kubernetes/README.md). +- [PubSub](pubsub/README.md): a producer app publishing messages with Spring Messaging and a consumer app subscribing to them. +- Workflows: + - [Patterns](workflows/patterns/README.md): common Dapr Workflow patterns. + - [Multi App](workflows/multi-app/README.md): workflows that span multiple applications. + - [Versioning](workflows/versioning/README.md): versioning workflows. diff --git a/spring-boot-examples/pom.xml b/spring-boot-examples/pom.xml index 32e08e30ee..6c275eb380 100644 --- a/spring-boot-examples/pom.xml +++ b/spring-boot-examples/pom.xml @@ -21,8 +21,7 @@ - producer-app - consumer-app + pubsub workflows diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Customer.java b/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Customer.java deleted file mode 100644 index 2211d1e90d..0000000000 --- a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Customer.java +++ /dev/null @@ -1,59 +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.springboot.examples.producer; - -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/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomerStore.java b/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomerStore.java deleted file mode 100644 index 35c884e065..0000000000 --- a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomerStore.java +++ /dev/null @@ -1,38 +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.springboot.examples.producer; - -import org.springframework.stereotype.Component; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -@Component -public class CustomerStore { - private Map customers = new HashMap<>(); - - public void addCustomer(Customer customer) { - customers.put(customer.getCustomerName(), customer); - } - - public Customer getCustomer(String customerName) { - return customers.get(customerName); - } - - public Collection getCustomers() { - return customers.values(); - } - -} diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomersRestController.java b/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomersRestController.java deleted file mode 100644 index 57622d1043..0000000000 --- a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/CustomersRestController.java +++ /dev/null @@ -1,89 +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.springboot.examples.producer; - -import io.dapr.spring.workflows.config.EnableDaprWorkflows; -import io.dapr.springboot.examples.producer.workflow.CustomerWorkflow; -import io.dapr.workflows.client.DaprWorkflowClient; -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.Collection; -import java.util.HashMap; -import java.util.Map; - -@RestController -@EnableDaprWorkflows -public class CustomersRestController { - - - private final Logger logger = LoggerFactory.getLogger(CustomersRestController.class); - - @Autowired - private DaprWorkflowClient daprWorkflowClient; - - @Autowired - private CustomerStore customerStore; - - @GetMapping("/") - public String root() { - return "OK"; - } - - private 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 " + instanceId + " started"); - 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(); - } else { - daprWorkflowClient.raiseEvent(workflowIdForCustomer, "CustomerReachOut", customer); - return "Customer Follow-up requested"; - } - } - - - public Collection getCustomers() { - return customerStore.getCustomers(); - } - - -} - diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerFollowupActivity.java b/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerFollowupActivity.java deleted file mode 100644 index fb4e207708..0000000000 --- a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerFollowupActivity.java +++ /dev/null @@ -1,46 +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.springboot.examples.producer.workflow; - -import io.dapr.springboot.examples.producer.Customer; -import io.dapr.springboot.examples.producer.CustomerStore; -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); - - private final CustomerStore customerStore; - - public CustomerFollowupActivity(CustomerStore customerStore) { - this.customerStore = customerStore; - } - - @Override - public Object run(WorkflowActivityContext ctx) { - Customer customer = ctx.getInput(Customer.class); - //Let's get the hydrate the real customer from the CustomerStore - customer = customerStore.getCustomer(customer.getCustomerName()); - customer.setFollowUp(true); - customerStore.addCustomer(customer); - logger.info("Customer: " + customer.getCustomerName() + " follow-up done."); - return customer; - } - -} diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerWorkflow.java b/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerWorkflow.java deleted file mode 100644 index 1e8757f99c..0000000000 --- a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/CustomerWorkflow.java +++ /dev/null @@ -1,42 +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.springboot.examples.producer.workflow; - -import io.dapr.springboot.examples.producer.Customer; -import io.dapr.workflows.Workflow; -import io.dapr.workflows.WorkflowStub; -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()); - ctx.callActivity(RegisterCustomerActivity.class.getName(), customer, Customer.class).await(); - ctx.getLogger().info("Let's wait for the customer: " + customer.getCustomerName() + " to request a follow up."); - customer = 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(CustomerFollowupActivity.class.getName(), customer, Customer.class).await(); - ctx.getLogger().info("Congratulations the customer: " + customer.getCustomerName() + " is happy!"); - ctx.complete(customer); - }; - } -} diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/RegisterCustomerActivity.java b/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/RegisterCustomerActivity.java deleted file mode 100644 index c326c002c7..0000000000 --- a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/workflow/RegisterCustomerActivity.java +++ /dev/null @@ -1,45 +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.springboot.examples.producer.workflow; - - -import io.dapr.springboot.examples.producer.Customer; -import io.dapr.springboot.examples.producer.CustomerStore; -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); - private final CustomerStore customerStore; - - public RegisterCustomerActivity(CustomerStore customerStore) { - this.customerStore = customerStore; - } - - @Override - public Object run(WorkflowActivityContext ctx) { - Customer customer = ctx.getInput(Customer.class); - customer.setInCustomerDB(true); - logger.info("Customer: " + customer.getCustomerName() + " registered."); - customerStore.addCustomer(customer); - return customer; - } - - -} diff --git a/spring-boot-examples/pubsub/README.md b/spring-boot-examples/pubsub/README.md new file mode 100644 index 0000000000..6d0ae9dc57 --- /dev/null +++ b/spring-boot-examples/pubsub/README.md @@ -0,0 +1,113 @@ +# Dapr Spring Boot PubSub Example + +This example consists of two applications: +- Producer App: + - Publish messages using a Spring Messaging approach + - Store and retrieve information using Spring Data CrudRepository +- Consumer App: + - Subscribe to messages + +## Running these examples from source code + +To run these examples you will need: +- Java SDK +- Maven +- Docker or a container runtime such as Podman + +From the `spring-boot-examples/pubsub/` directory you can start each service using the test configuration that uses +[Testcontainers](https://testcontainers.com) to boostrap [Dapr](https://dapr.io) by running the following command: + + + + +```sh +cd producer-app/ +../../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run +``` + + + +This will start the `producer-app` with Dapr services and the infrastructure needed by the application to run, +in this case RabbitMQ and PostgreSQL. The `producer-app` starts on port `8080` by default. + +The `-Dspring-boot.run.arguments="--reuse=true"` flag helps the application to connect to an existing shared +infrastructure if it already exists. For development purposes, and to connect both applications we will set the flag +in both. For more details check the `DaprTestContainersConfig.java` classes in both, the `producer-app` and the `consumer-app`. + +Then run in a different terminal: + + + + +```sh +cd consumer-app/ +../../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run +``` + + +The `consumer-app` starts in port `8081` by default. + +## Interacting with the applications + +Now that both applications are up you can place an order by sending a POST request to `:8080/orders/` +You can use `curl` to send a POST request to the `producer-app`: + + + + + +```sh +curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{ "item": "the mars volta EP", "amount": 1 }' +``` + + + + +If you check the `producer-app` logs you should see the following lines: + +```bash +... +Storing Order: Order{id='null', item='the mars volta EP', amount=1} +Publishing Order Event: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1} + +``` + +If you check the `consumer-app` logs you should see the following lines, showing that the message +published by the `producer-app` was correctly consumed by the `consumer-app`: + +```bash +Order Event Received: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1} +``` + +## Running on Kubernetes + +You can run the same example on a Kubernetes cluster. [Check the Kubernetes tutorial here](kubernetes/README.md). diff --git a/spring-boot-examples/consumer-app/pom.xml b/spring-boot-examples/pubsub/consumer-app/pom.xml similarity index 98% rename from spring-boot-examples/consumer-app/pom.xml rename to spring-boot-examples/pubsub/consumer-app/pom.xml index e5abee1582..e439402527 100644 --- a/spring-boot-examples/consumer-app/pom.xml +++ b/spring-boot-examples/pubsub/consumer-app/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.dapr - spring-boot-examples + pubsub 1.20.0-SNAPSHOT ../pom.xml diff --git a/spring-boot-examples/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/ConsumerApplication.java b/spring-boot-examples/pubsub/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/ConsumerApplication.java similarity index 100% rename from spring-boot-examples/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/ConsumerApplication.java rename to spring-boot-examples/pubsub/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/ConsumerApplication.java diff --git a/spring-boot-examples/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/Order.java b/spring-boot-examples/pubsub/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/Order.java similarity index 100% rename from spring-boot-examples/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/Order.java rename to spring-boot-examples/pubsub/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/Order.java diff --git a/spring-boot-examples/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/SubscriberRestController.java b/spring-boot-examples/pubsub/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/SubscriberRestController.java similarity index 100% rename from spring-boot-examples/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/SubscriberRestController.java rename to spring-boot-examples/pubsub/consumer-app/src/main/java/io/dapr/springboot/examples/consumer/SubscriberRestController.java diff --git a/spring-boot-examples/consumer-app/src/main/resources/application.properties b/spring-boot-examples/pubsub/consumer-app/src/main/resources/application.properties similarity index 100% rename from spring-boot-examples/consumer-app/src/main/resources/application.properties rename to spring-boot-examples/pubsub/consumer-app/src/main/resources/application.properties diff --git a/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java b/spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java similarity index 100% rename from spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java rename to spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppIT.java diff --git a/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTestConfiguration.java b/spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTestConfiguration.java similarity index 100% rename from spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTestConfiguration.java rename to spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/ConsumerAppTestConfiguration.java diff --git a/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/DaprTestContainersConfig.java b/spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/DaprTestContainersConfig.java similarity index 100% rename from spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/DaprTestContainersConfig.java rename to spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/DaprTestContainersConfig.java diff --git a/spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/TestConsumerApplication.java b/spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/TestConsumerApplication.java similarity index 100% rename from spring-boot-examples/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/TestConsumerApplication.java rename to spring-boot-examples/pubsub/consumer-app/src/test/java/io/dapr/springboot/examples/consumer/TestConsumerApplication.java diff --git a/spring-boot-examples/consumer-app/src/test/resources/application.properties b/spring-boot-examples/pubsub/consumer-app/src/test/resources/application.properties similarity index 100% rename from spring-boot-examples/consumer-app/src/test/resources/application.properties rename to spring-boot-examples/pubsub/consumer-app/src/test/resources/application.properties diff --git a/spring-boot-examples/kubernetes/README.md b/spring-boot-examples/pubsub/kubernetes/README.md similarity index 92% rename from spring-boot-examples/kubernetes/README.md rename to spring-boot-examples/pubsub/kubernetes/README.md index 6b74f48c8d..2f8dd1ce37 100644 --- a/spring-boot-examples/kubernetes/README.md +++ b/spring-boot-examples/pubsub/kubernetes/README.md @@ -41,7 +41,7 @@ helm upgrade --install dapr dapr/dapr \ Now that we have our cluster set up with a local container registry, we need to build our `producer-app` and `consumer-app` containers. For this we will use Spring Boot build it functions to create container images using [Buildpacks](https://buildpacks.io): -From inside the `spring-boot-examples/producer-app` directory you can run the following command to create a container: +From inside the `spring-boot-examples/pubsub/producer-app` directory you can run the following command to create a container: ```bash mvn spring-boot:build-image ``` @@ -59,7 +59,7 @@ docker push localhost:5001/sb-producer-app podman push localhost:5001/sb-producer-app --tls-verify=false ``` -From inside the `spring-boot-examples/consumer-app` directory you can run the following command to create a container: +From inside the `spring-boot-examples/pubsub/consumer-app` directory you can run the following command to create a container: ```bash mvn spring-boot:build-image ``` @@ -95,7 +95,7 @@ helm install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql --se ``` Once we have these components up and running we can install the application by running from inside -the `spring-boot-examples/kubernetes/` directory: +the `spring-boot-examples/pubsub/kubernetes/` directory: ```bash kubectl apply -f . diff --git a/spring-boot-examples/kubernetes/consumer-app.yaml b/spring-boot-examples/pubsub/kubernetes/consumer-app.yaml similarity index 100% rename from spring-boot-examples/kubernetes/consumer-app.yaml rename to spring-boot-examples/pubsub/kubernetes/consumer-app.yaml diff --git a/spring-boot-examples/kubernetes/kind-with-registry.sh b/spring-boot-examples/pubsub/kubernetes/kind-with-registry.sh similarity index 100% rename from spring-boot-examples/kubernetes/kind-with-registry.sh rename to spring-boot-examples/pubsub/kubernetes/kind-with-registry.sh diff --git a/spring-boot-examples/kubernetes/kvbinding.yaml b/spring-boot-examples/pubsub/kubernetes/kvbinding.yaml similarity index 100% rename from spring-boot-examples/kubernetes/kvbinding.yaml rename to spring-boot-examples/pubsub/kubernetes/kvbinding.yaml diff --git a/spring-boot-examples/kubernetes/kvstore.yaml b/spring-boot-examples/pubsub/kubernetes/kvstore.yaml similarity index 100% rename from spring-boot-examples/kubernetes/kvstore.yaml rename to spring-boot-examples/pubsub/kubernetes/kvstore.yaml diff --git a/spring-boot-examples/kubernetes/producer-app.yaml b/spring-boot-examples/pubsub/kubernetes/producer-app.yaml similarity index 100% rename from spring-boot-examples/kubernetes/producer-app.yaml rename to spring-boot-examples/pubsub/kubernetes/producer-app.yaml diff --git a/spring-boot-examples/kubernetes/pubsub.yaml b/spring-boot-examples/pubsub/kubernetes/pubsub.yaml similarity index 100% rename from spring-boot-examples/kubernetes/pubsub.yaml rename to spring-boot-examples/pubsub/kubernetes/pubsub.yaml diff --git a/spring-boot-examples/pubsub/pom.xml b/spring-boot-examples/pubsub/pom.xml new file mode 100644 index 0000000000..36bd7d3d56 --- /dev/null +++ b/spring-boot-examples/pubsub/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + io.dapr + spring-boot-examples + 1.20.0-SNAPSHOT + ../pom.xml + + + pubsub + pom + + + true + + + + producer-app + consumer-app + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + true + + + + + diff --git a/spring-boot-examples/producer-app/pom.xml b/spring-boot-examples/pubsub/producer-app/pom.xml similarity index 97% rename from spring-boot-examples/producer-app/pom.xml rename to spring-boot-examples/pubsub/producer-app/pom.xml index a5efd0c56e..8c84966e68 100644 --- a/spring-boot-examples/producer-app/pom.xml +++ b/spring-boot-examples/pubsub/producer-app/pom.xml @@ -5,7 +5,7 @@ io.dapr - spring-boot-examples + pubsub 1.20.0-SNAPSHOT ../pom.xml diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Jackson3SerializerConfiguration.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/Jackson3SerializerConfiguration.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Jackson3SerializerConfiguration.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/Jackson3SerializerConfiguration.java diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Order.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/Order.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/Order.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/Order.java diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderDTO.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderDTO.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderDTO.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderDTO.java diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderRepository.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderRepository.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderRepository.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrderRepository.java diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrdersRestController.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrdersRestController.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrdersRestController.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/OrdersRestController.java diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerAppConfiguration.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerAppConfiguration.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerAppConfiguration.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerAppConfiguration.java diff --git a/spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerApplication.java b/spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerApplication.java similarity index 100% rename from spring-boot-examples/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerApplication.java rename to spring-boot-examples/pubsub/producer-app/src/main/java/io/dapr/springboot/examples/producer/ProducerApplication.java diff --git a/spring-boot-examples/producer-app/src/main/resources/application.properties b/spring-boot-examples/pubsub/producer-app/src/main/resources/application.properties similarity index 100% rename from spring-boot-examples/producer-app/src/main/resources/application.properties rename to spring-boot-examples/pubsub/producer-app/src/main/resources/application.properties diff --git a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/DaprTestContainersConfig.java b/spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/DaprTestContainersConfig.java similarity index 100% rename from spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/DaprTestContainersConfig.java rename to spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/DaprTestContainersConfig.java diff --git a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java b/spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java similarity index 76% rename from spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java rename to spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java index be5058bf51..b6c464b26b 100644 --- a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java +++ b/spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/ProducerAppIT.java @@ -15,12 +15,6 @@ import io.dapr.client.DaprClient; import io.dapr.springboot.DaprAutoConfiguration; -import io.dapr.springboot.examples.producer.Customer; -import io.dapr.springboot.examples.producer.CustomerStore; -import io.dapr.springboot.examples.producer.OrderDTO; -import io.dapr.springboot.examples.producer.workflow.CustomerFollowupActivity; -import io.dapr.springboot.examples.producer.workflow.CustomerWorkflow; -import io.dapr.springboot.examples.producer.workflow.RegisterCustomerActivity; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.wait.strategy.DaprWait; import org.junit.jupiter.api.AfterEach; @@ -40,20 +34,15 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest(classes = {TestProducerApplication.class, DaprTestContainersConfig.class, - DaprAutoConfiguration.class, CustomerWorkflow.class, CustomerFollowupActivity.class, - RegisterCustomerActivity.class, CustomerStore.class}, + DaprAutoConfiguration.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) class ProducerAppIT { @Autowired private TestSubscriberRestController controller; - @Autowired - private CustomerStore customerStore; - @Autowired private DaprClient daprClient; @@ -179,35 +168,4 @@ void testOrdersEndpointAndMessaging() { } - @Test - void testCustomersWorkflows() { - - client.post() - .uri("/customers") - .contentType(MediaType.APPLICATION_JSON) - .body("{\"customerName\": \"salaboy\"}") - .exchange() - .expectStatus().isOk(); - - - await().atMost(Duration.ofSeconds(15)) - .until(customerStore.getCustomers()::size, equalTo(1)); - Customer customer = customerStore.getCustomer("salaboy"); - assertTrue(customer.isInCustomerDB()); - String workflowId = customer.getWorkflowId(); - - client.post() - .uri("/customers/followup") - .contentType(MediaType.APPLICATION_JSON) - .body("{ \"workflowId\": \"" + workflowId + "\",\"customerName\": \"salaboy\" }") - .exchange() - .expectStatus().isOk(); - - assertEquals(1, customerStore.getCustomers().size()); - - await().atMost(Duration.ofSeconds(10)) - .until(customerStore.getCustomer("salaboy")::isFollowUp, equalTo(true)); - - } - } diff --git a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestProducerApplication.java b/spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestProducerApplication.java similarity index 100% rename from spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestProducerApplication.java rename to spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestProducerApplication.java diff --git a/spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestSubscriberRestController.java b/spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestSubscriberRestController.java similarity index 100% rename from spring-boot-examples/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestSubscriberRestController.java rename to spring-boot-examples/pubsub/producer-app/src/test/java/io/dapr/springboot/examples/producer/TestSubscriberRestController.java diff --git a/spring-boot-examples/producer-app/src/test/resources/application.properties b/spring-boot-examples/pubsub/producer-app/src/test/resources/application.properties similarity index 100% rename from spring-boot-examples/producer-app/src/test/resources/application.properties rename to spring-boot-examples/pubsub/producer-app/src/test/resources/application.properties diff --git a/spring-boot-examples/pubsub/spotbugs-exclude.xml b/spring-boot-examples/pubsub/spotbugs-exclude.xml new file mode 100644 index 0000000000..264fc79b0a --- /dev/null +++ b/spring-boot-examples/pubsub/spotbugs-exclude.xml @@ -0,0 +1,6 @@ + + + + + +