Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update workflow quickstarts #1163

Merged
merged 14 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
6 changes: 4 additions & 2 deletions conversation/python/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
186 changes: 155 additions & 31 deletions workflows/csharp/sdk/README.md

Large diffs are not rendered by default.

Binary file modified workflows/csharp/sdk/img/workflow-trace-spans-zipkin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
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);
Expand All @@ -32,7 +32,7 @@ public override async Task<InventoryResult> RunAsync(WorkflowActivityContext con
// 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);
}

Expand All @@ -42,7 +42,7 @@ public override async Task<InventoryResult> RunAsync(WorkflowActivityContext con
}

[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);
Expand Down
2 changes: 2 additions & 0 deletions workflows/csharp/sdk/order-processor/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
9 changes: 5 additions & 4 deletions workflows/csharp/sdk/order-processor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@

// 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>();
});
Expand All @@ -36,13 +37,13 @@
// 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}");
Expand All @@ -67,5 +68,5 @@ await workflowClient.ScheduleNewWorkflowAsync(

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));
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ await context.CallActivityAsync(nameof(NotifyActivity),
// 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
Expand All @@ -35,6 +35,23 @@ await context.CallActivityAsync(nameof(NotifyActivity),
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);
Expand Down Expand Up @@ -73,6 +90,9 @@ await context.CallActivityAsync(nameof(NotifyActivity),
[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);

Expand Down
79 changes: 30 additions & 49 deletions workflows/go/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,64 +25,52 @@ 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
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.
Expand All @@ -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.
52 changes: 13 additions & 39 deletions workflows/go/sdk/order-processor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -71,7 +71,7 @@ func main() {
fmt.Println("==========Begin the purchase of item:==========")

itemName := defaultItemName
orderQuantity := 10
orderQuantity := 1

totalCost := inventory[1].PerItemCost * orderQuantity

Expand All @@ -86,47 +86,21 @@ 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)
}

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()
respFetch, err := wfClient.FetchWorkflowMetadata(context.Background(), id, workflow.WithFetchPayloads(true))
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)
log.Fatalf("failed to get workflow: %v", err)
}

fmt.Printf("workflow status: %v\n", respFetch.RuntimeStatus)

fmt.Println("Purchase of item is complete")
}

func restockInventory(daprClient client.Client, inventory []InventoryItem) error {
Expand Down
2 changes: 1 addition & 1 deletion workflows/go/sdk/order-processor/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading