mirror of https://github.com/dapr/quickstarts.git
updating combined patterns
Signed-off-by: salaboy <Salaboy@gmail.com>
This commit is contained in:
parent
1ea44f871f
commit
0bdbe01f5d
|
|
@ -41,6 +41,12 @@
|
|||
<version>${dapr.spring.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.redis</groupId>
|
||||
<artifactId>testcontainers-redis</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
|
|
|
|||
|
|
@ -1,94 +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;
|
||||
|
||||
|
||||
import io.dapr.spring.workflows.config.EnableDaprWorkflows;
|
||||
import io.dapr.springboot.examples.workflowapp.OrderStatus;
|
||||
import io.dapr.springboot.examples.workflowapp.OrderWorkflow;
|
||||
import io.dapr.springboot.examples.workflowapp.Order;
|
||||
import io.dapr.workflows.client.DaprWorkflowClient;
|
||||
import io.dapr.workflows.client.WorkflowInstanceStatus;
|
||||
import io.dapr.workflows.client.WorkflowRuntimeStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@RestController
|
||||
/*
|
||||
* Dapr Workflows and Activities need to be registered in the DI container otherwise
|
||||
* the Dapr runtime does not know this application contains workflows and activities.
|
||||
* In Spring Boot Applications, the `@EnableDaprWorkflow` annotation takes care of registering
|
||||
* all workflows and activities components found in the classpath.
|
||||
*/
|
||||
@EnableDaprWorkflows
|
||||
public class WorkflowAppRestController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WorkflowAppRestController.class);
|
||||
|
||||
@Autowired
|
||||
private DaprWorkflowClient daprWorkflowClient;
|
||||
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* The DaprWorkflowClient is the API to manage workflows.
|
||||
* Here it is used to schedule a new workflow instance.
|
||||
*
|
||||
* @return the instanceId of the ExternalEventsWorkflow execution
|
||||
*/
|
||||
@PostMapping("start")
|
||||
public String basic(@RequestBody Order order) throws TimeoutException {
|
||||
logger.info("Received order: {}", order);
|
||||
instanceId = daprWorkflowClient.scheduleNewWorkflow(OrderWorkflow.class, order);
|
||||
return instanceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the status of the workflow
|
||||
*
|
||||
* @return the status of the ExternalEventsWorkflow instance
|
||||
*/
|
||||
@GetMapping("status")
|
||||
public String status() throws TimeoutException {
|
||||
if(instanceId != null && !instanceId.isEmpty()) {
|
||||
WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(instanceId, true);
|
||||
if (instanceState != null) {
|
||||
if (instanceState.getRuntimeStatus().equals(WorkflowRuntimeStatus.COMPLETED)) {
|
||||
var output = instanceState.readOutputAs(String.class);
|
||||
if (output != null && !output.isEmpty()) {
|
||||
return "Workflow Instance (" + instanceId + ") Status: " + instanceState.getRuntimeStatus().name() + "\n"
|
||||
+ "Output: " + output;
|
||||
}
|
||||
}
|
||||
return "Workflow Instance (" + instanceId + ") Status: " + instanceState.getRuntimeStatus().name();
|
||||
}
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise a workflow event
|
||||
*/
|
||||
@PostMapping("event")
|
||||
public void raiseEvent(@RequestBody OrderStatus approvalStatus) {
|
||||
daprWorkflowClient.raiseEvent(instanceId, "approval-event", approvalStatus);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
|
||||
public record RegisterShipmentResult() {
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package io.dapr.springboot.workflowapp;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.springboot.workflowapp.model.ProductInventory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class InventoryManagementService {
|
||||
|
||||
@Autowired
|
||||
private DaprClient daprClient;
|
||||
|
||||
public void createDefaultInventory(){
|
||||
ProductInventory productInventory = new ProductInventory("RBD001", 50);
|
||||
daprClient.saveState("inventory", productInventory.productId(), productInventory).block();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples;
|
||||
package io.dapr.springboot.workflowapp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples;
|
||||
package io.dapr.springboot.workflowapp;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
|
|
@ -31,4 +31,6 @@ public class WorkflowAppConfiguration {
|
|||
public ObjectMapper mapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.workflowapp;
|
||||
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.domain.State;
|
||||
import io.dapr.spring.workflows.config.EnableDaprWorkflows;
|
||||
import io.dapr.springboot.workflowapp.model.Order;
|
||||
import io.dapr.springboot.workflowapp.model.ProductInventory;
|
||||
import io.dapr.springboot.workflowapp.model.ShipmentRegistrationStatus;
|
||||
import io.dapr.springboot.workflowapp.workflow.OrderWorkflow;
|
||||
import io.dapr.workflows.client.DaprWorkflowClient;
|
||||
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.*;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@RestController
|
||||
/*
|
||||
* Dapr Workflows and Activities need to be registered in the DI container otherwise
|
||||
* the Dapr runtime does not know this application contains workflows and activities.
|
||||
* In Spring Boot Applications, the `@EnableDaprWorkflow` annotation takes care of registering
|
||||
* all workflows and activities components found in the classpath.
|
||||
*/
|
||||
@EnableDaprWorkflows
|
||||
public class WorkflowAppRestController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WorkflowAppRestController.class);
|
||||
|
||||
public static final String DAPR_INVENTORY_COMPONENT = "inventory";
|
||||
public static final String DAPR_PUBSUB_COMPONENT = "shippingpubsub";
|
||||
public static final String DAPR_PUBSUB_REGISTRATION_TOPIC = "shipment-registration-events";
|
||||
public static final String SHIPMENT_REGISTERED_EVENT = "shipment-registered-event";
|
||||
|
||||
@Autowired
|
||||
private DaprWorkflowClient daprWorkflowClient;
|
||||
|
||||
@Autowired
|
||||
private DaprClient daprClient;
|
||||
|
||||
private InventoryManagementService inventoryManagementService;
|
||||
|
||||
public WorkflowAppRestController(InventoryManagementService inventoryManagementService) {
|
||||
this.inventoryManagementService = inventoryManagementService;
|
||||
inventoryManagementService.createDefaultInventory();
|
||||
}
|
||||
|
||||
/**
|
||||
* The DaprWorkflowClient is the API to manage workflows.
|
||||
* Here it is used to schedule a new workflow instance.
|
||||
*
|
||||
* @return the instanceId of the OrderWorkflow execution
|
||||
*/
|
||||
@PostMapping("start")
|
||||
public String basic(@RequestBody Order order) throws TimeoutException {
|
||||
logger.info("Received order: {}", order);
|
||||
return daprWorkflowClient.scheduleNewWorkflow(OrderWorkflow.class, order);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This endpoint handles messages that are published to the shipment-registration-confirmed-events topic.
|
||||
* It uses the workflow management API to raise an event to the workflow instance to indicate that the
|
||||
* shipment has been registered by the ShippingApp.
|
||||
* @param status ShipmentRegistrationStatus
|
||||
*/
|
||||
@PostMapping("shipmentRegistered")
|
||||
public void shipmentRegistered(@RequestBody ShipmentRegistrationStatus status){
|
||||
logger.info("Shipment registered for order {}", status);
|
||||
daprWorkflowClient.raiseEvent(status.orderId(), SHIPMENT_REGISTERED_EVENT, status);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This endpoint is a manual helper method to restock the inventory.
|
||||
* @param productInventory ProductInventory to restock
|
||||
*/
|
||||
@PostMapping("/inventory/restock")
|
||||
public void shipmentRegistered(@RequestBody ProductInventory productInventory){
|
||||
daprClient.saveState(DAPR_INVENTORY_COMPONENT, productInventory.productId(), productInventory).block();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This endpoint is a manual helper method to check the inventory.
|
||||
* @param productId product Id that
|
||||
*/
|
||||
@PostMapping("/inventory/{productId}")
|
||||
public ResponseEntity<ProductInventory> checkInventory(@PathVariable("productId") String productId){
|
||||
State<ProductInventory> productInventoryState = daprClient.getState(DAPR_INVENTORY_COMPONENT, productId, ProductInventory.class).block();
|
||||
if(productInventoryState != null) {
|
||||
ProductInventory productInventory = productInventoryState.getValue();
|
||||
if (productInventory == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(productInventory);
|
||||
}else{
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record ActivityResult(boolean isSuccess, String message) { }
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record CustomerInfo(String id, String country) {}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record Order(String id, OrderItem orderItem, CustomerInfo customerInfo){}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record OrderStatus(boolean isSuccess, String message){}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record PaymentResult(boolean isSuccess) {
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record ProductInventory(String productId, int quantity) { }
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record RegisterShipmentResult(boolean isSuccess) {
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record ReimburseCustomerResult(boolean isSuccess) {
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record ShipmentRegistrationStatus(String orderId, boolean isSuccess, String message) { }
|
||||
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.model;
|
||||
|
||||
public record UpdateInventoryResult(boolean isSuccess, String message) { }
|
||||
|
|
@ -11,11 +11,12 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples.workflowapp;
|
||||
package io.dapr.springboot.workflowapp.workflow;
|
||||
|
||||
import io.dapr.durabletask.Task;
|
||||
import io.dapr.durabletask.TaskCanceledException;
|
||||
import io.dapr.springboot.examples.workflowapp.activities.*;
|
||||
import io.dapr.springboot.workflowapp.model.*;
|
||||
import io.dapr.springboot.workflowapp.workflow.activities.*;
|
||||
import io.dapr.workflows.Workflow;
|
||||
import io.dapr.workflows.WorkflowStub;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -43,16 +44,17 @@ public class OrderWorkflow implements Workflow {
|
|||
|
||||
if( tasksResult.stream().anyMatch(r -> !r.isSuccess())){
|
||||
var message = "Order processing failed. Reason: " + tasksResult.get(0).message();
|
||||
ctx.complete(new OrderStatus(false, message);
|
||||
ctx.complete(new OrderStatus(false, message));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Two activities are called in sequence (chaining pattern) where the UpdateInventory
|
||||
// activity is dependent on the result of the ProcessPayment activity:
|
||||
var paymentResult = ctx.callActivity(ProcessPaymentActivity.class.getName(), order.orderItem(), ActivityResult.class).await();
|
||||
var paymentResult = ctx.callActivity(ProcessPaymentActivity.class.getName(), order.orderItem(), PaymentResult.class).await();
|
||||
|
||||
if(paymentResult.isSuccess()){
|
||||
var inventoryResult = ctx.callActivity(ProcessPaymentActivity.class.getName(), order.orderItem(), UpdateInventoryResult.class).await();
|
||||
ctx.callActivity(UpdateInventoryActivity.class.getName(), order.orderItem(), UpdateInventoryResult.class).await();
|
||||
}
|
||||
|
||||
ShipmentRegistrationStatus shipmentRegistrationStatus = null;
|
||||
|
|
@ -11,14 +11,12 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples.workflowapp.activities;
|
||||
package io.dapr.springboot.workflowapp.workflow.activities;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.domain.State;
|
||||
import io.dapr.springboot.examples.workflowapp.ActivityResult;
|
||||
import io.dapr.springboot.examples.workflowapp.Order;
|
||||
import io.dapr.springboot.examples.workflowapp.OrderItem;
|
||||
import io.dapr.springboot.examples.workflowapp.ProductInventory;
|
||||
import io.dapr.springboot.workflowapp.model.ActivityResult;
|
||||
import io.dapr.springboot.workflowapp.model.OrderItem;
|
||||
import io.dapr.springboot.workflowapp.model.ProductInventory;
|
||||
import io.dapr.workflows.WorkflowActivity;
|
||||
import io.dapr.workflows.WorkflowActivityContext;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -38,14 +36,15 @@ public class CheckInventoryActivity implements WorkflowActivity {
|
|||
var orderItem = ctx.getInput(OrderItem.class);
|
||||
logger.info("{} : Received input: {}", ctx.getName(), orderItem);
|
||||
|
||||
var productInventory = daprClient.getState("inventory", orderItem.productId(), ProductInventory.class).block();
|
||||
|
||||
var productInventoryState = daprClient.getState("inventory", orderItem.productId(), ProductInventory.class).block();
|
||||
assert productInventoryState != null;
|
||||
ProductInventory productInventory = productInventoryState.getValue();
|
||||
if (productInventory == null)
|
||||
{
|
||||
return new ActivityResult(false, "");
|
||||
}
|
||||
|
||||
var isAvailable = productInventory.getValue().quantity() >= orderItem.quantity();
|
||||
var isAvailable = productInventory.quantity() >= orderItem.quantity();
|
||||
return new ActivityResult(isAvailable, "");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,10 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples.workflowapp.activities;
|
||||
package io.dapr.springboot.workflowapp.workflow.activities;
|
||||
|
||||
import io.dapr.springboot.examples.workflowapp.Order;
|
||||
import io.dapr.springboot.workflowapp.model.ActivityResult;
|
||||
import io.dapr.springboot.workflowapp.model.Order;
|
||||
import io.dapr.workflows.WorkflowActivity;
|
||||
import io.dapr.workflows.WorkflowActivityContext;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -36,6 +37,6 @@ public class CheckShippingDestinationActivity implements WorkflowActivity {
|
|||
var order = ctx.getInput(Order.class);
|
||||
// Imagine the order being processed by another system
|
||||
logger.info("{} : Processed Order: {}", ctx.getName(), order.id());
|
||||
return true;
|
||||
return new ActivityResult(true, "");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,9 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples.workflowapp.activities;
|
||||
package io.dapr.springboot.workflowapp.workflow.activities;
|
||||
|
||||
import io.dapr.springboot.workflowapp.model.PaymentResult;
|
||||
import io.dapr.workflows.WorkflowActivity;
|
||||
import io.dapr.workflows.WorkflowActivityContext;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -35,6 +36,6 @@ public class ProcessPaymentActivity implements WorkflowActivity {
|
|||
|
||||
// Imagine a notification being sent to the user
|
||||
logger.info("{} : Sending Notification: {}", ctx.getName(), message);
|
||||
return true;
|
||||
return new PaymentResult(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,10 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples.workflowapp.activities;
|
||||
package io.dapr.springboot.workflowapp.workflow.activities;
|
||||
|
||||
import io.dapr.springboot.examples.workflowapp.Order;
|
||||
import io.dapr.springboot.workflowapp.model.Order;
|
||||
import io.dapr.springboot.workflowapp.model.RegisterShipmentResult;
|
||||
import io.dapr.workflows.WorkflowActivity;
|
||||
import io.dapr.workflows.WorkflowActivityContext;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -27,7 +28,7 @@ public class RegisterShipmentActivity implements WorkflowActivity {
|
|||
public Object run(WorkflowActivityContext ctx) {
|
||||
Logger logger = LoggerFactory.getLogger(RegisterShipmentActivity.class);
|
||||
var order = ctx.getInput(Order.class);
|
||||
logger.info("{} : Request Approval for Order: {}", ctx.getName(), order.id());
|
||||
return true;
|
||||
logger.info("{} : RegisterShipmentActivity for Order: {}", ctx.getName(), order.id());
|
||||
return new RegisterShipmentResult(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,10 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples.workflowapp.activities;
|
||||
package io.dapr.springboot.workflowapp.workflow.activities;
|
||||
|
||||
import io.dapr.springboot.examples.workflowapp.Order;
|
||||
import io.dapr.springboot.workflowapp.model.Order;
|
||||
import io.dapr.springboot.workflowapp.model.ReimburseCustomerResult;
|
||||
import io.dapr.workflows.WorkflowActivity;
|
||||
import io.dapr.workflows.WorkflowActivityContext;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -28,6 +29,6 @@ public class ReimburseCustomerActivity implements WorkflowActivity {
|
|||
Logger logger = LoggerFactory.getLogger(ReimburseCustomerActivity.class);
|
||||
var order = ctx.getInput(Order.class);
|
||||
logger.info("{} : Request Approval for Order: {}", ctx.getName(), order.id());
|
||||
return true;
|
||||
return new ReimburseCustomerResult(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2023 The Dapr Authors
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.workflowapp.workflow.activities;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.domain.State;
|
||||
import io.dapr.springboot.workflowapp.model.ActivityResult;
|
||||
import io.dapr.springboot.workflowapp.model.OrderItem;
|
||||
import io.dapr.springboot.workflowapp.model.ProductInventory;
|
||||
import io.dapr.springboot.workflowapp.model.UpdateInventoryResult;
|
||||
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 UpdateInventoryActivity implements WorkflowActivity {
|
||||
|
||||
@Autowired
|
||||
private DaprClient daprClient;
|
||||
|
||||
@Override
|
||||
public Object run(WorkflowActivityContext ctx) {
|
||||
Logger logger = LoggerFactory.getLogger(UpdateInventoryActivity.class);
|
||||
var orderItem = ctx.getInput(OrderItem.class);
|
||||
logger.info("{} : Received input: {}", ctx.getName(), orderItem);
|
||||
|
||||
State<ProductInventory> inventory = daprClient.getState("inventory", orderItem.productId(), ProductInventory.class).block();
|
||||
assert inventory != null;
|
||||
ProductInventory productInventory = inventory.getValue();
|
||||
if(productInventory == null){
|
||||
return new UpdateInventoryResult(false, "Product not in inventory: " + orderItem.productName());
|
||||
}
|
||||
|
||||
if (productInventory.quantity() < orderItem.quantity()){
|
||||
return new UpdateInventoryResult(false, "Inventory not sufficient for: " + orderItem.productName());
|
||||
}
|
||||
|
||||
var updateProductInventory = new ProductInventory(productInventory.productId(), productInventory.quantity() - orderItem.quantity());
|
||||
daprClient.saveState("inventory", orderItem.productId(), updateProductInventory).block();
|
||||
return new UpdateInventoryResult(true, "Inventory updated for: " + orderItem.productName());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
spring.application.name=workflow-app
|
||||
dapr.client.state=
|
||||
|
|
@ -1,43 +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;
|
||||
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
|
||||
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class DaprTestContainersConfig {
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
public DaprContainer daprContainer() {
|
||||
return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
|
||||
.withAppName("external-system-interactions")
|
||||
.withComponent(new Component("kvstore", "state.in-memory", "v1", Collections.singletonMap("actorStateStore", String.valueOf(true))))
|
||||
.withAppPort(8080)
|
||||
.withAppHealthCheckPath("/actuator/health")
|
||||
.withAppChannelAddress("host.testcontainers.internal");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.workflowapp;
|
||||
|
||||
import com.redis.testcontainers.RedisContainer;
|
||||
import io.dapr.testcontainers.Component;
|
||||
import io.dapr.testcontainers.DaprContainer;
|
||||
import io.dapr.testcontainers.DaprLogLevel;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.testcontainers.DockerClientFactory;
|
||||
import org.testcontainers.containers.Network;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
|
||||
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class DaprTestContainersConfig {
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public RedisContainer redisContainer(Network daprNetwork){
|
||||
return new RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME)
|
||||
.withNetwork(daprNetwork)
|
||||
.withNetworkAliases("redis");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
public DaprContainer daprContainer(Network daprNetwork, RedisContainer redisContainer) {
|
||||
Map<String, String> redisProps = new HashMap<>();
|
||||
redisProps.put("actorStateStore", String.valueOf(true));
|
||||
redisProps.put("redisHost", "redis:6379");
|
||||
redisProps.put("redisPassword", "");
|
||||
|
||||
return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
|
||||
.withAppName("workflow-app")
|
||||
.withComponent(new Component("inventory", "state.redis", "v1", redisProps))
|
||||
.withComponent(new Component("pubsub", "pubsub.redis", "v1", redisProps))
|
||||
.withAppPort(8080)
|
||||
.withNetwork(daprNetwork)
|
||||
.withAppHealthCheckPath("/actuator/health")
|
||||
.withAppChannelAddress("host.testcontainers.internal")
|
||||
//.withDaprLogLevel(DaprLogLevel.DEBUG)
|
||||
//.withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))
|
||||
.dependsOn(redisContainer);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public Network getDaprNetwork(Environment env) {
|
||||
boolean reuse = env.getProperty("reuse", Boolean.class, false);
|
||||
if (reuse) {
|
||||
Network defaultDaprNetwork = new Network() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return "dapr-network";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
List<com.github.dockerjava.api.model.Network> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples;
|
||||
package io.dapr.springboot.workflowapp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
|
@ -11,12 +11,12 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.springboot.examples;
|
||||
package io.dapr.springboot.workflowapp;
|
||||
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.springboot.DaprAutoConfiguration;
|
||||
import io.dapr.springboot.examples.workflowapp.OrderStatus;
|
||||
import io.dapr.springboot.examples.workflowapp.Order;
|
||||
import io.dapr.springboot.workflowapp.model.OrderStatus;
|
||||
import io.dapr.springboot.workflowapp.model.Order;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
|
@ -30,7 +30,7 @@ import org.hamcrest.TypeSafeMatcher;
|
|||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.dapr.springboot.examples.StringMatchesUUIDPattern.matchesThePatternOfAUUID;
|
||||
import static io.dapr.springboot.workflowapp.StringMatchesUUIDPattern.matchesThePatternOfAUUID;
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
|
@ -52,48 +52,9 @@ class WorkflowAppTests {
|
|||
|
||||
|
||||
@Test
|
||||
void testExternalEventsWorkflow() throws InterruptedException {
|
||||
|
||||
var order = new Order("123", "Rubber ducks", 100, 500);
|
||||
given().contentType(ContentType.JSON)
|
||||
.body(order)
|
||||
.when()
|
||||
.post("/start")
|
||||
.then()
|
||||
.statusCode(200).body(matchesThePatternOfAUUID());
|
||||
void testWorkflow() throws InterruptedException {
|
||||
|
||||
|
||||
String status = given().contentType(ContentType.JSON)
|
||||
.when()
|
||||
.get("/status")
|
||||
.then()
|
||||
.statusCode(200).extract().asString();
|
||||
|
||||
assertTrue(status.contains("RUNNING"));
|
||||
|
||||
OrderStatus approvalStatus = new OrderStatus("123", true);
|
||||
given().contentType(ContentType.JSON)
|
||||
.body(approvalStatus)
|
||||
.when()
|
||||
.post("/event")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
|
||||
// Wait for the workflow instance to complete
|
||||
|
||||
await().atMost(Duration.ofSeconds(2))
|
||||
.pollDelay(500, TimeUnit.MILLISECONDS)
|
||||
.pollInterval(500, TimeUnit.MILLISECONDS)
|
||||
.until(() -> {
|
||||
var completedStatus = given().contentType(ContentType.JSON)
|
||||
.when()
|
||||
.get("/status")
|
||||
.then()
|
||||
.statusCode(200).extract().asString();
|
||||
assertTrue(completedStatus.contains("COMPLETED"));
|
||||
assertTrue(completedStatus.contains("has been approved"));
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue