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

Modernized and updated Dapr Workflows .NET quickstart #1130

Merged
merged 3 commits into from
Jan 23, 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
71 changes: 40 additions & 31 deletions workflows/csharp/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ dotnet build
```

<!-- END_STEP -->
2. Run the console app with Dapr:
2. Run the console app with Dapr. Note that you may need to run `cd ..` to move the working directory up one to
'/workflows/csharp/sdk':

<!-- 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: 10 Cars available for purchase"
- "== APP - order-processor == Workflow Status: Completed"
expected_stderr_lines:
output_match_mode: substring
Expand All @@ -50,33 +51,41 @@ dapr run -f .

3. Expected output


```
== APP == Starting workflow 6d2abcc9 purchasing 10 Cars

== APP == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[40]
== APP == Scheduling new OrderProcessingWorkflow orchestration with instance ID '6d2abcc9' and 47 bytes of input data.
== APP == info: WorkflowConsoleApp.Activities.NotifyActivity[0]
== APP == Received order 6d2abcc9 for 10 Cars at $15000
== APP == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
== APP == Reserving inventory for order 6d2abcc9 of 10 Cars
== APP == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
== APP == There are: 100, Cars available for purchase

== APP == Your workflow has started. Here is the status of the workflow: Dapr.Workflow.WorkflowState

== APP == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
== APP == Processing payment: 6d2abcc9 for 10 Cars at $15000
== APP == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
== APP == Payment for request ID '6d2abcc9' processed successfully
== APP == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[0]
== APP == Checking Inventory for: Order# 6d2abcc9 for 10 Cars
== APP == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[0]
== APP == There are now: 90 Cars left in stock
== APP == info: WorkflowConsoleApp.Activities.NotifyActivity[0]
== APP == Order 6d2abcc9 has completed!

== APP == Workflow Status: Completed
== APP - order-processor == Starting workflow 898fd553 purchasing 10 Cars

== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[42]
== 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 == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == Presenting notification Notification { Message = Order 898fd553 has completed! }
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[510392223]
== APP - order-processor == Order 898fd553 has completed
== APP - order-processor == Workflow Status: Completed
```


Expand All @@ -99,12 +108,12 @@ docker run -d -p 9411:9411 openzipkin/zipkin

When you ran `dapr run --app-id order-processor dotnet run`

1. A unique order ID for the workflow is generated (in the above example, `6d2abcc9`) and the workflow is scheduled.
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 `6d2abcc9` and confirms if successful.
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 `6d2abcc9` has completed.
7. The `NotifyActivity` workflow activity sends a notification saying that order `898fd553` has completed.
8. The workflow terminates as completed.

34 changes: 14 additions & 20 deletions workflows/csharp/sdk/order-processor/Activities/NotifyActivity.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
namespace WorkflowConsoleApp.Activities
{
using System.Threading.Tasks;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
namespace WorkflowConsoleApp.Activities;

record Notification(string Message);
using System.Threading.Tasks;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;

class NotifyActivity : WorkflowActivity<Notification, object>
internal sealed partial class NotifyActivity(ILogger<NotifyActivity> logger) : WorkflowActivity<Notification, object?>
{
public override Task<object?> RunAsync(WorkflowActivityContext context, Notification notification)
{
readonly ILogger logger;

public NotifyActivity(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.CreateLogger<NotifyActivity>();
}

public override Task<object> RunAsync(WorkflowActivityContext context, Notification notification)
{
this.logger.LogInformation(notification.Message);

return Task.FromResult<object>(null);
}
LogNotification(logger, notification);
return Task.FromResult<object?>(null);
}

[LoggerMessage(LogLevel.Information, "Presenting notification {notification}")]
static partial void LogNotification(ILogger logger, Notification notification);
}

internal sealed record Notification(string Message);
Original file line number Diff line number Diff line change
@@ -1,40 +1,28 @@
namespace WorkflowConsoleApp.Activities
{
using System.Threading.Tasks;
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
using WorkflowConsoleApp.Models;
using System;
namespace WorkflowConsoleApp.Activities;

class ProcessPaymentActivity : WorkflowActivity<PaymentRequest, object>
{
readonly ILogger logger;
readonly DaprClient client;
using System.Threading.Tasks;
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
using Models;
using System;

public ProcessPaymentActivity(ILoggerFactory loggerFactory, DaprClient client)
{
this.logger = loggerFactory.CreateLogger<ProcessPaymentActivity>();
this.client = client;
}
internal sealed partial class ProcessPaymentActivity(ILogger<ProcessPaymentActivity> logger) : WorkflowActivity<PaymentRequest, object?>
{
public override async Task<object?> RunAsync(WorkflowActivityContext context, PaymentRequest req)
{
LogPaymentProcessing(logger, req.RequestId, req.Amount, req.ItemBeingPurchased, req.Currency);

public override async Task<object> RunAsync(WorkflowActivityContext context, PaymentRequest req)
{
this.logger.LogInformation(
"Processing payment: {requestId} for {amount} {item} at ${currency}",
req.RequestId,
req.Amount,
req.ItemBeingPruchased,
req.Currency);
// Simulate slow processing
await Task.Delay(TimeSpan.FromSeconds(7));

// Simulate slow processing
await Task.Delay(TimeSpan.FromSeconds(7));
LogSuccessfulPayment(logger, req.RequestId);
return null;
}

this.logger.LogInformation(
"Payment for request ID '{requestId}' processed successfully",
req.RequestId);
[LoggerMessage(LogLevel.Information, "Processing payment: request ID '{requestId}' for {amount} {itemBeingPurchased} at ${currency}")]
static partial void LogPaymentProcessing(ILogger logger, string requestId, int amount, string itemBeingPurchased, double currency);

return null;
}
}
}
[LoggerMessage(LogLevel.Information, "Payment for request ID '{requestId}' processed successfully")]
static partial void LogSuccessfulPayment(ILogger logger, string requestId);
}
Original file line number Diff line number Diff line change
@@ -1,62 +1,55 @@
namespace WorkflowConsoleApp.Activities
namespace WorkflowConsoleApp.Activities;

using System.Threading.Tasks;
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
using Models;
using System;

internal sealed partial class ReserveInventoryActivity(ILogger<ReserveInventoryActivity> logger, DaprClient daprClient) : WorkflowActivity<InventoryRequest, InventoryResult>
{
using System.Threading.Tasks;
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
using WorkflowConsoleApp.Models;
using System;

class ReserveInventoryActivity : WorkflowActivity<InventoryRequest, InventoryResult>
private const string StoreName = "statestore";

public override async Task<InventoryResult> RunAsync(WorkflowActivityContext context, InventoryRequest req)
{
readonly ILogger logger;
readonly DaprClient client;
static readonly string storeName = "statestore";
LogReserveInventory(logger, req.RequestId, req.Quantity, req.ItemName);

public ReserveInventoryActivity(ILoggerFactory loggerFactory, DaprClient client)
// Ensure that the store has items
var (orderResult, _) = await daprClient.GetStateAndETagAsync<OrderPayload>(StoreName, req.ItemName);

// Catch for the case where the statestore isn't setup
if (orderResult is null)
{
this.logger = loggerFactory.CreateLogger<ReserveInventoryActivity>();
this.client = client;
// Not enough items.
LogStateNotFound(logger, req.RequestId, req.ItemName);
return new InventoryResult(false, null);
}

public override async Task<InventoryResult> RunAsync(WorkflowActivityContext context, InventoryRequest req)
// See if there are enough items to purchase
if (orderResult.Quantity >= req.Quantity)
{
this.logger.LogInformation(
"Reserving inventory for order {requestId} of {quantity} {name}",
req.RequestId,
req.Quantity,
req.ItemName);

OrderPayload orderResponse;
string key;

// Ensure that the store has items
(orderResponse, key) = await client.GetStateAndETagAsync<OrderPayload>(storeName, req.ItemName);

// Catch for the case where the statestore isn't setup
if (orderResponse == null)
{
// Not enough items.
return new InventoryResult(false, orderResponse);
}

this.logger.LogInformation(
"There are: {requestId}, {name} available for purchase",
orderResponse.Quantity,
orderResponse.Name);

// See if there're enough items to purchase
if (orderResponse.Quantity >= req.Quantity)
{
// Simulate slow processing
await Task.Delay(TimeSpan.FromSeconds(2));

return new InventoryResult(true, orderResponse);
}

// Not enough items.
return new InventoryResult(false, orderResponse);
// Simulate slow processing
await Task.Delay(TimeSpan.FromSeconds(2));

LogSufficientInventory(logger, req.Quantity, req.ItemName);
return new InventoryResult(true, orderResult);
}

// Not enough items.
LogInsufficientInventory(logger, req.RequestId, req.ItemName);
return new InventoryResult(false, orderResult);
}
}

[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);

[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);

[LoggerMessage(LogLevel.Information, "There are: {quantity} {name} available for purchase")]
static partial void LogSufficientInventory(ILogger logger, int quantity, string name);

[LoggerMessage(LogLevel.Warning, "There is insufficient inventory available for order request ID '{requestId}' for the item {itemName}")]
static partial void LogInsufficientInventory(ILogger logger, string requestId, string itemName);
}
Loading
Loading