Merge pull request #1163 from marcduiker/release-1.15

Update workflow quickstarts
This commit is contained in:
Alice Gibbons 2025-02-14 11:44:10 +00:00 committed by GitHub
commit 38e9a81f10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 578 additions and 400 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

View File

@ -28,13 +28,15 @@ pip3 install -r requirements.txt
cd ..
```
<!-- END_STEP -->
2. Open a new terminal window and run the multi app run template:
<!-- STEP
name: Run multi app run template
expected_stdout_lines:
- '== APP == INFO:root:Input sent: What is dapr?'
- '== APP == INFO:root:Output response: What is dapr?'
- '== APP - conversation == INFO:root:Input sent: What is dapr?'
- '== APP - conversation == INFO:root:Output response: What is dapr?'
expected_stderr_lines:
output_match_mode: substring
match_order: none

View File

@ -1,16 +1,17 @@
# Dapr workflows
In this quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
In this quickstart, you'll run a console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
This quickstart includes one project:
- .NET console app `order-processor`
The quickstart contains 1 workflow to simulate purchasing items from a store, and 4 unique activities within the workflow. These 4 activities are as follows:
The quickstart contains 1 workflow to simulate purchasing items from a store, and 5 unique activities within the workflow. These 5 activities are as follows:
- NotifyActivity: This activity utilizes a logger to print out messages throughout the workflow. These messages notify the user when there is insufficient inventory, their payment couldn't be processed, and more.
- VerifyInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- RequestApprovalActivity: This activity is responsible requesting approval to purchase the order items in case the total cost is above a certain threshold.
- ProcessPaymentActivity: This activity is responsible for processing and authorizing the payment.
- ReserveInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- UpdateInventoryActivity: This activity removes the requested items from the state store and updates the store with the new remaining inventory value.
### Run the order processor workflow
@ -52,43 +53,164 @@ dapr run -f .
3. Expected output
```
== APP - order-processor == Starting workflow 898fd553 purchasing 10 Cars
== APP - order-processor == Starting workflow 571a6e25 purchasing 1 Cars
== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[40]
== APP - order-processor == Scheduling new OrderProcessingWorkflow orchestration with instance ID '571a6e25' and 45 bytes of input data.
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/StartInstance
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/StartInstance
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 3045.9209ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 3046.0945ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 3016.1346ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 3016.3572ms - 200
== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[42]
== APP - order-processor == Waiting for instance '571a6e25' to start.
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceStart
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceStart
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 2.9095ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 3.0445ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 99.446ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 99.5407ms - 200
== APP - order-processor == Your workflow has started. Here is the status of the workflow: Running
== APP - order-processor == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == Presenting notification Notification { Message = Received order 898fd553 for 10 Cars at $15000 }
== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[43]
== APP - order-processor == Waiting for instance '898fd553' to complete, fail, or terminate.
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[2013970020]
== APP - order-processor == Received request ID '898fd553' for 10 Cars at $15000
== APP - order-processor == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[1988035937]
== APP - order-processor == Reserving inventory for order request ID '898fd553' of 10 Cars
== APP - order-processor == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[1130866279]
== APP - order-processor == There are: 10 Cars available for purchase
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[1162731597]
== APP - order-processor == Checked inventory for request ID 'InventoryRequest { RequestId = 898fd553, ItemName = Cars, Quantity = 10 }'
== APP - order-processor == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[340284070]
== APP - order-processor == Processing payment: request ID '898fd553' for 10 Cars at $15000
== APP - order-processor == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[1851315765]
== APP - order-processor == Payment for request ID '898fd553' processed successfully
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[340284070]
== APP - order-processor == Processed payment request as there's sufficient inventory to proceed: PaymentRequest { RequestId = 898fd553, ItemBeingPurchased = Cars, Amount = 10, Currency = 15000 }
== APP - order-processor == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[2144991393]
== APP - order-processor == Checking inventory for request ID '898fd553' for 10 Cars
== APP - order-processor == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[1901852920]
== APP - order-processor == There are now 90 Cars left in stock
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[96138418]
== APP - order-processor == Updating available inventory for PaymentRequest { RequestId = 898fd553, ItemBeingPurchased = Cars, Amount = 10, Currency = 15000 }
== APP - order-processor == Waiting for instance '571a6e25' to complete, fail, or terminate.
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceCompletion
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceCompletion
== APP - order-processor == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == Presenting notification Notification { Message = Order 898fd553 has completed! }
== APP - order-processor == Presenting notification Notification { Message = Received order 571a6e25 for 1 Cars at $5000 }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.6785ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.7869ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[2013970020]
== APP - order-processor == Received request ID '571a6e25' for 1 Cars at $5000
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.1947ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.3293ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.VerifyInventoryActivity[1478802116]
== APP - order-processor == Reserving inventory for order request ID '571a6e25' of 1 Cars
== APP - order-processor == info: WorkflowConsoleApp.Activities.VerifyInventoryActivity[1130866279]
== APP - order-processor == There are: 10 Cars available for purchase
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.8534ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 2.0077ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[1162731597]
== APP - order-processor == Checked inventory for request ID 'InventoryRequest { RequestId = 571a6e25, ItemName = Cars, Quantity = 1 }'
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.1851ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.3742ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[340284070]
== APP - order-processor == Processing payment: request ID '571a6e25' for 1 Cars at $5000
== APP - order-processor == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[1851315765]
== APP - order-processor == Payment for request ID '571a6e25' processed successfully
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.8249ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.9595ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[340284070]
== APP - order-processor == Processed payment request as there's sufficient inventory to proceed: PaymentRequest { RequestId = 571a6e25, ItemBeingPurchased = Cars, Amount = 1, Currency = 5000 }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.4457ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.5267ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[2144991393]
== APP - order-processor == Checking inventory for request ID '571a6e25' for 1 Cars
== APP - order-processor == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[1901852920]
== APP - order-processor == There are now 9 Cars left in stock
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.6012ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.7097ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[96138418]
== APP - order-processor == Updating available inventory for PaymentRequest { RequestId = 571a6e25, ItemBeingPurchased = Cars, Amount = 1, Currency = 5000 }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.469ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.5431ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == Presenting notification Notification { Message = Order 571a6e25 has completed! }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.494ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.5685ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[510392223]
== APP - order-processor == Order 898fd553 has completed
== APP - order-processor == Order 571a6e25 has completed
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.6353ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.7546ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 15807.213ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 15807.3675ms - 200
== APP - order-processor == Workflow Status: Completed
```
4. Stop Dapr workflow with CTRL-C or:
```sh
dapr stop -f .
```
### View workflow output with Zipkin
@ -106,14 +228,16 @@ docker run -d -p 9411:9411 openzipkin/zipkin
### What happened?
When you ran `dapr run --app-id order-processor dotnet run`
When you ran `dapr run -f .`:
1. A unique order ID for the workflow is generated (in the above example, `898fd553`) and the workflow is scheduled.
2. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received.
3. The `ReserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
4. Your workflow starts and notifies you of its status.
5. The `ProcessPaymentActivity` workflow activity begins processing payment for order `898fd553` and confirms if successful.
6. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
7. The `NotifyActivity` workflow activity sends a notification saying that order `898fd553` has completed.
8. The workflow terminates as completed.
1. An OrderPayload is made containing one car.
2. A unique order ID for the workflow is generated (in the above example, `571a6e25`) and the workflow is scheduled.
3. The `NotifyActivity` workflow activity sends a notification saying an order for one car has been received.
4. The `VerifyInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. The inventory is sufficient so the workflow continues.
5. The total cost of the order is 5000, so the workflow will not call the `RequestApprovalActivity` activity.
6. The `ProcessPaymentActivity` workflow activity begins processing payment for order `571a6e25` and confirms if successful.
7. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
8. The `NotifyActivity` workflow activity sends a notification saying that order `571a6e25` has completed.
9. The workflow terminates as completed and the OrderResult is set to processed.
> **Note:** This quickstart uses an OrderPayload of one car with a total cost of $5000. Since the total order cost is not over 5000, the workflow will not call the `RequestApprovalActivity` activity nor wait for an approval event. Since the quickstart is a console application, it can't accept incoming events easily. If you want to test this scenario, convert the console app to a service and use the [raise event API](https://v1-15.docs.dapr.io/reference/api/workflow_api/#raise-event-request) via HTTP/gRPC or via the Dapr Workflow client to send an event to the workflow instance.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 KiB

After

Width:  |  Height:  |  Size: 732 KiB

View File

@ -0,0 +1,22 @@
namespace WorkflowConsoleApp.Activities;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Dapr.Workflow;
using WorkflowConsoleApp.Models;
internal sealed partial class RequestApprovalActivity(ILogger<RequestApprovalActivity> logger) : WorkflowActivity<ApprovalRequest, object?>
{
public override async Task<object?> RunAsync(WorkflowActivityContext context, ApprovalRequest approvalRequest)
{
LogRequestApproval(logger, approvalRequest);
// Simulate slow processing & sending the approval to the recipient
await Task.Delay(TimeSpan.FromSeconds(2));
return Task.FromResult<object?>(null);
}
[LoggerMessage(LogLevel.Information, "Approval Request {approvalRequest}")]
static partial void LogRequestApproval(ILogger logger, ApprovalRequest approvalRequest);
}

View File

@ -7,13 +7,13 @@ using Microsoft.Extensions.Logging;
using Models;
using System;
internal sealed partial class ReserveInventoryActivity(ILogger<ReserveInventoryActivity> logger, DaprClient daprClient) : WorkflowActivity<InventoryRequest, InventoryResult>
internal sealed partial class VerifyInventoryActivity(ILogger<VerifyInventoryActivity> logger, DaprClient daprClient) : WorkflowActivity<InventoryRequest, InventoryResult>
{
private const string StoreName = "statestore";
public override async Task<InventoryResult> RunAsync(WorkflowActivityContext context, InventoryRequest req)
{
LogReserveInventory(logger, req.RequestId, req.Quantity, req.ItemName);
LogVerifyInventory(logger, req.RequestId, req.Quantity, req.ItemName);
// Ensure that the store has items
var (orderResult, _) = await daprClient.GetStateAndETagAsync<OrderPayload>(StoreName, req.ItemName);
@ -32,7 +32,7 @@ internal sealed partial class ReserveInventoryActivity(ILogger<ReserveInventoryA
// Simulate slow processing
await Task.Delay(TimeSpan.FromSeconds(2));
LogSufficientInventory(logger, req.Quantity, req.ItemName);
LogSufficientInventory(logger, orderResult.Quantity, req.ItemName);
return new InventoryResult(true, orderResult);
}
@ -42,7 +42,7 @@ internal sealed partial class ReserveInventoryActivity(ILogger<ReserveInventoryA
}
[LoggerMessage(LogLevel.Information, "Reserving inventory for order request ID '{requestId}' of {quantity} {name}")]
static partial void LogReserveInventory(ILogger logger, string requestId, int quantity, string name);
static partial void LogVerifyInventory(ILogger logger, string requestId, int quantity, string name);
[LoggerMessage(LogLevel.Warning, "Unable to locate an order result for request ID '{requestId}' for the indicated item {itemName} in the state store")]
static partial void LogStateNotFound(ILogger logger, string requestId, string itemName);

View File

@ -3,5 +3,7 @@ namespace WorkflowConsoleApp.Models;
internal sealed record OrderPayload(string Name, double TotalCost, int Quantity = 1);
internal sealed record InventoryRequest(string RequestId, string ItemName, int Quantity);
internal sealed record InventoryResult(bool Success, OrderPayload? OrderPayload);
internal sealed record ApprovalRequest(string RequestId, string ItemBeingPurchased, int Quantity, double Amount);
internal sealed record ApprovalResponse(string RequestId, bool IsApproved);
internal sealed record PaymentRequest(string RequestId, string ItemBeingPurchased, int Amount, double Currency);
internal sealed record OrderResult(bool Processed);

View File

@ -20,7 +20,8 @@ var builder = Host.CreateDefaultBuilder(args).ConfigureServices(services =>
// These are the activities that get invoked by the workflow(s).
options.RegisterActivity<NotifyActivity>();
options.RegisterActivity<ReserveInventoryActivity>();
options.RegisterActivity<VerifyInventoryActivity>();
options.RegisterActivity<RequestApprovalActivity>();
options.RegisterActivity<ProcessPaymentActivity>();
options.RegisterActivity<UpdateInventoryActivity>();
});
@ -36,13 +37,13 @@ var workflowClient = host.Services.GetRequiredService<DaprWorkflowClient>();
// Generate a unique ID for the workflow
var orderId = Guid.NewGuid().ToString()[..8];
const string itemToPurchase = "Cars";
const int amountToPurchase = 10;
const int amountToPurchase = 1;
// Populate the store with items
RestockInventory(itemToPurchase);
// Construct the order
var orderInfo = new OrderPayload(itemToPurchase, 15000, amountToPurchase);
var orderInfo = new OrderPayload(itemToPurchase, 5000, amountToPurchase);
// Start the workflow
Console.WriteLine($"Starting workflow {orderId} purchasing {amountToPurchase} {itemToPurchase}");
@ -67,5 +68,5 @@ return;
void RestockInventory(string itemToPurchase)
{
daprClient.SaveStateAsync(storeName, itemToPurchase, new OrderPayload(Name: itemToPurchase, TotalCost: 15000, Quantity: 100));
daprClient.SaveStateAsync(storeName, itemToPurchase, new OrderPayload(Name: itemToPurchase, TotalCost: 50000, Quantity: 10));
}

View File

@ -22,7 +22,7 @@ internal sealed partial class OrderProcessingWorkflow : Workflow<OrderPayload, O
// Determine if there is enough of the item available for purchase by checking the inventory
var inventoryRequest = new InventoryRequest(RequestId: orderId, order.Name, order.Quantity);
var result = await context.CallActivityAsync<InventoryResult>(
nameof(ReserveInventoryActivity), inventoryRequest);
nameof(VerifyInventoryActivity), inventoryRequest);
LogCheckInventory(logger, inventoryRequest);
// If there is insufficient inventory, fail and let the user know
@ -35,6 +35,23 @@ internal sealed partial class OrderProcessingWorkflow : Workflow<OrderPayload, O
return new OrderResult(Processed: false);
}
if (order.TotalCost > 5000)
{
await context.CallActivityAsync(nameof(RequestApprovalActivity),
new ApprovalRequest(orderId, order.Name, order.Quantity, order.TotalCost));
var approvalResponse = await context.WaitForExternalEventAsync<ApprovalResponse>(
eventName: "ApprovalEvent",
timeout: TimeSpan.FromSeconds(30));
if (!approvalResponse.IsApproved)
{
await context.CallActivityAsync(nameof(NotifyActivity),
new Notification($"Order {orderId} was not approved"));
LogOrderNotApproved(logger, orderId);
return new OrderResult(Processed: false);
}
}
// There is enough inventory available so the user can purchase the item(s). Process their payment
var processPaymentRequest = new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost);
await context.CallActivityAsync(nameof(ProcessPaymentActivity),processPaymentRequest);
@ -73,6 +90,9 @@ internal sealed partial class OrderProcessingWorkflow : Workflow<OrderPayload, O
[LoggerMessage(LogLevel.Information, "Insufficient inventory for order {orderName}")]
static partial void LogInsufficientInventory(ILogger logger, string orderName);
[LoggerMessage(LogLevel.Information, "Order {orderName} was not approved")]
static partial void LogOrderNotApproved(ILogger logger, string orderName);
[LoggerMessage(LogLevel.Information, "Processed payment request as there's sufficient inventory to proceed: {request}")]
static partial void LogPaymentProcessing(ILogger logger, PaymentRequest request);

View File

@ -13,10 +13,10 @@ activities are as follows:
- NotifyActivity: This activity utilizes a logger to print out messages throughout the workflow. These messages notify the user when there is insufficient
§inventory, their payment couldn't be processed, and more.
- ProcessPaymentActivity: This activity is responsible for processing and authorizing the payment.
- VerifyInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- RequestApprovalActivity: This activity seeks approval from a manager, if payment is greater than 5000 USD.
- ProcessPaymentActivity: This activity is responsible for processing and authorizing the payment.
- UpdateInventoryActivity: This activity removes the requested items from the state store and updates the store with the new remaining inventory value.
- RequestApprovalActivity: This activity seeks approval from Manager, if payment is greater than 50000 USD.
### Run the order processor workflow
@ -25,12 +25,10 @@ activities are as follows:
<!-- STEP
name: Running this example
expected_stdout_lines:
- "for 10 cars - $150000"
- "There are 100 cars available for purchase"
- "Requesting approval for payment of 150000USD for 10 cars"
- "has been approved!"
- "There are now 90 cars left in stock"
- "Workflow completed - result: COMPLETED"
- "for 1 cars - $5000"
- "There are 10 cars available for purchase"
- "There are now 9 cars left in stock"
- "workflow status: COMPLETED"
output_match_mode: substring
background: false
timeout_seconds: 120
@ -38,51 +36,41 @@ sleep: 30
-->
```sh
dapr run -f .
```
<!-- END_STEP -->
3. Expected output
```
== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == dapr client initializing for: 127.0.0.1:50056
== APP - order-processor == dapr client initializing for: 127.0.0.1:46533
== APP - order-processor == INFO: 2025/02/13 13:18:33 connecting work item listener stream
== APP - order-processor == 2025/02/13 13:18:33 work item listener started
== APP - order-processor == INFO: 2025/02/13 13:18:33 starting background processor
== APP - order-processor == adding base stock item: paperclip
== APP - order-processor == 2024/02/01 12:59:52 work item listener started
== APP - order-processor == INFO: 2024/02/01 12:59:52 starting background processor
== APP - order-processor == adding base stock item: cars
== APP - order-processor == adding base stock item: computers
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == NotifyActivity: Received order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 for 10 cars - $150000
== APP - order-processor == VerifyInventoryActivity: Verifying inventory for order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 of 10 cars
== APP - order-processor == VerifyInventoryActivity: There are 100 cars available for purchase
== APP - order-processor == RequestApprovalActivity: Requesting approval for payment of 150000USD for 10 cars
== APP - order-processor == NotifyActivity: Payment for order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 has been approved!
== APP - order-processor == ProcessPaymentActivity: 48ee83b7-5d80-48d5-97f9-6b372f5480a5 for 10 - cars (150000USD)
== APP - order-processor == UpdateInventoryActivity: Checking Inventory for order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 for 10 * cars
== APP - order-processor == UpdateInventoryActivity: There are now 90 cars left in stock
== APP - order-processor == NotifyActivity: Order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 has completed!
== APP - order-processor == Workflow completed - result: COMPLETED
== APP - order-processor == NotifyActivity: Received order b4cb2687-1af0-4f8d-9659-eb6389c07ade for 1 cars - $5000
== APP - order-processor == VerifyInventoryActivity: Verifying inventory for order b4cb2687-1af0-4f8d-9659-eb6389c07ade of 1 cars
== APP - order-processor == VerifyInventoryActivity: There are 10 cars available for purchase
== APP - order-processor == ProcessPaymentActivity: b4cb2687-1af0-4f8d-9659-eb6389c07ade for 1 - cars (5000USD)
== APP - order-processor == UpdateInventoryActivity: Checking Inventory for order b4cb2687-1af0-4f8d-9659-eb6389c07ade for 1 * cars
== APP - order-processor == UpdateInventoryActivity: There are now 9 cars left in stock
== APP - order-processor == NotifyActivity: Order b4cb2687-1af0-4f8d-9659-eb6389c07ade has completed!
== APP - order-processor == workflow status: COMPLETED
== APP - order-processor == Purchase of item is complete
```
4. Stop Dapr workflow with CTRL-C or:
<!-- END_STEP -->
<!-- STEP
name: Stop multi-app run
sleep: 5
-->
```sh
dapr stop -f .
```
<!-- END_STEP -->
### View workflow output with Zipkin
For a more detailed view of the workflow activities (duration, progress etc.), try using Zipkin.
@ -97,21 +85,14 @@ launched on running `dapr init`.
When you ran the above comands:
1. First the "user" inputs an order for 10 cars into the concole app.
2. A unique order ID for the workflow is generated (in the above example, `b903d749cd814e099f06ebf4a56a2f90`) and the workflow is scheduled.
1. An OrderPayload is made containing one car.
2. A unique order ID for the workflow is generated (in the above example, `b4cb2687-1af0-4f8d-9659-eb6389c07ade`) and the workflow is scheduled.
3. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received.
4. The `VerifyInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars
in stock.
5. The `RequestApprovalActivity` workflow activity is triggered due to buisness logic for orders exceeding $50k and user is prompted to manually approve the
purchase before continuing the order.
6. The workflow starts and notifies you of its status.
7. The `ProcessPaymentActivity` workflow activity begins processing payment for order `b903d749cd814e099f06ebf4a56a2f90` and confirms if successful.
8. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
9. The `NotifyActivity` workflow activity sends a notification saying that order `b903d749cd814e099f06ebf4a56a2f90` has completed.
10. The workflow terminates as completed.
4. The `VerifyInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
5. The total cost of the order is 5000, so the workflow will not call the `RequestApprovalActivity` activity.
6. The `ProcessPaymentActivity` workflow activity begins processing payment for order `b4cb2687-1af0-4f8d-9659-eb6389c07ade` and confirms if successful.
7. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
8. The `NotifyActivity` workflow activity sends a notification saying that order `b4cb2687-1af0-4f8d-9659-eb6389c07ade` has completed.
9. The workflow terminates as completed and the OrderResult is set to processed.
> **Note:** This quickstart uses an OrderPayload of one car with a total cost of $5000. Since the total order cost is not over 5000, the workflow will not call the `RequestApprovalActivity` activity nor wait for an approval event. Since the quickstart is a console application, it can't accept incoming events easily. If you want to test this scenario, convert the console app to a service and use the [raise event API](https://v1-15.docs.dapr.io/reference/api/workflow_api/#raise-event-request) via HTTP/gRPC or via the Dapr Workflow client to send an event to the workflow instance.

View File

@ -61,7 +61,7 @@ func main() {
inventory := []InventoryItem{
{ItemName: "paperclip", PerItemCost: 5, Quantity: 100},
{ItemName: "cars", PerItemCost: 15000, Quantity: 100},
{ItemName: "cars", PerItemCost: 5000, Quantity: 10},
{ItemName: "computers", PerItemCost: 500, Quantity: 100},
}
if err := restockInventory(daprClient, inventory); err != nil {
@ -71,7 +71,7 @@ func main() {
fmt.Println("==========Begin the purchase of item:==========")
itemName := defaultItemName
orderQuantity := 10
orderQuantity := 1
totalCost := inventory[1].PerItemCost * orderQuantity
@ -86,49 +86,23 @@ func main() {
log.Fatalf("failed to start workflow: %v", err)
}
approvalSought := false
startTime := time.Now()
for {
timeDelta := time.Since(startTime)
metadata, err := wfClient.FetchWorkflowMetadata(context.Background(), id)
if err != nil {
log.Fatalf("failed to fetch workflow: %v", err)
}
if (metadata.RuntimeStatus == workflow.StatusCompleted) || (metadata.RuntimeStatus == workflow.StatusFailed) || (metadata.RuntimeStatus == workflow.StatusTerminated) {
fmt.Printf("Workflow completed - result: %v\n", metadata.RuntimeStatus.String())
break
}
if timeDelta.Seconds() >= 10 {
metadata, err := wfClient.FetchWorkflowMetadata(context.Background(), id)
if err != nil {
log.Fatalf("failed to fetch workflow: %v", err)
}
if totalCost > 50000 && !approvalSought && ((metadata.RuntimeStatus != workflow.StatusCompleted) || (metadata.RuntimeStatus != workflow.StatusFailed) || (metadata.RuntimeStatus != workflow.StatusTerminated)) {
approvalSought = true
promptForApproval(id)
}
}
// Sleep before the next iteration
time.Sleep(time.Second)
waitCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
_, err = wfClient.WaitForWorkflowCompletion(waitCtx, id)
cancel()
if err != nil {
log.Fatalf("failed to wait for workflow: %v", err)
}
respFetch, err := wfClient.FetchWorkflowMetadata(context.Background(), id, workflow.WithFetchPayloads(true))
if err != nil {
log.Fatalf("failed to get workflow: %v", err)
}
fmt.Printf("workflow status: %v\n", respFetch.RuntimeStatus)
fmt.Println("Purchase of item is complete")
}
// promptForApproval is an example case. There is no user input required here due to this being for testing purposes only.
// It would be perfectly valid to add a wait here or display a prompt to continue the process.
func promptForApproval(id string) {
wfClient, err := workflow.NewClient()
if err != nil {
log.Fatalf("failed to initialise wfClient: %v", err)
}
if err := wfClient.RaiseEvent(context.Background(), id, "manager_approval"); err != nil {
log.Fatal(err)
}
}
func restockInventory(daprClient client.Client, inventory []InventoryItem) error {
for _, item := range inventory {
itemSerialized, err := json.Marshal(item)

View File

@ -40,7 +40,7 @@ func OrderProcessingWorkflow(ctx *workflow.WorkflowContext) (any, error) {
return OrderResult{Processed: false}, err
}
if orderPayload.TotalCost > 50000 {
if orderPayload.TotalCost > 5000 {
var approvalRequired ApprovalRequired
if err := ctx.CallActivity(RequestApprovalActivity, workflow.ActivityInput(orderPayload)).Await(&approvalRequired); err != nil {
return OrderResult{Processed: false}, err

View File

@ -1,6 +1,6 @@
# Dapr workflows
In this quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
In this quickstart, you'll run console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
This quickstart includes one project:
@ -9,8 +9,8 @@ This quickstart includes one project:
The quickstart contains 1 workflow to simulate purchasing items from a store, and 5 unique activities within the workflow. These 5 activities are as follows:
- NotifyActivity: This activity utilizes a logger to print out messages throughout the workflow. These messages notify the user when there is insufficient inventory, their payment couldn't be processed, and more.
- ReserveInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- RequestApprovalActivity: This activity requests approval for orders over a certain threshold
- VerifyInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- RequestApprovalActivity: This activity requests approval for orders over a certain threshold.
- ProcessPaymentActivity: This activity is responsible for processing and authorizing the payment.
- UpdateInventoryActivity: This activity updates the state store with the new remaining inventory value.
@ -34,7 +34,7 @@ cd ..
<!-- STEP
name: Run order-processor service
expected_stdout_lines:
- '== APP - order-processor == there are now 90 cars left in stock'
- '== APP - order-processor == there are now 9 cars left in stock'
- '== APP - order-processor == workflow instance completed, out is: {"processed":true}'
expected_stderr_lines:
output_match_mode: substring
@ -55,32 +55,45 @@ dapr run -f .
```
== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Workflow: OrderProcessingWorkflow
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: NotifyActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: ProcessPaymentActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: RequestApprovalActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: VerifyInventoryActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: UpdateInventoryActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - List of registered workflows: [io.dapr.quickstarts.workflows.OrderProcessingWorkflow]
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - List of registered activites: [io.dapr.quickstarts.workflows.activities.NotifyActivity, io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity, io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity, io.dapr.quickstarts.workflows.activities.RequestApprovalActivity, io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity]
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Successfully built dapr workflow runtime
== APP - order-processor == Start workflow runtime
== APP - order-processor == Oct 06, 2023 3:10:01 PM com.microsoft.durabletask.DurableTaskGrpcWorker startAndBlock
== APP - order-processor == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001.
== APP - order-processor == Feb 12, 2025 2:44:13 PM com.microsoft.durabletask.DurableTaskGrpcWorker startAndBlock
== APP - order-processor == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:39261.
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == Starting order workflow, purchasing 10 of cars
== APP - order-processor == scheduled new workflow instance of OrderProcessingWorkflow with instance ID: 397faa44-1374-4f9d-a7fe-c74160604064
== APP - order-processor == Starting order workflow, purchasing 1 of cars
== APP - order-processor == scheduled new workflow instance of OrderProcessingWorkflow with instance ID: d1bf548b-c854-44af-978e-90c61ed88e3c
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Starting Workflow: io.dapr.quickstarts.workflows.OrderProcessingWorkflow
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Instance ID(order ID): 397faa44-1374-4f9d-a7fe-c74160604064
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Current Orchestration Time: 2023-10-06T22:10:04.769Z
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP - order-processor == workflow instance 397faa44-1374-4f9d-a7fe-c74160604064 started
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserving inventory for order '397faa44-1374-4f9d-a7fe-c74160604064' of 10 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - There are 100 cars available for purchase
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserved inventory for order '397faa44-1374-4f9d-a7fe-c74160604064' of 10 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Approved requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Processing payment: 397faa44-1374-4f9d-a7fe-c74160604064 for 10 cars at $150000
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Payment for request ID '397faa44-1374-4f9d-a7fe-c74160604064' processed successfully
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updating inventory for order '397faa44-1374-4f9d-a7fe-c74160604064' of 10 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updated inventory for order '397faa44-1374-4f9d-a7fe-c74160604064': there are now 90 cars left in stock
== APP - order-processor == there are now 90 cars left in stock
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Order completed! : 397faa44-1374-4f9d-a7fe-c74160604064
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Instance ID(order ID): d1bf548b-c854-44af-978e-90c61ed88e3c
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Current Orchestration Time: 2025-02-12T14:44:18.154Z
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Received Order: OrderPayload [itemName=cars, totalCost=5000, quantity=1]
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Received Order: OrderPayload [itemName=cars, totalCost=5000, quantity=1]
== APP - order-processor == workflow instance d1bf548b-c854-44af-978e-90c61ed88e3c started
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity - Verifying inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c' of 1 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity - There are 10 cars available for purchase
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity - Verified inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c' of 1 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Processing payment: d1bf548b-c854-44af-978e-90c61ed88e3c for 1 cars at $5000
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Payment for request ID 'd1bf548b-c854-44af-978e-90c61ed88e3c' processed successfully
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updating inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c' of 1 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updated inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c': there are now 9 cars left in stock
== APP - order-processor == there are now 9 cars left in stock
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Order completed! : d1bf548b-c854-44af-978e-90c61ed88e3c
== APP - order-processor == workflow instance completed, out is: {"processed":true}
```
4. Stop Dapr workflow with CTRL-C or:
```sh
dapr stop -f .
```
### View workflow output with Zipkin
For a more detailed view of the workflow activities (duration, progress etc.), try using Zipkin.
@ -97,15 +110,16 @@ docker run -d -p 9411:9411 openzipkin/zipkin
### What happened?
When you ran `dapr run --app-id order-processor --resources-path ../../../components/ --dapr-grpc-port 50001 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar io.dapr.quickstarts.workflows.WorkflowConsoleApp`
When you ran `dapr run -f .`:
1. A unique order ID for the workflow is generated (in the above example, `95d33f7c-3af8-4960-ba11-4ecea83b0509`) and the workflow is scheduled.
2. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received.
3. The `ReserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
4. Your workflow starts and notifies you of its status.
5. The `RequestApprovalActivity` workflow activity requests approval for order `95d33f7c-3af8-4960-ba11-4ecea83b0509`
6. The `ProcessPaymentActivity` workflow activity begins processing payment for order `95d33f7c-3af8-4960-ba11-4ecea83b0509` and confirms if successful.
1. An OrderPayload is made containing one car.
2. A unique order ID for the workflow is generated (in the above example, `d1bf548b-c854-44af-978e-90c61ed88e3c`) and the workflow is scheduled.
3. The `NotifyActivity` workflow activity sends a notification saying an order for one car has been received.
4. The `VertifyInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. The inventory is sufficient so the workflow continues.
5. The total cost of the order is 5000, so the workflow will not call the `RequestApprovalActivity` activity.
6. The `ProcessPaymentActivity` workflow activity begins processing payment for order `d1bf548b-c854-44af-978e-90c61ed88e3c` and confirms if successful.
7. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
8. The `NotifyActivity` workflow activity sends a notification saying that order `95d33f7c-3af8-4960-ba11-4ecea83b0509` has completed and processed.
9. The workflow terminates as completed and processed.
8. The `NotifyActivity` workflow activity sends a notification saying that order `d1bf548b-c854-44af-978e-90c61ed88e3c` has completed.
9. The workflow terminates as completed and the orderResult is set to processed.
> **Note:** This quickstart uses an OrderPayload of one car with a total cost of $5000. Since the total order cost is not over 5000, the workflow will not call the `RequestApprovalActivity` activity nor wait for an approval event. Since the quickstart is a console application, it can't accept incoming events easily. If you want to test this scenario, convert the console app to a service and use the [raise event API](https://v1-15.docs.dapr.io/reference/api/workflow_api/#raise-event-request) via HTTP/gRPC or via the Dapr Workflow client to send an event to the workflow instance.

View File

@ -1,13 +1,14 @@
package io.dapr.quickstarts.workflows;
import java.time.Duration;
import org.slf4j.Logger;
import io.dapr.quickstarts.workflows.activities.NotifyActivity;
import io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity;
import io.dapr.quickstarts.workflows.activities.RequestApprovalActivity;
import io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity;
import io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity;
import io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity;
import io.dapr.quickstarts.workflows.models.ApprovalResult;
import io.dapr.quickstarts.workflows.models.ApprovalResponse;
import io.dapr.quickstarts.workflows.models.InventoryRequest;
import io.dapr.quickstarts.workflows.models.InventoryResult;
import io.dapr.quickstarts.workflows.models.Notification;
@ -44,7 +45,7 @@ public class OrderProcessingWorkflow extends Workflow {
inventoryRequest.setRequestId(orderId);
inventoryRequest.setItemName(order.getItemName());
inventoryRequest.setQuantity(order.getQuantity());
InventoryResult inventoryResult = ctx.callActivity(ReserveInventoryActivity.class.getName(),
InventoryResult inventoryResult = ctx.callActivity(VerifyInventoryActivity.class.getName(),
inventoryRequest, InventoryResult.class).await();
// If there is insufficient inventory, fail and let the user know
@ -57,9 +58,11 @@ public class OrderProcessingWorkflow extends Workflow {
// 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) {
ctx.callActivity(RequestApprovalActivity.class.getName(), order).await();
ApprovalResponse approvalResponse = ctx.waitForExternalEvent("approvalEvent",
Duration.ofSeconds(30), ApprovalResponse.class).await();
if (!approvalResponse.isApproved()) {
notification.setMessage("Order " + order.getItemName() + " was not approved.");
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
ctx.complete(orderResult);

View File

@ -21,7 +21,7 @@ import io.dapr.client.DaprClientBuilder;
import io.dapr.quickstarts.workflows.activities.NotifyActivity;
import io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity;
import io.dapr.quickstarts.workflows.activities.RequestApprovalActivity;
import io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity;
import io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity;
import io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity;
import io.dapr.quickstarts.workflows.models.InventoryItem;
import io.dapr.quickstarts.workflows.models.OrderPayload;
@ -51,7 +51,7 @@ public class WorkflowConsoleApp {
builder.registerActivity(NotifyActivity.class);
builder.registerActivity(ProcessPaymentActivity.class);
builder.registerActivity(RequestApprovalActivity.class);
builder.registerActivity(ReserveInventoryActivity.class);
builder.registerActivity(VerifyInventoryActivity.class);
builder.registerActivity(UpdateInventoryActivity.class);
// Build and then start the workflow runtime pulling and executing tasks
@ -109,19 +109,19 @@ public class WorkflowConsoleApp {
}
private static InventoryItem prepareInventoryAndOrder() {
// prepare 100 cars in inventory
// prepare 10 cars in inventory
InventoryItem inventory = new InventoryItem();
inventory.setName("cars");
inventory.setPerItemCost(15000);
inventory.setQuantity(100);
inventory.setPerItemCost(50000);
inventory.setQuantity(10);
DaprClient daprClient = new DaprClientBuilder().build();
restockInventory(daprClient, inventory);
// prepare order for 10 cars
InventoryItem order = new InventoryItem();
order.setName("cars");
order.setPerItemCost(15000);
order.setQuantity(10);
order.setPerItemCost(5000);
order.setQuantity(1);
return order;
}

View File

@ -3,7 +3,6 @@ package io.dapr.quickstarts.workflows.activities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.dapr.quickstarts.workflows.models.ApprovalResult;
import io.dapr.quickstarts.workflows.models.OrderPayload;
import io.dapr.workflows.runtime.WorkflowActivity;
import io.dapr.workflows.runtime.WorkflowActivityContext;
@ -16,9 +15,13 @@ public class RequestApprovalActivity implements WorkflowActivity {
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;
// Simulate slow processing & sending the approval to the recipient
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
}
return "";
}
}

View File

@ -12,21 +12,21 @@ import io.dapr.quickstarts.workflows.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);
public class VerifyInventoryActivity implements WorkflowActivity {
private static Logger logger = LoggerFactory.getLogger(VerifyInventoryActivity.class);
private static final String STATE_STORE_NAME = "statestore";
private DaprClient daprClient;
public ReserveInventoryActivity() {
public VerifyInventoryActivity() {
this.daprClient = new DaprClientBuilder().build();
}
@Override
public Object run(WorkflowActivityContext ctx) {
InventoryRequest inventoryRequest = ctx.getInput(InventoryRequest.class);
logger.info("Reserving inventory for order '{}' of {} {}",
logger.info("Verifying inventory for order '{}' of {} {}",
inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName());
State<InventoryItem> inventoryState = daprClient.getState(STATE_STORE_NAME, inventoryRequest.getItemName(), InventoryItem.class).block();
@ -42,7 +42,7 @@ public class ReserveInventoryActivity implements WorkflowActivity {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
}
logger.info("Reserved inventory for order '{}' of {} {}",
logger.info("Verified inventory for order '{}' of {} {}",
inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName());
InventoryResult inventoryResult = new InventoryResult();
inventoryResult.setSuccess(true);
@ -51,7 +51,7 @@ public class ReserveInventoryActivity implements WorkflowActivity {
}
// Not enough items.
logger.info("Not enough items to reserve inventory for order '{}' of {} {}",
logger.info("Not enough items in inventory for order '{}' of {} {}",
inventoryRequest.getRequestId(), inventoryRequest.getQuantity(), inventoryRequest.getItemName());
InventoryResult inventoryResult = new InventoryResult();
inventoryResult.setSuccess(false);

View File

@ -0,0 +1,19 @@
package io.dapr.quickstarts.workflows.models;
public class ApprovalResponse {
private boolean approved;
public boolean isApproved() {
return approved;
}
public void setIsApproved(boolean isApproved) {
this.approved = isApproved;
}
@Override
public String toString() {
return "ApprovalResponse [isApproved=" + approved + "]";
}
}

View File

@ -1,7 +0,0 @@
package io.dapr.quickstarts.workflows.models;
public enum ApprovalResult {
Unspecified,
Approved,
Rejected
}

View File

@ -1,6 +1,6 @@
# Dapr workflows
In this quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
In this quickstart, you'll run a console application to demonstrate Dapr's workflow programming model and the workflow management API. The console app starts and manages the lifecycle of a workflow that stores and retrieves data in a state store.
This quickstart includes 3 entry points demonstrating different ways to host a workflow:
@ -11,8 +11,8 @@ This quickstart includes 3 entry points demonstrating different ways to host a w
The quickstart contains 1 workflow to simulate purchasing items from a store, and 5 unique activities within the workflow. These 5 activities are as follows:
- notifyActivity: This activity utilizes a logger to print out messages throughout the workflow. These messages notify the user when there is insufficient inventory, their payment couldn't be processed, and more.
- reserveInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- requestApprovalActivity: This activity requests approval for orders over a certain threshold
- verifyInventoryActivity: This activity checks the state store to ensure that there is enough inventory present for purchase.
- requestApprovalActivity: This activity requests approval for orders over a certain threshold.
- processPaymentActivity: This activity is responsible for processing and authorizing the payment.
- updateInventoryActivity: This activity updates the state store with the new remaining inventory value.
@ -37,8 +37,8 @@ npm run build
<!-- STEP
name: Run order-processor service
expected_stdout_lines:
- '== APP - order-processor == Payment of 100 for 10 item1 processed successfully'
- 'there are now 90 item1 in stock'
- '== APP - order-processor == Payment of 5000 for 1 car processed successfully'
- 'there are now 9 car in stock'
- 'processed successfully!'
expected_stderr_lines:
output_match_mode: substring
@ -77,66 +77,73 @@ curl --request POST \
```
== APP - order-processor == == APP == Orchestration scheduled with ID: 0c332155-1e02-453a-a333-28cfc7777642
== APP - order-processor == == APP == Waiting 30 seconds for instance 0c332155-1e02-453a-a333-28cfc7777642 to complete...
== APP - order-processor == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 0 history event...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, EXECUTIONSTARTED=1]
== APP - order-processor == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - order-processor == == APP == Received "Activity Request" work item
== APP - order-processor == == APP == Received order 0c332155-1e02-453a-a333-28cfc7777642 for 10 item1 at a total cost of 100
== APP - order-processor == == APP == Activity notifyActivity completed with output undefined (0 chars)
== APP - order-processor == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 3 history event...
== APP - order-processor == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - order-processor == == APP == Received "Activity Request" work item
== APP - order-processor == == APP == Reserving inventory for 0c332155-1e02-453a-a333-28cfc7777642 of 10 item1
== APP - order-processor == == APP == 2024-02-16T03:15:59.498Z INFO [HTTPClient, HTTPClient] Sidecar Started
== APP - order-processor == == APP == There are 100 item1 in stock
== APP - order-processor == == APP == Activity reserveInventoryActivity completed with output {"success":true,"inventoryItem":{"perItemCost":100,"quantity":100,"itemName":"item1"}} (86 chars)
== APP - order-processor == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 6 history event...
== APP - order-processor == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - order-processor == == APP == Received "Activity Request" work item
== APP - order-processor == == APP == Processing payment for order item1
== APP - order-processor == == APP == Payment of 100 for 10 item1 processed successfully
== APP - order-processor == == APP == Activity processPaymentActivity completed with output true (4 chars)
== APP - order-processor == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 9 history event...
== APP - order-processor == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - order-processor == == APP == Received "Activity Request" work item
== APP - order-processor == == APP == Updating inventory for 0c332155-1e02-453a-a333-28cfc7777642 of 10 item1
== APP - order-processor == == APP == Inventory updated for 0c332155-1e02-453a-a333-28cfc7777642, there are now 90 item1 in stock
== APP - order-processor == == APP == Activity updateInventoryActivity completed with output {"success":true,"inventoryItem":{"perItemCost":100,"quantity":90,"itemName":"item1"}} (85 chars)
== APP - order-processor == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 12 history event...
== APP - order-processor == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - order-processor == == APP == Received "Activity Request" work item
== APP - order-processor == == APP == order 0c332155-1e02-453a-a333-28cfc7777642 processed successfully!
== APP - order-processor == == APP == Activity notifyActivity completed with output undefined (0 chars)
== APP - order-processor == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 15 history event...
== APP - order-processor == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == == APP == Order 0c332155-1e02-453a-a333-28cfc7777642 processed successfully!
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Orchestration completed with status COMPLETED
== APP - order-processor == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - order-processor == time="2024-02-15T21:15:59.5589687-06:00" level=info msg="0c332155-1e02-453a-a333-28cfc7777642: 'orderProcessingWorkflow' completed with a COMPLETED status." app_id=activity-sequence-workflow instance=kaibocai-devbox scope=wfengine.backend type=log ver=1.12.4
== APP - order-processor == == APP == Instance 0c332155-1e02-453a-a333-28cfc7777642 completed
== APP - order-processor == Starting new orderProcessingWorkflow instance with ID = f5087775-779c-4e73-ac77-08edfcb375f4
== APP - order-processor == Orchestration scheduled with ID: f5087775-779c-4e73-ac77-08edfcb375f4
== APP - order-processor == Waiting 30 seconds for instance f5087775-779c-4e73-ac77-08edfcb375f4 to complete...
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 0 history event...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, EXECUTIONSTARTED=1]
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Received order f5087775-779c-4e73-ac77-08edfcb375f4 for 1 car at a total cost of 5000
== APP - order-processor == Activity notifyActivity completed with output undefined (0 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 3 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Verifying inventory for f5087775-779c-4e73-ac77-08edfcb375f4 of 1 car
== APP - order-processor == 2025-02-13T10:33:21.622Z INFO [HTTPClient, HTTPClient] Sidecar Started
== APP - order-processor == There are 10 car in stock
== APP - order-processor == Activity verifyInventoryActivity completed with output {"success":true,"inventoryItem":{"itemName":"car","perItemCost":5000,"quantity":10}} (84 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 6 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Processing payment for order car
== APP - order-processor == Payment of 5000 for 1 car processed successfully
== APP - order-processor == Activity processPaymentActivity completed with output true (4 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 9 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Updating inventory for f5087775-779c-4e73-ac77-08edfcb375f4 of 1 car
== APP - order-processor == Inventory updated for f5087775-779c-4e73-ac77-08edfcb375f4, there are now 9 car in stock
== APP - order-processor == Activity updateInventoryActivity completed with output {"success":true,"inventoryItem":{"itemName":"car","perItemCost":5000,"quantity":9}} (83 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 12 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == order f5087775-779c-4e73-ac77-08edfcb375f4 processed successfully!
== APP - order-processor == Activity notifyActivity completed with output undefined (0 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 15 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == Order f5087775-779c-4e73-ac77-08edfcb375f4 processed successfully!
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Orchestration completed with status COMPLETED
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Instance f5087775-779c-4e73-ac77-08edfcb375f4 completed
== APP - order-processor == Orchestration completed! Result: {"processed":true}
```
4. Stop Dapr workflow with CTRL-C or:
```sh
dapr stop -f .
```
### View workflow output with Zipkin
@ -155,15 +162,16 @@ docker run -d -p 9411:9411 openzipkin/zipkin
### What happened?
When you ran `dapr run --app-id activity-sequence-workflow --app-protocol grpc --dapr-grpc-port 50001 --components-path ../../components npm run start:order-process`
When you ran `dapr run -f .`
1. A unique order ID for the workflow is generated (in the above example, `0c332155-1e02-453a-a333-28cfc7777642`) and the workflow is scheduled.
2. The `notifyActivity` workflow activity sends a notification saying an order for 10 cars has been received.
3. The `reserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
1. A unique order ID for the workflow is generated (in the above example, `f5087775-779c-4e73-ac77-08edfcb375f4`) and the workflow is scheduled.
2. The `notifyActivity` workflow activity sends a notification saying an order for 1 car has been received.
3. The `verifyInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
4. Your workflow starts and notifies you of its status.
5. The `requestApprovalActivity` workflow activity requests approval for order `0c332155-1e02-453a-a333-28cfc7777642`
6. The `processPaymentActivity` workflow activity begins processing payment for order `0c332155-1e02-453a-a333-28cfc7777642` and confirms if successful.
5. The `requestApprovalActivity` workflow activity requests approval for order `f5087775-779c-4e73-ac77-08edfcb375f4`
6. The `processPaymentActivity` workflow activity begins processing payment for order `f5087775-779c-4e73-ac77-08edfcb375f4` and confirms if successful.
7. The `updateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
8. The `notifyActivity` workflow activity sends a notification saying that order `0c332155-1e02-453a-a333-28cfc7777642` has completed and processed.
8. The `notifyActivity` workflow activity sends a notification saying that order `f5087775-779c-4e73-ac77-08edfcb375f4` has completed and processed.
9. The workflow terminates as completed and processed.
> **Note:** This quickstart uses an OrderPayload of one car with a total cost of $5000. Since the total order cost is not over 5000, the workflow will not call the `requestApprovalActivity` activity nor wait for an approval event. The dapr.yaml multi-app run file starts a console application and can't accept incoming events easily. The dapr-AppWithDaprServer.yaml and dapr-AppWithExpressServer.yaml files start a service that can accept incoming events. Use the [raise event API](https://v1-15.docs.dapr.io/reference/api/workflow_api/#raise-event-request) via HTTP/gRPC or via the Dapr Workflow client in the server apps to send an event to the workflow.

View File

@ -1,6 +1,6 @@
import { DaprWorkflowClient, WorkflowRuntime, DaprClient, CommunicationProtocolEnum } from "@dapr/dapr";
import { InventoryItem, OrderPayload } from "./model";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, verifyInventoryActivity as verifyInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
const workflowWorker = new WorkflowRuntime();
@ -20,7 +20,7 @@ async function start() {
const storeName = "statestore";
const inventory = new InventoryItem("item1", 100, 100);
const inventory = new InventoryItem("car", 5000, 10);
const key = inventory.itemName;
await daprClient.state.save(storeName, [
@ -30,12 +30,12 @@ async function start() {
}
]);
const order = new OrderPayload("item1", 100, 10);
const order = new OrderPayload("car", 5000, 1);
workflowWorker
.registerWorkflow(orderProcessingWorkflow)
.registerActivity(notifyActivity)
.registerActivity(reserveInventoryActivity)
.registerActivity(verifyInventoryActivity)
.registerActivity(requestApprovalActivity)
.registerActivity(processPaymentActivity)
.registerActivity(updateInventoryActivity);

View File

@ -1,6 +1,6 @@
import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr";
import { InventoryItem, OrderPayload } from "./model";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, verifyInventoryActivity as verifyInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
import { DaprServer, CommunicationProtocolEnum } from "@dapr/dapr";
import express from "express";
@ -27,7 +27,7 @@ const workflowWorker = new WorkflowRuntime();
app.post("/start-workflow", async (req, res) => {
const storeName = "statestore";
const inventory = new InventoryItem("item1", 100, 100);
const inventory = new InventoryItem("car", 5000, 10);
const key = inventory.itemName;
await daprClient.state.delete(storeName, key);
@ -38,7 +38,7 @@ app.post("/start-workflow", async (req, res) => {
}
]);
const order = new OrderPayload("item1", 100, 10);
const order = new OrderPayload("car", 5000, 1);
// Schedule a new orchestration
try {
@ -62,7 +62,7 @@ async function start() {
workflowWorker
.registerWorkflow(orderProcessingWorkflow)
.registerActivity(notifyActivity)
.registerActivity(reserveInventoryActivity)
.registerActivity(verifyInventoryActivity)
.registerActivity(requestApprovalActivity)
.registerActivity(processPaymentActivity)
.registerActivity(updateInventoryActivity);

View File

@ -1,6 +1,6 @@
import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr";
import { InventoryItem, OrderPayload } from "./model";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, verifyInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
import express from "express";
const app = express();
@ -12,7 +12,7 @@ const workflowWorker = new WorkflowRuntime();
app.post("/start-workflow", async (req, res) => {
const storeName = "statestore";
const inventory = new InventoryItem("item1", 100, 100);
const inventory = new InventoryItem("car", 5000, 10);
const key = inventory.itemName;
await daprClient.state.save(storeName, [
@ -22,7 +22,7 @@ app.post("/start-workflow", async (req, res) => {
}
]);
const order = new OrderPayload("item1", 100, 10);
const order = new OrderPayload("car", 5000, 1);
// Schedule a new orchestration
try {
@ -46,7 +46,7 @@ async function start() {
workflowWorker
.registerWorkflow(orderProcessingWorkflow)
.registerActivity(notifyActivity)
.registerActivity(reserveInventoryActivity)
.registerActivity(verifyInventoryActivity)
.registerActivity(requestApprovalActivity)
.registerActivity(processPaymentActivity)
.registerActivity(updateInventoryActivity);

View File

@ -105,3 +105,15 @@ export class OrderNotification {
return `Notification(message=${this.message})`;
}
}
export class OrderResult {
processed: boolean;
constructor(processed: boolean) {
this.processed = processed;
}
toString(): string {
return `OrderResult(processed=${this.processed})`;
}
}

View File

@ -1,5 +1,5 @@
import { WorkflowActivityContext, WorkflowContext, TWorkflow, DaprClient } from "@dapr/dapr";
import { InventoryItem, InventoryRequest, InventoryResult, OrderNotification, OrderPayload, OrderPaymentRequest } from "./model";
import { Task, WorkflowActivityContext, WorkflowContext, TWorkflow, DaprClient } from "@dapr/dapr";
import { InventoryItem, InventoryRequest, InventoryResult, OrderNotification, OrderPayload, OrderPaymentRequest, OrderResult } from "./model";
const daprClient = new DaprClient();
const storeName = "statestore";
@ -10,9 +10,9 @@ export const notifyActivity = async (_: WorkflowActivityContext, orderNotificati
return;
};
//Defines Reserve Inventory Activity. This is used by the workflow to verify if inventory is available for the order
export const reserveInventoryActivity = async (_: WorkflowActivityContext, inventoryRequest: InventoryRequest) => {
console.log(`Reserving inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
//Defines Verify Inventory Activity. This is used by the workflow to verify if inventory is available for the order
export const verifyInventoryActivity = async (_: WorkflowActivityContext, inventoryRequest: InventoryRequest) => {
console.log(`Verifying inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
const result = await daprClient.state.get(storeName, inventoryRequest.itemName);
if (result == undefined || result == null) {
return new InventoryResult(false, undefined);
@ -69,24 +69,40 @@ export const orderProcessingWorkflow: TWorkflow = async function* (ctx: Workflow
yield ctx.callActivity(notifyActivity, orderNotification);
const inventoryRequest = new InventoryRequest(orderId, orderPayLoad.itemName, orderPayLoad.quantity);
const inventoryResult = yield ctx.callActivity(reserveInventoryActivity, inventoryRequest);
const inventoryResult = yield ctx.callActivity(verifyInventoryActivity, inventoryRequest);
if (!inventoryResult.success) {
const orderNotification: OrderNotification = {
message: `Insufficient inventory for order ${orderId}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
return new OrderResult(false);
}
if (orderPayLoad.totalCost > 5000) {
const approvalResult = yield ctx.callActivity(requestApprovalActivity, orderPayLoad);
if (!approvalResult) {
yield ctx.callActivity(requestApprovalActivity, orderPayLoad);
const tasks: Task<any>[] = [];
const approvalEvent = ctx.waitForExternalEvent("approval_event");
tasks.push(approvalEvent);
const timeOutEvent = ctx.createTimer(30);
tasks.push(timeOutEvent);
const winner = ctx.whenAny(tasks);
if (winner == timeOutEvent) {
const orderNotification: OrderNotification = {
message: `Order ${orderId} approval denied`,
message: `Order ${orderId} has been cancelled due to approval timeout.`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
return new OrderResult(false);
}
const approvalResult = approvalEvent.getResult();
if (!approvalResult) {
const orderNotification: OrderNotification = {
message: `Order ${orderId} was not approved.`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return new OrderResult(false);
}
}
@ -98,7 +114,7 @@ export const orderProcessingWorkflow: TWorkflow = async function* (ctx: Workflow
message: `Payment for order ${orderId} failed`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
return new OrderResult(false);
}
const updatedResult = yield ctx.callActivity(updateInventoryActivity, inventoryRequest);
@ -107,7 +123,7 @@ export const orderProcessingWorkflow: TWorkflow = async function* (ctx: Workflow
message: `Failed to update inventory for order ${orderId}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return;
return new OrderResult(false);
}
const orderCompletedNotification: OrderNotification = {
@ -116,4 +132,5 @@ export const orderProcessingWorkflow: TWorkflow = async function* (ctx: Workflow
yield ctx.callActivity(notifyActivity, orderCompletedNotification);
console.log(`Order ${orderId} processed successfully!`);
return new OrderResult(true);
}

View File

@ -33,8 +33,8 @@ cd ..
<!-- STEP
name: Running this example
expected_stdout_lines:
- "There are now 90 cars left in stock"
- "Workflow completed!"
- "== APP - order-processor == INFO:UpdateInventoryActivity:There are now 9 cars left in stock"
- "== APP - order-processor == Workflow completed! Result: {\"processed\": true"
output_match_mode: substring
background: true
timeout_seconds: 120
@ -45,34 +45,63 @@ sleep: 15
dapr run -f .
```
<!-- END_STEP -->
3. Expected output
```
==========Begin the purchase of item:==========
Starting order workflow, purchasing 10 of cars
INFO:NotifyActivity:Received order b903d749cd814e099f06ebf4a56a2f90 for 10 cars at $150000 !
INFO:VerifyInventoryActivity:Verifying inventory for order b903d749cd814e099f06ebf4a56a2f90 of 10 cars
INFO:VerifyInventoryActivity:There are 100 Cars available for purchase
INFO:RequestApprovalActivity:Requesting approval for payment of 150000 USD for 10 cars
INFO:NotifyActivity:Payment for order b903d749cd814e099f06ebf4a56a2f90 has been approved!
INFO:ProcessPaymentActivity:Processing payment: b903d749cd814e099f06ebf4a56a2f90 for 10 cars at 150000 USD
INFO:ProcessPaymentActivity:Payment for request ID b903d749cd814e099f06ebf4a56a2f90 processed successfully
INFO:UpdateInventoryActivity:Checking inventory for order b903d749cd814e099f06ebf4a56a2f90 for 10 cars
INFO:UpdateInventoryActivity:There are now 90 cars left in stock
INFO:NotifyActivity:Order b903d749cd814e099f06ebf4a56a2f90 has completed!
Workflow completed! Result: Completed
Purchase of item is Completed
== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == 2025-02-13 11:44:11.357 durabletask-worker INFO: Starting gRPC worker that connects to dns:127.0.0.1:38891
== APP - order-processor == 2025-02-13 11:44:11.361 durabletask-worker INFO: Successfully connected to dns:127.0.0.1:38891. Waiting for work items...
== APP - order-processor == INFO:NotifyActivity:Received order 6830cb00174544a0b062ba818e14fddc for 1 cars at $5000 !
== APP - order-processor == 2025-02-13 11:44:14.157 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:VerifyInventoryActivity:Verifying inventory for order 6830cb00174544a0b062ba818e14fddc of 1 cars
== APP - order-processor == INFO:VerifyInventoryActivity:There are 10 Cars available for purchase
== APP - order-processor == 2025-02-13 11:44:14.171 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:ProcessPaymentActivity:Processing payment: 6830cb00174544a0b062ba818e14fddc for 1 cars at 5000 USD
== APP - order-processor == INFO:ProcessPaymentActivity:Payment for request ID 6830cb00174544a0b062ba818e14fddc processed successfully
== APP - order-processor == 2025-02-13 11:44:14.177 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:UpdateInventoryActivity:Checking inventory for order 6830cb00174544a0b062ba818e14fddc for 1 cars
== APP - order-processor == INFO:UpdateInventoryActivity:There are now 9 cars left in stock
== APP - order-processor == 2025-02-13 11:44:14.189 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:NotifyActivity:Order 6830cb00174544a0b062ba818e14fddc has completed!
== APP - order-processor == 2025-02-13 11:44:14.195 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestration completed with status: COMPLETED
== APP - order-processor == item: InventoryItem(item_name=Paperclip, per_item_cost=5, quantity=100)
== APP - order-processor == item: InventoryItem(item_name=Cars, per_item_cost=5000, quantity=10)
== APP - order-processor == item: InventoryItem(item_name=Computers, per_item_cost=500, quantity=100)
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == Starting order workflow, purchasing 1 of cars
== APP - order-processor == 2025-02-13 11:44:16.363 durabletask-client INFO: Starting new 'order_processing_workflow' instance with ID = 'fc8a507e4a2246d2917d3ad4e3111240'.
== APP - order-processor == 2025-02-13 11:44:16.366 durabletask-client INFO: Waiting 30s for instance 'fc8a507e4a2246d2917d3ad4e3111240' to complete.
== APP - order-processor == 2025-02-13 11:44:16.366 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:NotifyActivity:Received order fc8a507e4a2246d2917d3ad4e3111240 for 1 cars at $5000 !
== APP - order-processor == 2025-02-13 11:44:16.373 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:VerifyInventoryActivity:Verifying inventory for order fc8a507e4a2246d2917d3ad4e3111240 of 1 cars
== APP - order-processor == INFO:VerifyInventoryActivity:There are 10 Cars available for purchase
== APP - order-processor == 2025-02-13 11:44:16.383 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:ProcessPaymentActivity:Processing payment: fc8a507e4a2246d2917d3ad4e3111240 for 1 cars at 5000 USD
== APP - order-processor == INFO:ProcessPaymentActivity:Payment for request ID fc8a507e4a2246d2917d3ad4e3111240 processed successfully
== APP - order-processor == 2025-02-13 11:44:16.390 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:UpdateInventoryActivity:Checking inventory for order fc8a507e4a2246d2917d3ad4e3111240 for 1 cars
== APP - order-processor == INFO:UpdateInventoryActivity:There are now 9 cars left in stock
== APP - order-processor == 2025-02-13 11:44:16.403 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:NotifyActivity:Order fc8a507e4a2246d2917d3ad4e3111240 has completed!
== APP - order-processor == 2025-02-13 11:44:16.411 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestration completed with status: COMPLETED
== APP - order-processor == 2025-02-13 11:44:16.425 durabletask-client INFO: Instance 'fc8a507e4a2246d2917d3ad4e3111240' completed.
== APP - order-processor == 2025-02-13 11:44:16.425 durabletask-worker INFO: Stopping gRPC worker...
== APP - order-processor == 2025-02-13 11:44:16.426 durabletask-worker INFO: Disconnected from dns:127.0.0.1:38891
== APP - order-processor == 2025-02-13 11:44:16.426 durabletask-worker INFO: No longer listening for work items
== APP - order-processor == 2025-02-13 11:44:16.426 durabletask-worker INFO: Worker shutdown completed
== APP - order-processor == Workflow completed! Result: {"processed": true, "__durabletask_autoobject__": true}
```
4. Stop Dapr workflow with CTRL-C or:
<!-- END_STEP -->
```sh
dapr stop -f .
```
### View workflow output with Zipkin
For a more detailed view of the workflow activities (duration, progress etc.), try using Zipkin.
@ -83,21 +112,16 @@ For a more detailed view of the workflow activities (duration, progress etc.), t
### What happened?
When you ran `dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py`
1. First the user inputs an order for 10 cars into the concole app.
2. A unique order ID for the workflow is generated (in the above example, `b903d749cd814e099f06ebf4a56a2f90`) and the workflow is scheduled.
3. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received.
4. The `VerifyInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock.
5. The `RequestApprovalActivity` workflow activity is triggered due to buisness logic for orders exceeding $50k and user is prompted to manually approve the purchase before continuing order.
6. The workflow starts and notifies you of its status.
7. The `ProcessPaymentActivity` workflow activity begins processing payment for order `b903d749cd814e099f06ebf4a56a2f90` and confirms if successful.
8. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed.
9. The `NotifyActivity` workflow activity sends a notification saying that order `b903d749cd814e099f06ebf4a56a2f90` has completed.
10. The workflow terminates as completed.
When you ran `dapr run -f .`
1. An OrderPayload is made containing one car.
2. A unique order ID for the workflow is generated (in the above example, `fc8a507e4a2246d2917d3ad4e3111240`) and the workflow is scheduled.
3. The `notify_activity` workflow activity sends a notification saying an order for one car has been received.
4. The `verify_inventory_activity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. The inventory is sufficient so the workflow continues.
5. The total cost of the order is 5000, so the workflow will not call the `request_approval_activity` activity.
6. The `process_payment_activity` workflow activity begins processing payment for order `fc8a507e4a2246d2917d3ad4e3111240` and confirms if successful.
7. The `update_inventory_activity` workflow activity updates the inventory with the current available cars after the order has been processed.
8. The `notify_activity` workflow activity sends a notification saying that order `fc8a507e4a2246d2917d3ad4e3111240` has completed.
9. The workflow terminates as completed and the OrderResult is set to processed.
> **Note:** This quickstart uses an OrderPayload of one car with a total cost of $5000. Since the total order cost is not over 5000, the workflow will not call the `request_approval_activity` activity nor wait for an approval event. Since the quickstart is a console application, it can't accept incoming events easily. If you want to test this scenario, convert the console app to a service and use the [raise event API](https://v1-15.docs.dapr.io/reference/api/workflow_api/#raise-event-request) via HTTP/gRPC or via the Dapr Workflow client to send an event to the workflow instance.

View File

@ -1,5 +1,4 @@
from datetime import datetime
import threading
from time import sleep
from dapr.clients import DaprClient
@ -26,7 +25,7 @@ class WorkflowConsoleApp:
baseInventory = {
"paperclip": InventoryItem("Paperclip", 5, 100),
"cars": InventoryItem("Cars", 15000, 100),
"cars": InventoryItem("Cars", 5000, 10),
"computers": InventoryItem("Computers", 500, 100),
}
@ -36,67 +35,27 @@ class WorkflowConsoleApp:
print("==========Begin the purchase of item:==========", flush=True)
item_name = default_item_name
order_quantity = 10
order_quantity = 1
total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost
order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost)
print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True)
instance_id = wfClient.schedule_new_workflow(
workflow=order_processing_workflow, input=order.to_json())
_id = instance_id
def prompt_for_approval(wfClient: DaprWorkflowClient):
"""This is a helper function to prompt for approval.
Not using the prompt here ACTUALLY, as quickstart validation is required to be automated.
But, in case you may want to run this sample manually, you can uncomment the following lines:
try:
signal.alarm(15)
approved = input(f'(ID = {_id}) requires approval. Approve? [Y/N] ')
signal.alarm(0) # cancel the alarm
except TimeoutError:
approved = "y"
if state.runtime_status.name == "COMPLETED":
return
if approved.lower() == "y":
wfClient.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': True})
else:
wfClient.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': False})
## Additionally, you would need to import signal and define timeout_error:
# import signal
# def timeout_error(*_):
# raise TimeoutError
# signal.signal(signal.SIGALRM, timeout_error)
"""
wfClient.raise_workflow_event(instance_id=_id, event_name="manager_approval", data={'approval': True})
approval_seeked = False
start_time = datetime.now()
while True:
time_delta = datetime.now() - start_time
state = wfClient.get_workflow_state(instance_id=_id)
try:
state = wfClient.wait_for_workflow_completion(instance_id=instance_id, timeout_in_seconds=30)
if not state:
print("Workflow not found!") # not expected
break
if state.runtime_status in {WorkflowStatus.COMPLETED, WorkflowStatus.FAILED, WorkflowStatus.TERMINATED}:
print(f'Workflow completed! Result: {state.runtime_status}', flush=True)
break
if time_delta.total_seconds() >= 10:
state = wfClient.get_workflow_state(instance_id=_id)
if total_cost > 50000 and state not in {WorkflowStatus.COMPLETED, WorkflowStatus.FAILED, WorkflowStatus.TERMINATED} and not approval_seeked:
approval_seeked = True
threading.Thread(target=prompt_for_approval(wfClient), daemon=True).start()
print("Workflow not found!")
elif state.runtime_status.name == 'COMPLETED':
print(f'Workflow completed! Result: {state.serialized_output}')
else:
print(f'Workflow failed! Status: {state.runtime_status.name}') # not expected
except TimeoutError:
print('*** Workflow timed out!')
wfr.shutdown()
def restock_inventory(self, daprClient: DaprClient, baseInventory):
for key, item in baseInventory.items():
print(f'item: {item}')

View File

@ -22,7 +22,7 @@ def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: str):
When the order is received, the inventory is checked to see if there is enough inventory to
fulfill the order. If there is enough inventory, the payment is processed and the inventory is
updated. If there is not enough inventory, the order is rejected.
If the total order is greater than $50,000, the order is sent to a manager for approval.
If the total order is greater than $5,000, the order is sent to a manager for approval.
"""
order_id = ctx.instance_id
order_payload=json.loads(order_payload_str)
@ -40,23 +40,20 @@ def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: str):
+f'{order_payload["item_name"]}'+'!'))
return OrderResult(processed=False)
if order_payload["total_cost"] > 50000:
if order_payload["total_cost"] > 5000:
yield ctx.call_activity(request_approval_activity, input=order_payload)
approval_task = ctx.wait_for_external_event("manager_approval")
timeout_event = ctx.create_timer(timedelta(seconds=200))
approval_task = ctx.wait_for_external_event("approval_event")
timeout_event = ctx.create_timer(timedelta(seconds=30))
winner = yield when_any([approval_task, timeout_event])
if winner == timeout_event:
yield ctx.call_activity(notify_activity,
input=Notification(message='Payment for order '+order_id
+' has been cancelled due to timeout!'))
input=Notification(message='Order '+order_id
+' has been cancelled due to approval timeout.'))
return OrderResult(processed=False)
approval_result = yield approval_task
if approval_result["approval"]:
if approval_result == False:
yield ctx.call_activity(notify_activity, input=Notification(
message=f'Payment for order {order_id} has been approved!'))
else:
yield ctx.call_activity(notify_activity, input=Notification(
message=f'Payment for order {order_id} has been rejected!'))
message=f'Order {order_id} was not approved'))
return OrderResult(processed=False)
yield ctx.call_activity(process_payment_activity, input=PaymentRequest(
@ -137,7 +134,7 @@ def update_inventory_activity(ctx: WorkflowActivityContext,
new_quantity = res_json['quantity'] - input.quantity
per_item_cost = res_json['per_item_cost']
if new_quantity < 0:
raise ValueError('Payment for request ID '+f'{input.item_being_purchased}'
raise ValueError('Inventory update for request ID '+f'{input.item_being_purchased}'
+' could not be processed. Insufficient inventory.')
new_val = f'{{"name": "{input.item_being_purchased}", "quantity": {str(new_quantity)}, "per_item_cost": {str(per_item_cost)}}}'
client.save_state(store_name, input.item_being_purchased, new_val)
@ -150,7 +147,7 @@ def request_approval_activity(ctx: WorkflowActivityContext,
input: OrderPayload):
"""Defines Request Approval Activity. This is used by the workflow to request approval
for payment of an order. This activity is used only if the order total cost is greater than
a particular threshold, currently 50000 USD"""
a particular threshold"""
logger = logging.getLogger('RequestApprovalActivity')
logger.info('Requesting approval for payment of '+f'{input["total_cost"]}'+' USD for '