diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md index c8b07593b..79276b193 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/README.md +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -4,12 +4,16 @@ In this example, we'll use Dapr to test workflow features. Visit [the Workflow documentation landing page](https://docs.dapr.io/developing-applications/building-blocks/workflow) for more information. -This example contains the follow classes: +This example contains the two parts : + +1. WorkflowConsoleApp + + It utilizes the workflow SDK as well as the workflow management API for starting workflows instances. The main WorkflowConsoleApp.java file contains the main setup of the app, including the registration of the workflow and workflow activities. The workflow definition is found in the workflows package and the workflow activity definitions are found in the activities package. All the models used by workflow activities are in models package. + +2. WorkflowClient + + It scheduales an instance of the OrderProcessingWorkflow (defined in the console package), starts it, and waits for the workflow result and output. -* DemoWorkflow: An example of a Dapr Workflow. -* DemoWorkflowClient: This application will start workflows using Dapr. -* DemoWorkflowWorker: An application that registers a workflow to the Dapr workflow runtime engine. It also executes the workflow instance. - ## Pre-requisites * [Dapr and Dapr Cli](https://docs.dapr.io/getting-started/install-dapr/). @@ -43,36 +47,44 @@ cd examples ### Running the demo Workflow worker -The first Java class to consider is `DemoWorkflowWorker`. Its job is to register an implementation of `DemoWorkflow` in the Dapr's workflow runtime engine. In `DemoWorkflowWorker.java` file, you will find the `DemoWorkflowWorker` class and the `main` method. See the code snippet below: +The first Java class to consider is `WorkflowConsoleApp`. Its job is to register an implementation of `OrderProcessingWorkflow` in the Dapr's workflow runtime engine. In `WorkflowConsoleApp.java` file, you will find the `WorkflowConsoleApp` class and the `main` method. See the code snippet below: ```java -public class DemoWorkflowWorker { - +public class WorkflowConsoleApp { public static void main(String[] args) throws Exception { - // Register the Workflow with the runtime. - WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); - System.out.println("Start workflow runtime"); - WorkflowRuntime.getInstance().startAndBlock(); + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(OrderProcessingWorkflow.class); + builder.registerActivity(NotifyActivity.class); + builder.registerActivity(ProcessPaymentActivity.class); + builder.registerActivity(RequestApprovalActivity.class); + builder.registerActivity(ReserveInventoryActivity.class); + builder.registerActivity(UpdateInventoryActivity.class); + + // Build and then start the workflow runtime pulling and executing tasks + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start(); + } + System.exit(0); } } ``` -This application uses `WorkflowRuntime.getInstance().registerWorkflow()` in order to register `DemoWorkflow` as a Workflow in the Dapr Workflow runtime. +This application uses WorkflowRuntimeBuilder class to build up Dapr workflow runtime. `registerWorkflow()` method is used to register `OrderProcessingWorkflow` as a Workflow in the Dapr Workflow runtime, and `registerActivity()` method is used to register all the activities in `OrderProcessingWorkflow`. -`WorkflowRuntime.getInstance().start()` method will build and start the engine within the Dapr workflow runtime. +`runtime.start()` method will start the engine within the Dapr workflow runtime. -Now, execute the following script in order to run DemoWorkflowWorker: +Now, execute the following script in order to run WorkflowConsoleApp: ```sh -dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowWorker +dapr run --app-id WorkflowConsoleApp --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.console.WorkflowConsoleApp ``` ### Running the Workflow client -The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr. +The `WorkflowClient` starts instances of OrderProcessingWorkflow that had been registered with Dapr. -With the DemoWorkflowWorker running, use the follow command to start the workflow with the DemoWorkflowClient: +With the WorkflowConsoleApp running, use the follow command to start the workflow with the WorkflowClient: ```sh -java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowClient +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.client.WorkflowClient ``` diff --git a/examples/src/main/java/io/dapr/examples/workflows/client/WorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/client/WorkflowClient.java new file mode 100644 index 000000000..df593b520 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/client/WorkflowClient.java @@ -0,0 +1,51 @@ +package io.dapr.examples.workflows.client; + +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import io.dapr.examples.workflows.console.models.OrderPayload; +import io.dapr.examples.workflows.console.workflows.OrderProcessingWorkflow; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; + +public class WorkflowClient { + + public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + try (client) { + testWorkflow(client); + } + } + + private static void testWorkflow(DaprWorkflowClient client) { + // schedule Workflow to order two intel i7 13900KS CPUs + OrderPayload order = new OrderPayload(); + order.setName("intel-i7-13900KS"); + order.setTotalCost(9000); + order.setQuantity(2); + String instanceId = client.scheduleNewWorkflow(OrderProcessingWorkflow.class, order); + System.out.printf("scheduled new workflow instance of OrderProcessingWorkflow with instance ID: %s%n", + instanceId); + + try { + client.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false); + System.out.printf("workflow instance %s started%n", instanceId); + } catch (TimeoutException e) { + System.out.printf("workflow instance %s did not start within 10 seconds%n", instanceId); + return; + } + + try { + WorkflowInstanceStatus workflowStatus = client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(30), + true); + if (workflowStatus != null) { + System.out.printf("workflow instance %s completed, out is: %s %n", instanceId, + workflowStatus.getSerializedOutput()); + } else { + System.out.printf("workflow instance %s not found%n", instanceId); + } + } catch (TimeoutException e) { + System.out.printf("workflow instance %s did not complete within 30 seconds%n", instanceId); + } + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java b/examples/src/main/java/io/dapr/examples/workflows/console/WorkflowConsoleApp.java similarity index 52% rename from examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java rename to examples/src/main/java/io/dapr/examples/workflows/console/WorkflowConsoleApp.java index 21bd01052..546980c9f 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/console/WorkflowConsoleApp.java @@ -11,26 +11,33 @@ limitations under the License. */ -package io.dapr.examples.workflows; +package io.dapr.examples.workflows.console; +import io.dapr.examples.workflows.console.activities.NotifyActivity; +import io.dapr.examples.workflows.console.activities.ProcessPaymentActivity; +import io.dapr.examples.workflows.console.activities.RequestApprovalActivity; +import io.dapr.examples.workflows.console.activities.ReserveInventoryActivity; +import io.dapr.examples.workflows.console.activities.UpdateInventoryActivity; +import io.dapr.examples.workflows.console.workflows.OrderProcessingWorkflow; import io.dapr.workflows.runtime.WorkflowRuntime; import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; -/** - * For setup instructions, see the README. - */ -public class DemoWorkflowWorker { +public class WorkflowConsoleApp { /** - * The main method of this app. + * The main method of this console app. * * @param args The port the app will listen on. * @throws Exception An Exception. */ public static void main(String[] args) throws Exception { - // Register the Workflow with the builder. - WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoWorkflow.class); - builder.registerActivity(DemoWorkflowActivity.class); + // Register the OrderProcessingWorkflow and its activities with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(OrderProcessingWorkflow.class); + builder.registerActivity(NotifyActivity.class); + builder.registerActivity(ProcessPaymentActivity.class); + builder.registerActivity(RequestApprovalActivity.class); + builder.registerActivity(ReserveInventoryActivity.class); + builder.registerActivity(UpdateInventoryActivity.class); // Build and then start the workflow runtime pulling and executing tasks try (WorkflowRuntime runtime = builder.build()) { diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/activities/NotifyActivity.java b/examples/src/main/java/io/dapr/examples/workflows/console/activities/NotifyActivity.java new file mode 100644 index 000000000..1832986a8 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/activities/NotifyActivity.java @@ -0,0 +1,21 @@ +package io.dapr.examples.workflows.console.activities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.dapr.examples.workflows.console.models.Notification; +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class NotifyActivity implements WorkflowActivity { + private static Logger logger = LoggerFactory.getLogger(NotifyActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + Notification notification = ctx.getInput(Notification.class); + logger.info(notification.getMessage()); + + return ""; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/activities/ProcessPaymentActivity.java b/examples/src/main/java/io/dapr/examples/workflows/console/activities/ProcessPaymentActivity.java new file mode 100644 index 000000000..ec86441c1 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/activities/ProcessPaymentActivity.java @@ -0,0 +1,29 @@ +package io.dapr.examples.workflows.console.activities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.dapr.examples.workflows.console.models.PaymentRequest; +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class ProcessPaymentActivity implements WorkflowActivity { + private static Logger logger = LoggerFactory.getLogger(ProcessPaymentActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + PaymentRequest req = ctx.getInput(PaymentRequest.class); + logger.info("Processing payment: {} for {} {} at ${}", + req.getRequestId(), req.getAmount(), req.getItemName(), req.getCurrency()); + + // Simulate slow processing + try { + Thread.sleep(7 * 1000); + } catch (InterruptedException e) { + } + logger.info("Payment for request ID '{}' processed successfully", req.getRequestId()); + + return true; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/activities/RequestApprovalActivity.java b/examples/src/main/java/io/dapr/examples/workflows/console/activities/RequestApprovalActivity.java new file mode 100644 index 000000000..51bd56ea8 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/activities/RequestApprovalActivity.java @@ -0,0 +1,24 @@ +package io.dapr.examples.workflows.console.activities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.dapr.examples.workflows.console.models.ApprovalResult; +import io.dapr.examples.workflows.console.models.OrderPayload; +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class RequestApprovalActivity implements WorkflowActivity { + private static Logger logger = LoggerFactory.getLogger(RequestApprovalActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + OrderPayload order = ctx.getInput(OrderPayload.class); + logger.info("Requesting approval for order: {}", order); + + // hard code to Approved in example + logger.info("Approved requesting approval for order: {}", order); + return ApprovalResult.Approved; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/activities/ReserveInventoryActivity.java b/examples/src/main/java/io/dapr/examples/workflows/console/activities/ReserveInventoryActivity.java new file mode 100644 index 000000000..c6096411c --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/activities/ReserveInventoryActivity.java @@ -0,0 +1,54 @@ +package io.dapr.examples.workflows.console.activities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.dapr.examples.workflows.console.models.InventoryItem; +import io.dapr.examples.workflows.console.models.InventoryRequest; +import io.dapr.examples.workflows.console.models.InventoryResult; +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class ReserveInventoryActivity implements WorkflowActivity { + private static Logger logger = LoggerFactory.getLogger(ReserveInventoryActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + InventoryRequest inventoryRequest = ctx.getInput(InventoryRequest.class); + logger.info("Reserving inventory for order '{}' of {} {}", + inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName()); + + // hard code that we have some inventory in this example + // TBD: use DaprClient to query state store for inventory + InventoryItem item = new InventoryItem(); + item.setName(inventoryRequest.getItemName()); + item.setQuantity(10); + item.setPerItemCost(10); + logger.info("There are {} {} available for purchase", + item.getQuantity(), item.getName()); + + // See if there're enough items to purchase + if (item.getQuantity() >= inventoryRequest.getQuantity()) { + // Simulate slow processing + try { + Thread.sleep(2 * 1000); + } catch (InterruptedException e) { + } + logger.info("Reserved inventory for order '{}' of {} {}", + inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName()); + InventoryResult inventoryResult = new InventoryResult(); + inventoryResult.setSuccess(true); + inventoryResult.setOrderPayload(item); + return inventoryResult; + } + + // Not enough items. + logger.info("Not enough items to reserve inventory for order '{}' of {} {}", + inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName()); + InventoryResult inventoryResult = new InventoryResult(); + inventoryResult.setSuccess(false); + inventoryResult.setOrderPayload(item); + return inventoryResult; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/activities/UpdateInventoryActivity.java b/examples/src/main/java/io/dapr/examples/workflows/console/activities/UpdateInventoryActivity.java new file mode 100644 index 000000000..2b4d39981 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/activities/UpdateInventoryActivity.java @@ -0,0 +1,34 @@ +package io.dapr.examples.workflows.console.activities; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.dapr.examples.workflows.console.models.InventoryRequest; +import io.dapr.examples.workflows.console.models.InventoryResult; +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; + +public class UpdateInventoryActivity implements WorkflowActivity { + private static Logger logger = LoggerFactory.getLogger(UpdateInventoryActivity.class); + + @Override + public Object run(WorkflowActivityContext ctx) { + InventoryRequest inventoryRequest = ctx.getInput(InventoryRequest.class); + logger.info("Updating inventory for order '{}' of {} {}", + inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName()); + + // hard code that we updated inventory in this example + // TBD: use DaprClient to update state store for inventory + // Simulate slow processing + try { + Thread.sleep(2 * 1000); + } catch (InterruptedException e) { + } + logger.info("Updated inventory for order '{}' of {} {}", + inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName()); + InventoryResult inventoryResult = new InventoryResult(); + inventoryResult.setSuccess(true); + return inventoryResult; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/ApprovalResult.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/ApprovalResult.java new file mode 100644 index 000000000..f2de84cc5 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/ApprovalResult.java @@ -0,0 +1,7 @@ +package io.dapr.examples.workflows.console.models; + +public enum ApprovalResult { + Unspecified, + Approved, + Rejected +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryItem.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryItem.java new file mode 100644 index 000000000..2bd66bce1 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryItem.java @@ -0,0 +1,36 @@ +package io.dapr.examples.workflows.console.models; + +public class InventoryItem { + private String name; + private double perItemCost; + private int quantity; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPerItemCost() { + return perItemCost; + } + + public void setPerItemCost(double perItemCost) { + this.perItemCost = perItemCost; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + @Override + public String toString() { + return "InventoryItem [name=" + name + ", perItemCost=" + perItemCost + ", quantity=" + quantity + "]"; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryRequest.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryRequest.java new file mode 100644 index 000000000..e31bac1d1 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryRequest.java @@ -0,0 +1,37 @@ +package io.dapr.examples.workflows.console.models; + +public class InventoryRequest { + + private String requestId; + private String itemName; + private int quantity; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getItemName() { + return itemName; + } + + public void setItemName(String itemName) { + this.itemName = itemName; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + @Override + public String toString() { + return "InventoryRequest [requestId=" + requestId + ", itemName=" + itemName + ", quantity=" + quantity + "]"; + } +} \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryResult.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryResult.java new file mode 100644 index 000000000..aff085581 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/InventoryResult.java @@ -0,0 +1,27 @@ +package io.dapr.examples.workflows.console.models; + +public class InventoryResult { + private boolean success; + private InventoryItem orderPayload; + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public InventoryItem getOrderPayload() { + return orderPayload; + } + + public void setOrderPayload(InventoryItem orderPayload) { + this.orderPayload = orderPayload; + } + + @Override + public String toString() { + return "InventoryResult [success=" + success + ", orderPayload=" + orderPayload + "]"; + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/Notification.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/Notification.java new file mode 100644 index 000000000..c0c9444c0 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/Notification.java @@ -0,0 +1,14 @@ +package io.dapr.examples.workflows.console.models; + +public class Notification { + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/OrderPayload.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/OrderPayload.java new file mode 100644 index 000000000..d04fd4855 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/OrderPayload.java @@ -0,0 +1,38 @@ +package io.dapr.examples.workflows.console.models; + +public class OrderPayload { + + private String name; + private double totalCost; + private int quantity; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + @Override + public String toString() { + return "OrderPayload [name=" + name + ", totalCost=" + totalCost + ", quantity=" + quantity + "]"; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/OrderResult.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/OrderResult.java new file mode 100644 index 000000000..726adb03a --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/OrderResult.java @@ -0,0 +1,19 @@ +package io.dapr.examples.workflows.console.models; + +public class OrderResult { + private boolean processed; + + public boolean isProcessed() { + return processed; + } + + public void setProcessed(boolean processed) { + this.processed = processed; + } + + @Override + public String toString() { + return "OrderResult [processed=" + processed + "]"; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/models/PaymentRequest.java b/examples/src/main/java/io/dapr/examples/workflows/console/models/PaymentRequest.java new file mode 100644 index 000000000..8db4963eb --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/models/PaymentRequest.java @@ -0,0 +1,47 @@ +package io.dapr.examples.workflows.console.models; + +public class PaymentRequest { + private String requestId; + private String itemName; + private int amount; + private double currency; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getItemName() { + return itemName; + } + + public void setItemName(String itemName) { + this.itemName = itemName; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + public double getCurrency() { + return currency; + } + + public void setCurrency(double currency) { + this.currency = currency; + } + + @Override + public String toString() { + return "PaymentRequest [requestId=" + requestId + ", itemName=" + itemName + ", amount=" + amount + + ", currency=" + currency + "]"; + } + +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/console/workflows/OrderProcessingWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/console/workflows/OrderProcessingWorkflow.java new file mode 100644 index 000000000..c4571f814 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/console/workflows/OrderProcessingWorkflow.java @@ -0,0 +1,111 @@ +package io.dapr.examples.workflows.console.workflows; + +import org.slf4j.Logger; + +import io.dapr.examples.workflows.console.activities.NotifyActivity; +import io.dapr.examples.workflows.console.activities.ProcessPaymentActivity; +import io.dapr.examples.workflows.console.activities.RequestApprovalActivity; +import io.dapr.examples.workflows.console.activities.ReserveInventoryActivity; +import io.dapr.examples.workflows.console.activities.UpdateInventoryActivity; +import io.dapr.examples.workflows.console.models.ApprovalResult; +import io.dapr.examples.workflows.console.models.InventoryRequest; +import io.dapr.examples.workflows.console.models.InventoryResult; +import io.dapr.examples.workflows.console.models.Notification; +import io.dapr.examples.workflows.console.models.OrderPayload; +import io.dapr.examples.workflows.console.models.OrderResult; +import io.dapr.examples.workflows.console.models.PaymentRequest; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; + +public class OrderProcessingWorkflow extends Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + Logger logger = ctx.getLogger(); + String orderId = ctx.getInstanceId(); + logger.info("Starting Workflow: " + ctx.getName()); + logger.info("Instance ID(order ID): " + orderId); + logger.info("Current Orchestration Time: " + ctx.getCurrentInstant()); + + OrderPayload order = ctx.getInput(OrderPayload.class); + logger.info("Received Order: " + order.toString()); + OrderResult orderResult = new OrderResult(); + orderResult.setProcessed(false); + + // Notify the user that an order has come through + Notification notification = new Notification(); + notification.setMessage("Received Order: " + order.toString()); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + + // Determine if there is enough of the item available for purchase by checking + // the inventory + InventoryRequest inventoryRequest = new InventoryRequest(); + inventoryRequest.setRequestId(orderId); + inventoryRequest.setItemName(order.getName()); + inventoryRequest.setQuantity(order.getQuantity()); + InventoryResult inventoryResult = ctx.callActivity(ReserveInventoryActivity.class.getName(), + inventoryRequest, InventoryResult.class).await(); + + // If there is insufficient inventory, fail and let the user know + if (!inventoryResult.isSuccess()) { + notification.setMessage("Insufficient inventory for order : " + order.getName()); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + // Require orders over a certain threshold to be approved + if (order.getTotalCost() > 5000) { + ApprovalResult approvalResult = ctx.callActivity(RequestApprovalActivity.class.getName(), + order, ApprovalResult.class).await(); + if (approvalResult != ApprovalResult.Approved) { + notification.setMessage("Order " + order.getName() + " was not approved."); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + } + + // There is enough inventory available so the user can purchase the item(s). + // Process their payment + PaymentRequest paymentRequest = new PaymentRequest(); + paymentRequest.setRequestId(orderId); + paymentRequest.setItemName(order.getName()); + paymentRequest.setAmount(order.getQuantity()); + paymentRequest.setCurrency(order.getTotalCost()); + boolean isOK = ctx.callActivity(ProcessPaymentActivity.class.getName(), + paymentRequest, boolean.class).await(); + if (!isOK) { + notification.setMessage("Payment failed for order : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + inventoryResult = ctx.callActivity(UpdateInventoryActivity.class.getName(), + inventoryRequest, InventoryResult.class).await(); + if (!inventoryResult.isSuccess()) { + // If there is an error updating the inventory, refund the user + // paymentRequest.setAmount(-1 * paymentRequest.getAmount()); + // ctx.callActivity(ProcessPaymentActivity.class.getName(), + // paymentRequest).await(); + + // Let users know their payment processing failed + notification.setMessage("Order failed to update inventory! : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + // Let user know their order was processed + notification.setMessage("Order completed! : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + + // Complete the workflow with order result is processed + orderResult.setProcessed(true); + ctx.complete(orderResult); + }; + } + +}