From 75adfb93a387b885f9eb77307654ebe2e51073c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Andr=C3=A9?= Date: Thu, 29 Oct 2026 15:46:12 -0300 Subject: [PATCH] add example event-workflow spring boot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matheus André --- .github/workflows/validate.yml | 1 + spring-boot-examples/README.md | 1 + .../workflows/event-workflow/README.md | 182 ++++++++++++++++++ .../workflows/event-workflow/pom.xml | 75 ++++++++ .../EventWorkflowApplication.java | 26 +++ .../EventWorkflowConfiguration.java | 48 +++++ .../examples/eventworkflow/Order.java | 65 +++++++ .../eventworkflow/OrderEventsSubscriber.java | 83 ++++++++ .../OrderProcessingWorkflow.java | 59 ++++++ .../examples/eventworkflow/OrderStatus.java | 59 ++++++ .../eventworkflow/OrderStatusStore.java | 40 ++++ .../eventworkflow/OrdersRestController.java | 88 +++++++++ .../examples/eventworkflow/Payment.java | 54 ++++++ .../PublishOrderStatusActivity.java | 40 ++++ .../ReserveInventoryActivity.java | 34 ++++ .../src/main/resources/application.properties | 2 + .../DaprTestContainersConfig.java | 124 ++++++++++++ .../eventworkflow/EventWorkflowAppIT.java | 108 +++++++++++ .../TestEventWorkflowApplication.java | 29 +++ .../src/test/resources/application.properties | 2 + spring-boot-examples/workflows/pom.xml | 1 + 21 files changed, 1121 insertions(+) create mode 100644 spring-boot-examples/workflows/event-workflow/README.md create mode 100644 spring-boot-examples/workflows/event-workflow/pom.xml create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowApplication.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowConfiguration.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Order.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderEventsSubscriber.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderProcessingWorkflow.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatus.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatusStore.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrdersRestController.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Payment.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/PublishOrderStatusActivity.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/ReserveInventoryActivity.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/main/resources/application.properties create mode 100644 spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/DaprTestContainersConfig.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowAppIT.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/TestEventWorkflowApplication.java create mode 100644 spring-boot-examples/workflows/event-workflow/src/test/resources/application.properties diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index cf0dcce8f0..039b70484b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -126,6 +126,7 @@ jobs: (cd examples && mm.py ./src/main/java/io/dapr/examples/workflows/README.md) (cd spring-boot-examples/pubsub && mm.py README.md) (cd spring-boot-examples/workflows/patterns && mm.py README.md) + (cd spring-boot-examples/workflows/event-workflow && 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) (cd examples && mm.py ./src/main/java/io/dapr/examples/invoke/http/README.md) diff --git a/spring-boot-examples/README.md b/spring-boot-examples/README.md index ca03bf9bc8..892bccc3bc 100644 --- a/spring-boot-examples/README.md +++ b/spring-boot-examples/README.md @@ -6,5 +6,6 @@ folder per Dapr API: - [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. + - [Event Workflow](workflows/event-workflow/README.md): workflows started and continued by Pub/Sub events. - [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/workflows/event-workflow/README.md b/spring-boot-examples/workflows/event-workflow/README.md new file mode 100644 index 0000000000..16ef6d7354 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/README.md @@ -0,0 +1,182 @@ +# Dapr Spring Boot Event Driven Workflow Example + +This application shows how to combine [Dapr Workflows](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/) +with [Dapr Pub/Sub](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/) in a Spring Boot +application: + +- An `orders` event starts a new workflow instance. +- The workflow waits for a `payments` event, which is forwarded to it as an external event. +- The workflow publishes its progress to the `order-status` topic from an activity. + +```mermaid +graph LR + O[orders topic] -->|start workflow| SW((Start + Workflow)) + SW --> A1[Reserve Inventory] + A1 --> A2[Publish WAITING_FOR_PAYMENT] + A2 --> E{Wait for + PaymentReceived} + P[payments topic] -->|raise event| E + E --> A3[Publish COMPLETED] + A3 --> EW((End + Workflow)) + A2 --> S[order-status topic] + A3 --> S +``` + +The main classes are: + +- [`OrderEventsSubscriber`](src/main/java/io/dapr/springboot/examples/eventworkflow/OrderEventsSubscriber.java): + subscribes to the topics using the `@Topic` annotation. It starts the workflow with `DaprWorkflowClient.scheduleNewWorkflow`, + using the order id as the workflow instance id, and forwards payments with `DaprWorkflowClient.raiseEvent`. +- [`OrderProcessingWorkflow`](src/main/java/io/dapr/springboot/examples/eventworkflow/OrderProcessingWorkflow.java): + waits for the `PaymentReceived` external event. If the payment does not arrive within 5 minutes, the order is cancelled. +- [`PublishOrderStatusActivity`](src/main/java/io/dapr/springboot/examples/eventworkflow/PublishOrderStatusActivity.java): + publishes the status events using `DaprMessagingTemplate`. Publishing happens in an activity because workflow code + is replayed and must not perform I/O directly. + +## Running this example from source code + +To run this example you will need: +- Java SDK +- Maven +- Docker or a container runtime such as Podman + +From the `spring-boot-examples/workflows/event-workflow` directory you can start the service by running the following command: + + + + +```sh +../../../mvnw spring-boot:test-run +``` + + + +By running the `spring-boot:test-run` goal, the application is loaded using the [test configurations](src/test/java/io/dapr/springboot/examples/eventworkflow/DaprTestContainersConfig.java) +configured using [Testcontainers](https://testcontainers.com) to bootstrap the [Dapr](https://dapr.io) sidecar, +PostgreSQL (used as the workflow state store) and RabbitMQ (used as the Pub/Sub broker). + +## Placing an order + +Publish an order event by sending a POST request to the application: + + + + +```sh +curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{ "id": "order-1", "item": "the mars volta EP", "amount": 1 }' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 +``` + + + +The subscriber of the `orders` topic starts a workflow instance. The workflow reserves the inventory and publishes +the `WAITING_FOR_PAYMENT` status. You can check the last status received from the `order-status` topic: + + + + +```sh +curl --fail localhost:8080/orders/order-1/status --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 +``` + + + +```bash +{"orderId":"order-1","status":"WAITING_FOR_PAYMENT"} +``` + +In the application output you should see: + +```bash +i.d.s.e.e.OrdersRestController : Publishing Order Event: Order{id='order-1', item='the mars volta EP', amount=1} +i.d.s.e.e.OrderEventsSubscriber : Order Event Received: Order{id='order-1', item='the mars volta EP', amount=1} +i.d.s.e.e.OrderEventsSubscriber : Workflow instance order-1 started +i.d.s.e.e.OrderProcessingWorkflow : Starting Workflow: io.dapr.springboot.examples.eventworkflow.OrderProcessingWorkflow +i.d.s.e.e.ReserveInventoryActivity : Reserving 1 unit(s) of 'the mars volta EP' for order order-1 +i.d.s.e.e.PublishOrderStatusActivity : Publishing Order Status Event: OrderStatus{orderId='order-1', status='WAITING_FOR_PAYMENT'} +i.d.s.e.e.OrderEventsSubscriber : Order Status Event Received: OrderStatus{orderId='order-1', status='WAITING_FOR_PAYMENT'} +``` + +## Paying for the order + +Publish a payment event for the order: + + + + +```sh +curl -X POST localhost:8080/orders/order-1/payment -H 'Content-Type: application/json' -d '{ "total": 10.0 }' --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 +``` + + + +The subscriber of the `payments` topic raises the `PaymentReceived` event on the workflow instance `order-1`, and the +workflow completes the order: + + + + +```sh +curl --fail localhost:8080/orders/order-1/status --retry 10 --max-time 20 --retry-all-errors --retry-max-time 90 +``` + + + +```bash +{"orderId":"order-1","status":"COMPLETED"} +``` + +In the application output you should see: + +```bash +i.d.s.e.e.OrderEventsSubscriber : Payment Event Received: Payment{orderId='order-1', total=10.0} +i.d.s.e.e.OrderProcessingWorkflow : Payment received for order order-1: Payment{orderId='order-1', total=10.0} +i.d.s.e.e.PublishOrderStatusActivity : Publishing Order Status Event: OrderStatus{orderId='order-1', status='COMPLETED'} +i.d.s.e.e.OrderEventsSubscriber : Order Status Event Received: OrderStatus{orderId='order-1', status='COMPLETED'} +``` + +## Testing event driven workflows + +The [`EventWorkflowAppIT`](src/test/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowAppIT.java) test uses +Testcontainers to run the same flow end to end: it places an order, waits for the `WAITING_FOR_PAYMENT` status, +pays the order and checks that the workflow instance completed. diff --git a/spring-boot-examples/workflows/event-workflow/pom.xml b/spring-boot-examples/workflows/event-workflow/pom.xml new file mode 100644 index 0000000000..a351cefe69 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + io.dapr + workflows + 1.20.0-SNAPSHOT + ../pom.xml + + + event-workflow + event-workflow + Spring Boot 4, Testcontainers and Dapr Integration Examples :: Event Driven Workflow + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + + + io.dapr.spring + dapr-spring-boot-starter + + + io.dapr.spring + dapr-spring-boot-starter-test + test + + + org.testcontainers + testcontainers-postgresql + test + + + org.testcontainers + testcontainers-rabbitmq + 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/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowApplication.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowApplication.java new file mode 100644 index 0000000000..9b5884fa10 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowApplication.java @@ -0,0 +1,26 @@ +/* + * Copyright 2026 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.eventworkflow; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class EventWorkflowApplication { + + public static void main(String[] args) { + SpringApplication.run(EventWorkflowApplication.class, args); + } + +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowConfiguration.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowConfiguration.java new file mode 100644 index 0000000000..21ebbc8da6 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.client.DaprClient; +import io.dapr.spring.boot.properties.pubsub.DaprPubSubProperties; +import io.dapr.spring.messaging.DaprMessagingTemplate; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({DaprPubSubProperties.class}) +public class EventWorkflowConfiguration { + + public static final String ORDERS_TOPIC = "orders"; + public static final String PAYMENTS_TOPIC = "payments"; + public static final String ORDER_STATUS_TOPIC = "order-status"; + + @Bean + public DaprMessagingTemplate orderMessagingTemplate(DaprClient daprClient, + DaprPubSubProperties daprPubSubProperties) { + return new DaprMessagingTemplate<>(daprClient, daprPubSubProperties.getName(), false); + } + + @Bean + public DaprMessagingTemplate paymentMessagingTemplate(DaprClient daprClient, + DaprPubSubProperties daprPubSubProperties) { + return new DaprMessagingTemplate<>(daprClient, daprPubSubProperties.getName(), false); + } + + @Bean + public DaprMessagingTemplate orderStatusMessagingTemplate(DaprClient daprClient, + DaprPubSubProperties daprPubSubProperties) { + return new DaprMessagingTemplate<>(daprClient, daprPubSubProperties.getName(), false); + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Order.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Order.java new file mode 100644 index 0000000000..a6f2f60177 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Order.java @@ -0,0 +1,65 @@ +/* + * Copyright 2026 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.eventworkflow; + +public class Order { + private String id; + private String item; + private Integer amount; + + public Order() { + } + + /** + * Creates a new Order. + * + * @param id order id + * @param item item reference + * @param amount of items in the order + */ + public Order(String id, String item, Integer amount) { + this.id = id; + this.item = item; + this.amount = amount; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } + + public Integer getAmount() { + return amount; + } + + public void setAmount(Integer amount) { + this.amount = amount; + } + + @Override + public String toString() { + return "Order{" + "id='" + id + '\'' + ", item='" + item + '\'' + ", amount=" + amount + '}'; + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderEventsSubscriber.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderEventsSubscriber.java new file mode 100644 index 0000000000..933356e3f4 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderEventsSubscriber.java @@ -0,0 +1,83 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.Topic; +import io.dapr.client.domain.CloudEvent; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceAlreadyExistsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * Subscribes to the Pub/Sub topics that drive the {@link OrderProcessingWorkflow}. + */ +@RestController +public class OrderEventsSubscriber { + + private final Logger logger = LoggerFactory.getLogger(OrderEventsSubscriber.class); + + @Autowired + private DaprWorkflowClient daprWorkflowClient; + + @Autowired + private OrderStatusStore orderStatusStore; + + /** + * Start a workflow instance for each order event. The order id is used as the workflow instance id, + * so a redelivered event does not start a second instance for the same order. + * @param cloudEvent order event + */ + @PostMapping("events/orders") + @Topic(pubsubName = "pubsub", name = EventWorkflowConfiguration.ORDERS_TOPIC) + public void onOrder(@RequestBody CloudEvent cloudEvent) { + Order order = cloudEvent.getData(); + logger.info("Order Event Received: " + order); + try { + daprWorkflowClient.scheduleNewWorkflow(OrderProcessingWorkflow.class, order, order.getId()); + logger.info("Workflow instance " + order.getId() + " started"); + } catch (WorkflowInstanceAlreadyExistsException e) { + logger.info("Workflow instance " + order.getId() + " already exists, ignoring duplicated event"); + } + } + + /** + * Forward each payment event to the workflow instance waiting for it. + * @param cloudEvent payment event + */ + @PostMapping("events/payments") + @Topic(pubsubName = "pubsub", name = EventWorkflowConfiguration.PAYMENTS_TOPIC) + public void onPayment(@RequestBody CloudEvent cloudEvent) { + Payment payment = cloudEvent.getData(); + logger.info("Payment Event Received: " + payment); + daprWorkflowClient.raiseEvent(payment.getOrderId(), OrderProcessingWorkflow.PAYMENT_RECEIVED_EVENT, payment); + } + + /** + * Record the status events published by the workflow. + * @param cloudEvent order status event + */ + @PostMapping("events/order-status") + @Topic(pubsubName = "pubsub", name = EventWorkflowConfiguration.ORDER_STATUS_TOPIC) + public void onOrderStatus(@RequestBody CloudEvent cloudEvent) { + OrderStatus orderStatus = cloudEvent.getData(); + logger.info("Order Status Event Received: " + orderStatus); + orderStatusStore.put(orderStatus); + } + +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderProcessingWorkflow.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderProcessingWorkflow.java new file mode 100644 index 0000000000..7c7981e4dc --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderProcessingWorkflow.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.durabletask.TaskCanceledException; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +/** + * Workflow started by an order event and continued by a payment event, both received through Pub/Sub. + * Status changes are published back to Pub/Sub from an activity, as workflows must not perform I/O directly. + */ +@Component +public class OrderProcessingWorkflow implements Workflow { + + public static final String PAYMENT_RECEIVED_EVENT = "PaymentReceived"; + + private static final Duration PAYMENT_TIMEOUT = Duration.ofMinutes(5); + + @Override + public WorkflowStub create() { + return ctx -> { + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + Order order = ctx.getInput(Order.class); + + ctx.callActivity(ReserveInventoryActivity.class.getName(), order).await(); + + OrderStatus waiting = new OrderStatus(order.getId(), OrderStatus.WAITING_FOR_PAYMENT); + ctx.callActivity(PublishOrderStatusActivity.class.getName(), waiting).await(); + + OrderStatus result; + try { + Payment payment = ctx.waitForExternalEvent(PAYMENT_RECEIVED_EVENT, PAYMENT_TIMEOUT, Payment.class).await(); + ctx.getLogger().info("Payment received for order " + order.getId() + ": " + payment); + result = new OrderStatus(order.getId(), OrderStatus.COMPLETED); + } catch (TaskCanceledException e) { + ctx.getLogger().info("Payment not received in time for order " + order.getId()); + result = new OrderStatus(order.getId(), OrderStatus.CANCELLED); + } + + ctx.callActivity(PublishOrderStatusActivity.class.getName(), result).await(); + ctx.complete(result); + }; + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatus.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatus.java new file mode 100644 index 0000000000..287e00c2d9 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatus.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026 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.eventworkflow; + +public class OrderStatus { + + public static final String WAITING_FOR_PAYMENT = "WAITING_FOR_PAYMENT"; + public static final String COMPLETED = "COMPLETED"; + public static final String CANCELLED = "CANCELLED"; + + private String orderId; + private String status; + + public OrderStatus() { + } + + /** + * Creates a new OrderStatus. + * + * @param orderId id of the order + * @param status current status of the order + */ + public OrderStatus(String orderId, String status) { + this.orderId = orderId; + this.status = status; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + public String toString() { + return "OrderStatus{" + "orderId='" + orderId + '\'' + ", status='" + status + '\'' + '}'; + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatusStore.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatusStore.java new file mode 100644 index 0000000000..c542bc77a6 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrderStatusStore.java @@ -0,0 +1,40 @@ +/* + * Copyright 2026 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.eventworkflow; + +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Keeps the last status received from the order-status topic for each order. + */ +@Component +public class OrderStatusStore { + + private final Map statuses = new ConcurrentHashMap<>(); + + public void put(OrderStatus orderStatus) { + statuses.put(orderStatus.getOrderId(), orderStatus); + } + + public OrderStatus get(String orderId) { + return statuses.get(orderId); + } + + public void clear() { + statuses.clear(); + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrdersRestController.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrdersRestController.java new file mode 100644 index 0000000000..66d4e4457a --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/OrdersRestController.java @@ -0,0 +1,88 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.spring.messaging.DaprMessagingTemplate; +import io.dapr.spring.workflows.config.EnableDaprWorkflows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +@RestController +@EnableDaprWorkflows +public class OrdersRestController { + + private final Logger logger = LoggerFactory.getLogger(OrdersRestController.class); + + @Autowired + private DaprMessagingTemplate orderMessagingTemplate; + + @Autowired + private DaprMessagingTemplate paymentMessagingTemplate; + + @Autowired + private OrderStatusStore orderStatusStore; + + /** + * Publish an order event. The workflow is started by the subscriber of the orders topic. + * @param order from the customer + * @return confirmation that the order event was published + */ + @PostMapping("/orders") + public String placeOrder(@RequestBody Order order) { + if (order.getId() == null) { + order.setId(UUID.randomUUID().toString()); + } + logger.info("Publishing Order Event: " + order); + orderMessagingTemplate.send(EventWorkflowConfiguration.ORDERS_TOPIC, order); + return "Order Event Published: " + order.getId(); + } + + /** + * Publish a payment event. The subscriber of the payments topic forwards it to the waiting workflow. + * @param orderId id of the order being paid + * @param payment details of the payment + * @return confirmation that the payment event was published + */ + @PostMapping("/orders/{orderId}/payment") + public String pay(@PathVariable("orderId") String orderId, @RequestBody Payment payment) { + payment.setOrderId(orderId); + logger.info("Publishing Payment Event: " + payment); + paymentMessagingTemplate.send(EventWorkflowConfiguration.PAYMENTS_TOPIC, payment); + return "Payment Event Published: " + orderId; + } + + /** + * Get the last status published by the workflow for an order. + * @param orderId id of the order + * @return the last known status of the order + */ + @GetMapping("/orders/{orderId}/status") + public ResponseEntity status(@PathVariable("orderId") String orderId) { + OrderStatus orderStatus = orderStatusStore.get(orderId); + if (orderStatus == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(orderStatus); + } + +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Payment.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Payment.java new file mode 100644 index 0000000000..a6d06e4933 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/Payment.java @@ -0,0 +1,54 @@ +/* + * Copyright 2026 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.eventworkflow; + +public class Payment { + private String orderId; + private Double total; + + public Payment() { + } + + /** + * Creates a new Payment. + * + * @param orderId id of the order being paid + * @param total amount paid + */ + public Payment(String orderId, Double total) { + this.orderId = orderId; + this.total = total; + } + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public Double getTotal() { + return total; + } + + public void setTotal(Double total) { + this.total = total; + } + + @Override + public String toString() { + return "Payment{" + "orderId='" + orderId + '\'' + ", total=" + total + '}'; + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/PublishOrderStatusActivity.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/PublishOrderStatusActivity.java new file mode 100644 index 0000000000..d1a6b00fef --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/PublishOrderStatusActivity.java @@ -0,0 +1,40 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.spring.messaging.DaprMessagingTemplate; +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class PublishOrderStatusActivity implements WorkflowActivity { + + private final Logger logger = LoggerFactory.getLogger(PublishOrderStatusActivity.class); + + @Autowired + private DaprMessagingTemplate messagingTemplate; + + @Override + public Object run(WorkflowActivityContext ctx) { + logger.info("Starting Activity: " + ctx.getName()); + OrderStatus orderStatus = ctx.getInput(OrderStatus.class); + logger.info("Publishing Order Status Event: " + orderStatus); + messagingTemplate.send(EventWorkflowConfiguration.ORDER_STATUS_TOPIC, orderStatus); + return orderStatus; + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/ReserveInventoryActivity.java b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/ReserveInventoryActivity.java new file mode 100644 index 0000000000..14ac80dded --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/java/io/dapr/springboot/examples/eventworkflow/ReserveInventoryActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.eventworkflow; + +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 ReserveInventoryActivity implements WorkflowActivity { + + private final Logger logger = LoggerFactory.getLogger(ReserveInventoryActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + logger.info("Starting Activity: " + ctx.getName()); + Order order = ctx.getInput(Order.class); + logger.info("Reserving " + order.getAmount() + " unit(s) of '" + order.getItem() + "' for order " + order.getId()); + return order; + } +} diff --git a/spring-boot-examples/workflows/event-workflow/src/main/resources/application.properties b/spring-boot-examples/workflows/event-workflow/src/main/resources/application.properties new file mode 100644 index 0000000000..71e75dc361 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/main/resources/application.properties @@ -0,0 +1,2 @@ +dapr.pubsub.name=pubsub +spring.application.name=event-workflow-app diff --git a/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/DaprTestContainersConfig.java b/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/DaprTestContainersConfig.java new file mode 100644 index 0000000000..12cb4afb0a --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/DaprTestContainersConfig.java @@ -0,0 +1,124 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +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 org.testcontainers.postgresql.PostgreSQLContainer; +import org.testcontainers.rabbitmq.RabbitMQContainer; +import org.testcontainers.utility.DockerImageName; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; + +/** + * Starts the Dapr sidecar with a PostgreSQL state store for the workflow state and a RabbitMQ Pub/Sub broker + * for the order, payment and order-status topics. + */ +@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() { + + } + }; + + 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 PostgreSQLContainer postgreSQLContainer(Network daprNetwork) { + return new PostgreSQLContainer(DockerImageName.parse("postgres")) + .withNetworkAliases("postgresql") + .withDatabaseName("dapr") + .withUsername("postgres") + .withPassword("postgres") + .withNetwork(daprNetwork); + } + + @Bean + public RabbitMQContainer rabbitMQContainer(Network daprNetwork, Environment env) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + return new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.7.25-management-alpine")) + .withExposedPorts(5672) + .withNetworkAliases("rabbitmq") + .withReuse(reuse) + .withNetwork(daprNetwork); + } + + @Bean + @ServiceConnection + public DaprContainer daprContainer(Network daprNetwork, PostgreSQLContainer postgreSQLContainer, + RabbitMQContainer rabbitMQContainer, Environment env) { + boolean reuse = env.getProperty("reuse", Boolean.class, false); + + Map postgreSQLDetails = new HashMap<>(); + postgreSQLDetails.put("host", "postgresql"); + postgreSQLDetails.put("user", "postgres"); + postgreSQLDetails.put("password", "postgres"); + postgreSQLDetails.put("database", "dapr"); + postgreSQLDetails.put("port", "5432"); + postgreSQLDetails.put("actorStateStore", String.valueOf(true)); + + Map rabbitMqProperties = new HashMap<>(); + rabbitMqProperties.put("connectionString", "amqp://guest:guest@rabbitmq:5672"); + rabbitMqProperties.put("user", "guest"); + rabbitMqProperties.put("password", "guest"); + + return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) + .withAppName("event-workflow-app") + .withNetwork(daprNetwork) + .withComponent(new Component("kvstore", "state.postgresql", "v2", postgreSQLDetails)) + .withComponent(new Component("pubsub", "pubsub.rabbitmq", "v1", rabbitMqProperties)) + .withDaprLogLevel(DaprLogLevel.INFO) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withAppPort(8080).withAppChannelAddress("host.testcontainers.internal") + .withReusablePlacement(reuse) + .withReusableScheduler(reuse) + .withAppHealthCheckPath("/actuator/health") + .dependsOn(postgreSQLContainer, rabbitMQContainer); + } + +} diff --git a/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowAppIT.java b/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowAppIT.java new file mode 100644 index 0000000000..f5dabd3934 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/EventWorkflowAppIT.java @@ -0,0 +1,108 @@ +/* + * Copyright 2026 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.eventworkflow; + +import io.dapr.springboot.DaprAutoConfiguration; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowRuntimeStatus; +import io.dapr.workflows.client.WorkflowState; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.client.RestTestClient; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.time.Duration; +import java.util.UUID; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest(classes = {TestEventWorkflowApplication.class, DaprTestContainersConfig.class, + DaprAutoConfiguration.class}, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +class EventWorkflowAppIT { + + private static final String SUBSCRIPTION_MESSAGE_PATTERN = ".*app is subscribed to the following topics.*"; + + @Autowired + private DaprContainer daprContainer; + + @Autowired + private DaprWorkflowClient daprWorkflowClient; + + @Autowired + private OrderStatusStore orderStatusStore; + + @LocalServerPort + private int port; + + private RestTestClient client; + + @BeforeAll + public static void setup() { + org.testcontainers.Testcontainers.exposeHostPorts(8080); + } + + @BeforeEach + void setUp() { + client = RestTestClient.bindToServer() + .baseUrl("http://localhost:" + port) + .build(); + Wait.forLogMessage(SUBSCRIPTION_MESSAGE_PATTERN, 1).waitUntilReady(daprContainer); + orderStatusStore.clear(); + } + + @Test + void testOrderCompletedByPaymentEvent() { + String orderId = UUID.randomUUID().toString(); + + client.post() + .uri("/orders") + .contentType(MediaType.APPLICATION_JSON) + .body(new Order(orderId, "the mars volta EP", 1)) + .exchange() + .expectStatus().isOk(); + + await().atMost(Duration.ofSeconds(30)) + .until(() -> statusOf(orderId), s -> OrderStatus.WAITING_FOR_PAYMENT.equals(s)); + + client.post() + .uri("/orders/" + orderId + "/payment") + .contentType(MediaType.APPLICATION_JSON) + .body(new Payment(orderId, 10.0)) + .exchange() + .expectStatus().isOk(); + + await().atMost(Duration.ofSeconds(30)) + .until(() -> statusOf(orderId), s -> OrderStatus.COMPLETED.equals(s)); + + WorkflowState workflowState = daprWorkflowClient.getWorkflowState(orderId, true); + assertNotNull(workflowState); + assertEquals(WorkflowRuntimeStatus.COMPLETED, workflowState.getRuntimeStatus()); + assertEquals(OrderStatus.COMPLETED, workflowState.readOutputAs(OrderStatus.class).getStatus()); + } + + private String statusOf(String orderId) { + OrderStatus orderStatus = orderStatusStore.get(orderId); + return orderStatus == null ? null : orderStatus.getStatus(); + } + +} diff --git a/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/TestEventWorkflowApplication.java b/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/TestEventWorkflowApplication.java new file mode 100644 index 0000000000..f70104a97a --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/test/java/io/dapr/springboot/examples/eventworkflow/TestEventWorkflowApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2026 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.eventworkflow; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestEventWorkflowApplication { + + public static void main(String[] args) { + SpringApplication.from(EventWorkflowApplication::main) + .with(DaprTestContainersConfig.class) + .run(args); + org.testcontainers.Testcontainers.exposeHostPorts(8080); + } + +} diff --git a/spring-boot-examples/workflows/event-workflow/src/test/resources/application.properties b/spring-boot-examples/workflows/event-workflow/src/test/resources/application.properties new file mode 100644 index 0000000000..71e75dc361 --- /dev/null +++ b/spring-boot-examples/workflows/event-workflow/src/test/resources/application.properties @@ -0,0 +1,2 @@ +dapr.pubsub.name=pubsub +spring.application.name=event-workflow-app diff --git a/spring-boot-examples/workflows/pom.xml b/spring-boot-examples/workflows/pom.xml index 5d2f182972..f37f657a0c 100644 --- a/spring-boot-examples/workflows/pom.xml +++ b/spring-boot-examples/workflows/pom.xml @@ -21,6 +21,7 @@ patterns multi-app versioning + event-workflow