diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index 6caa7f3ff..3ca68fde5 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -30,7 +30,25 @@ The Dapr sidecar doesn’t load any workflow definitions. Rather, the sidecar si [Workflow activities]({{< ref "workflow-features-concepts.md#workflow-activites" >}}) are the basic unit of work in a workflow and are the tasks that get orchestrated in the business process. -{{< tabs ".NET" Python >}} +{{< tabs Python ".NET" Java >}} + +{{% codetab %}} + + + +Define the workflow activities you'd like your workflow to perform. Activities are a function definition and can take inputs and outputs. The following example creates a counter (activity) called `hello_act` that notifies users of the current counter value. `hello_act` is a function derived from a class called `WorkflowActivityContext`. + +```python +def hello_act(ctx: WorkflowActivityContext, input): + global counter + counter += input + print(f'New counter value is: {counter}!', flush=True) +``` + +[See the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59) + + +{{% /codetab %}} {{% codetab %}} @@ -102,29 +120,67 @@ public class ProcessPaymentActivity : WorkflowActivity {{% codetab %}} - + -Define the workflow activities you'd like your workflow to perform. Activities are a function definition and can take inputs and outputs. The following example creates a counter (activity) called `hello_act` that notifies users of the current counter value. `hello_act` is a function derived from a class called `WorkflowActivityContext`. +Define the workflow activities you'd like your workflow to perform. -```python -def hello_act(ctx: WorkflowActivityContext, input): - global counter - counter += input - print(f'New counter value is: {counter}!', flush=True) +The activities called in the example below are: +- `need`: Receive notification of a new order. + +### [activity] + +```java +todo ``` -[See the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59) +[See the full `todo` workflow activity example.](todo) +### [activity] + +```java +todo +``` +[See the full `todo` workflow activity example.](todo) + +### [todo] + +```java +todo +``` + +[See the full `todo` workflow activity example.](todo) {{% /codetab %}} + {{< /tabs >}} ## Write the workflow Next, register and call the activites in a workflow. -{{< tabs ".NET" Python >}} +{{< tabs Python ".NET" Java >}} + +{{% codetab %}} + + + +The `hello_world_wf` function is derived from a class called `DaprWorkflowContext` with input and output parameter types. It also includes a `yield` statement that does the heavy lifting of the workflow and calls the workflow activities. + +```python +def hello_world_wf(ctx: DaprWorkflowContext, input): + print(f'{input}') + yield ctx.call_activity(hello_act, input=1) + yield ctx.call_activity(hello_act, input=10) + yield ctx.wait_for_external_event("event1") + yield ctx.call_activity(hello_act, input=100) + yield ctx.call_activity(hello_act, input=1000) +``` + +[See the `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51) + + +{{% /codetab %}} {{% codetab %}} @@ -171,21 +227,15 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo {{% codetab %}} - + -The `hello_world_wf` function is derived from a class called `DaprWorkflowContext` with input and output parameter types. It also includes a `yield` statement that does the heavy lifting of the workflow and calls the workflow activities. +Intro -```python -def hello_world_wf(ctx: DaprWorkflowContext, input): - print(f'{input}') - yield ctx.call_activity(hello_act, input=1) - yield ctx.call_activity(hello_act, input=10) - yield ctx.wait_for_external_event("event1") - yield ctx.call_activity(hello_act, input=100) - yield ctx.call_activity(hello_act, input=1000) +```java +todo ``` -[See the `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51) +[See the `todo` workflow in context.](todo) {{% /codetab %}} @@ -196,78 +246,7 @@ def hello_world_wf(ctx: DaprWorkflowContext, input): Finally, compose the application using the workflow. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - - -[In the following `Program.cs` example](https://github.com/dapr/dotnet-sdk/blob/master/examples/Workflow/WorkflowConsoleApp/Program.cs), for a basic ASP.NET order processing application using the .NET SDK, your project code would include: - -- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities -- A builder with an extension method called `AddDaprWorkflow` - - This will allow you to register workflows and workflow activities (tasks that workflows can schedule) -- HTTP API calls - - One for submitting a new order - - One for checking the status of an existing order - -```csharp -using Dapr.Workflow; -//... - -// Dapr Workflows are registered as part of the service configuration -builder.Services.AddDaprWorkflow(options => -{ - // Note that it's also possible to register a lambda function as the workflow - // or activity implementation instead of a class. - options.RegisterWorkflow(); - - // These are the activities that get invoked by the workflow(s). - options.RegisterActivity(); - options.RegisterActivity(); - options.RegisterActivity(); -}); - -WebApplication app = builder.Build(); - -// POST starts new order workflow instance -app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) => -{ - if (orderInfo?.Name == null) - { - return Results.BadRequest(new - { - message = "Order data was missing from the request", - example = new OrderPayload("Paperclips", 99.95), - }); - } - -//... -}); - -// GET fetches state for order workflow to report status -app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) => -{ - WorkflowState state = await client.GetWorkflowStateAsync(orderId, true); - if (!state.Exists) - { - return Results.NotFound($"No order with ID = '{orderId}' was found."); - } - - var httpResponsePayload = new - { - details = state.ReadInputAs(), - status = state.RuntimeStatus.ToString(), - result = state.ReadOutputAs(), - }; - -//... -}).WithName("GetOrderInfoEndpoint"); - -app.Run(); -``` - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -356,6 +335,91 @@ if __name__ == '__main__': ``` +{{% /codetab %}} + +{{% codetab %}} + + + +[In the following `Program.cs` example](https://github.com/dapr/dotnet-sdk/blob/master/examples/Workflow/WorkflowConsoleApp/Program.cs), for a basic ASP.NET order processing application using the .NET SDK, your project code would include: + +- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities +- A builder with an extension method called `AddDaprWorkflow` + - This will allow you to register workflows and workflow activities (tasks that workflows can schedule) +- HTTP API calls + - One for submitting a new order + - One for checking the status of an existing order + +```csharp +using Dapr.Workflow; +//... + +// Dapr Workflows are registered as part of the service configuration +builder.Services.AddDaprWorkflow(options => +{ + // Note that it's also possible to register a lambda function as the workflow + // or activity implementation instead of a class. + options.RegisterWorkflow(); + + // These are the activities that get invoked by the workflow(s). + options.RegisterActivity(); + options.RegisterActivity(); + options.RegisterActivity(); +}); + +WebApplication app = builder.Build(); + +// POST starts new order workflow instance +app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) => +{ + if (orderInfo?.Name == null) + { + return Results.BadRequest(new + { + message = "Order data was missing from the request", + example = new OrderPayload("Paperclips", 99.95), + }); + } + +//... +}); + +// GET fetches state for order workflow to report status +app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) => +{ + WorkflowState state = await client.GetWorkflowStateAsync(orderId, true); + if (!state.Exists) + { + return Results.NotFound($"No order with ID = '{orderId}' was found."); + } + + var httpResponsePayload = new + { + details = state.ReadInputAs(), + status = state.RuntimeStatus.ToString(), + result = state.ReadOutputAs(), + }; + +//... +}).WithName("GetOrderInfoEndpoint"); + +app.Run(); +``` + +{{% /codetab %}} + +{{% codetab %}} + + + +[In the following example](todo), for a basic Java hello world application using the Java SDK, your project code would include: + +- A Java package called `todo` to receive the Java SDK capabilities. + +```java +todo +``` + {{% /codetab %}} @@ -377,5 +441,6 @@ Now that you've authored a workflow, learn how to manage it. - [Workflow overview]({{< ref workflow-overview.md >}}) - [Workflow API reference]({{< ref workflow_api.md >}}) - Try out the full SDK examples: - - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - [Python example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java example](todo) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index 99cb87c9b..27d5d75cb 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -8,43 +8,7 @@ description: Manage and run workflows Now that you've [authored the workflow and its activities in your application]({{< ref howto-author-workflow.md >}}), you can start, terminate, and get information about the workflow using HTTP API calls. For more information, read the [workflow API reference]({{< ref workflow_api.md >}}). -{{< tabs ".NET" Python HTTP >}} - - -{{% codetab %}} - -Manage your workflow within your code. In the `OrderProcessingWorkflow` example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code. You can now start, terminate, and get information about a running workflow: - -```csharp -string orderId = "exampleOrderId"; -string workflowComponent = "dapr"; -string workflowName = "OrderProcessingWorkflow"; -OrderPayload input = new OrderPayload("Paperclips", 99.95); -Dictionary workflowOptions; // This is an optional parameter - -// Start the workflow. This returns back a "StartWorkflowResponse" which contains the instance ID for the particular workflow instance. -StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions); - -// Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more! -GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, workflowName); - -// Terminate the workflow -await daprClient.TerminateWorkflowAsync(orderId, workflowComponent); - -// Raise an event (an incoming purchase order) that your workflow will wait for. This returns the item waiting to be purchased. -await daprClient.RaiseWorkflowEventAsync(orderId, workflowComponent, workflowName, input); - -// Pause -await daprClient.PauseWorkflowAsync(orderId, workflowComponent); - -// Resume -await daprClient.ResumeWorkflowAsync(orderId, workflowComponent); - -// Purge -await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); -``` - -{{% /codetab %}} +{{< tabs Python ".NET" Java HTTP >}} {{% codetab %}} @@ -95,6 +59,53 @@ d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponen {{% /codetab %}} + +{{% codetab %}} + +Manage your workflow within your code. In the `OrderProcessingWorkflow` example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code. You can now start, terminate, and get information about a running workflow: + +```csharp +string orderId = "exampleOrderId"; +string workflowComponent = "dapr"; +string workflowName = "OrderProcessingWorkflow"; +OrderPayload input = new OrderPayload("Paperclips", 99.95); +Dictionary workflowOptions; // This is an optional parameter + +// Start the workflow. This returns back a "StartWorkflowResponse" which contains the instance ID for the particular workflow instance. +StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions); + +// Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more! +GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, workflowName); + +// Terminate the workflow +await daprClient.TerminateWorkflowAsync(orderId, workflowComponent); + +// Raise an event (an incoming purchase order) that your workflow will wait for. This returns the item waiting to be purchased. +await daprClient.RaiseWorkflowEventAsync(orderId, workflowComponent, workflowName, input); + +// Pause +await daprClient.PauseWorkflowAsync(orderId, workflowComponent); + +// Resume +await daprClient.ResumeWorkflowAsync(orderId, workflowComponent); + +// Purge +await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +Manage your workflow within your code. In the workflow example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code using the following APIs: + +```java +todo +``` + +{{% /codetab %}} + {{% codetab %}} @@ -172,6 +183,8 @@ Learn more about these HTTP calls in the [workflow API reference guide]({{< ref ## Next steps - [Try out the Workflow quickstart]({{< ref workflow-quickstart.md >}}) - Try out the full SDK examples: - - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - [Python example](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) + - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java example](todo) + - [Workflow API reference]({{< ref workflow_api.md >}}) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 9f70500c0..998e0cccc 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -83,9 +83,9 @@ You can use the following SDKs to author a workflow. | Language stack | Package | | - | - | -| .NET | [Dapr.Workflow](https://www.nuget.org/profiles/dapr.io) | | Python | [dapr-ext-workflow](https://github.com/dapr/python-sdk/tree/master/ext/dapr-ext-workflow) | - +| .NET | [Dapr.Workflow](https://www.nuget.org/profiles/dapr.io) | +| Java | need | ## Try out workflows @@ -96,8 +96,9 @@ Want to put workflows to the test? Walk through the following quickstart and tut | Quickstart/tutorial | Description | | ------------------- | ----------- | | [Workflow quickstart]({{< ref workflow-quickstart.md >}}) | Run a .NET workflow application with four workflow activities to see Dapr Workflow in action | -| [Workflow .NET SDK example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) | Learn how to create a Dapr Workflow and invoke it using ASP.NET Core web APIs. | | [Workflow Python SDK example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) | Learn how to create a Dapr Workflow and invoke it using the Python `DaprClient` package. | +| [Workflow .NET SDK example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) | Learn how to create a Dapr Workflow and invoke it using ASP.NET Core web APIs. | +| [Workflow Java SDK example](todo) | Learn how to create a Dapr Workflow and invoke it using the Java `need` package. | ### Start using workflows directly in your app diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index d139561cb..69d94afea 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -21,242 +21,9 @@ In this guide, you'll: -{{< tabs ".NET" "Python" >}} +{{< tabs "Python" ".NET" "Java" >}} - -{{% codetab %}} - -The `order-processor` console app starts and manages the lifecycle of an order processing workflow that stores and retrieves data in a state store. The workflow consists of four workflow activities, or tasks: -- `NotifyActivity`: Utilizes a logger to print out messages throughout the workflow -- `ReserveInventoryActivity`: Checks the state store to ensure that there is enough inventory for the purchase -- `ProcessPaymentActivity`: Processes and authorizes the payment -- `UpdateInventoryActivity`: Removes the requested items from the state store and updates the store with the new remaining inventory value - - -### Step 1: Pre-requisites - -For this example, you will need: - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- [.NET SDK or .NET 6 SDK installed](https://dotnet.microsoft.com/download). - -- [Docker Desktop](https://www.docker.com/products/docker-desktop) - - -### Step 2: Set up the environment - -Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/workflows). - -```bash -git clone https://github.com/dapr/quickstarts.git -``` - -In a new terminal window, navigate to the `order-processor` directory: - -```bash -cd workflows/csharp/sdk/order-processor -``` - -### Step 3: Run the order processor app - -In the terminal, start the order processor app alongside a Dapr sidecar: - -```bash -dapr run --app-id order-processor dotnet run -``` - -This starts the `order-processor` app with unique workflow ID and runs the workflow activities. - -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 -``` - -### (Optional) Step 4: View in Zipkin - -If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). - - - -### What happened? - -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. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received. -1. 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. Your workflow starts and notifies you of its status. -1. The `ProcessPaymentActivity` workflow activity begins processing payment for order `6d2abcc9` and confirms if successful. -1. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed. -1. The `NotifyActivity` workflow activity sends a notification saying that order `6d2abcc9` has completed. -1. The workflow terminates as completed. - -#### `order-processor/Program.cs` - -In the application's program file: -- The unique workflow order ID is generated -- The workflow is scheduled -- The workflow status is retrieved -- The workflow and the workflow activities it invokes are registered - -```csharp -using Dapr.Client; -using Dapr.Workflow; -//... - -{ - services.AddDaprWorkflow(options => - { - // Note that it's also possible to register a lambda function as the workflow - // or activity implementation instead of a class. - options.RegisterWorkflow(); - - // These are the activities that get invoked by the workflow(s). - options.RegisterActivity(); - options.RegisterActivity(); - options.RegisterActivity(); - options.RegisterActivity(); - }); -}; - -//... - -// Generate a unique ID for the workflow -string orderId = Guid.NewGuid().ToString()[..8]; -string itemToPurchase = "Cars"; -int ammountToPurchase = 10; - -// Construct the order -OrderPayload orderInfo = new OrderPayload(itemToPurchase, 15000, ammountToPurchase); - -// Start the workflow -Console.WriteLine("Starting workflow {0} purchasing {1} {2}", orderId, ammountToPurchase, itemToPurchase); - -await daprClient.StartWorkflowAsync( - workflowComponent: DaprWorkflowComponent, - workflowName: nameof(OrderProcessingWorkflow), - input: orderInfo, - instanceId: orderId); - -// Wait for the workflow to start and confirm the input -GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync( - instanceId: orderId, - workflowComponent: DaprWorkflowComponent); - -Console.WriteLine("Your workflow has started. Here is the status of the workflow: {0}", state.RuntimeStatus); - -// Wait for the workflow to complete -state = await daprClient.WaitForWorkflowCompletionAsync( - instanceId: orderId, - workflowComponent: DaprWorkflowComponent); - -Console.WriteLine("Workflow Status: {0}", state.RuntimeStatus); -``` - -#### `order-processor/Workflows/OrderProcessingWorkflow.cs` - -In `OrderProcessingWorkflow.cs`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). - -```csharp -using Dapr.Workflow; -//... - -class OrderProcessingWorkflow : Workflow - { - public override async Task RunAsync(WorkflowContext context, OrderPayload order) - { - string orderId = context.InstanceId; - - // Notify the user that an order has come through - await context.CallActivityAsync( - nameof(NotifyActivity), - new Notification($"Received order {orderId} for {order.Quantity} {order.Name} at ${order.TotalCost}")); - - string requestId = context.InstanceId; - - // Determine if there is enough of the item available for purchase by checking the inventory - InventoryResult result = await context.CallActivityAsync( - nameof(ReserveInventoryActivity), - new InventoryRequest(RequestId: orderId, order.Name, order.Quantity)); - - // If there is insufficient inventory, fail and let the user know - if (!result.Success) - { - // End the workflow here since we don't have sufficient inventory - await context.CallActivityAsync( - nameof(NotifyActivity), - new Notification($"Insufficient inventory for {order.Name}")); - return new OrderResult(Processed: false); - } - - // There is enough inventory available so the user can purchase the item(s). Process their payment - await context.CallActivityAsync( - nameof(ProcessPaymentActivity), - new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost)); - - try - { - // There is enough inventory available so the user can purchase the item(s). Process their payment - await context.CallActivityAsync( - nameof(UpdateInventoryActivity), - new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost)); - } - catch (TaskFailedException) - { - // Let them know their payment was processed - await context.CallActivityAsync( - nameof(NotifyActivity), - new Notification($"Order {orderId} Failed! You are now getting a refund")); - return new OrderResult(Processed: false); - } - - // Let them know their payment was processed - await context.CallActivityAsync( - nameof(NotifyActivity), - new Notification($"Order {orderId} has completed!")); - - // End the workflow with a success result - return new OrderResult(Processed: true); - } - } -``` - -#### `order-processor/Activities` directory - -The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files: -- `NotifyActivity.cs` -- `ReserveInventoryActivity.cs` -- `ProcessPaymentActivity.cs` -- `UpdateInventoryActivity.cs` - -{{% /codetab %}} - - + {{% codetab %}} The `order-processor` console app starts and manages the `order_processing_workflow`, which simulates purchasing items from a store. The workflow consists of five unique workflow activities, or tasks: @@ -494,6 +261,334 @@ In `workflow.py`, the workflow is defined as a class with all of its associated ``` {{% /codetab %}} + +{{% codetab %}} + +The `order-processor` console app starts and manages the lifecycle of an order processing workflow that stores and retrieves data in a state store. The workflow consists of four workflow activities, or tasks: +- `NotifyActivity`: Utilizes a logger to print out messages throughout the workflow +- `ReserveInventoryActivity`: Checks the state store to ensure that there is enough inventory for the purchase +- `ProcessPaymentActivity`: Processes and authorizes the payment +- `UpdateInventoryActivity`: Removes the requested items from the state store and updates the store with the new remaining inventory value + + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [.NET SDK or .NET 6 SDK installed](https://dotnet.microsoft.com/download). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/workflows). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +In a new terminal window, navigate to the `order-processor` directory: + +```bash +cd workflows/csharp/sdk/order-processor +``` + +### Step 3: Run the order processor app + +In the terminal, start the order processor app alongside a Dapr sidecar: + +```bash +dapr run --app-id order-processor dotnet run +``` + +This starts the `order-processor` app with unique workflow ID and runs the workflow activities. + +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 +``` + +### (Optional) Step 4: View in Zipkin + +If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). + + + +### What happened? + +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. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received. +1. 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. Your workflow starts and notifies you of its status. +1. The `ProcessPaymentActivity` workflow activity begins processing payment for order `6d2abcc9` and confirms if successful. +1. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed. +1. The `NotifyActivity` workflow activity sends a notification saying that order `6d2abcc9` has completed. +1. The workflow terminates as completed. + +#### `order-processor/Program.cs` + +In the application's program file: +- The unique workflow order ID is generated +- The workflow is scheduled +- The workflow status is retrieved +- The workflow and the workflow activities it invokes are registered + +```csharp +using Dapr.Client; +using Dapr.Workflow; +//... + +{ + services.AddDaprWorkflow(options => + { + // Note that it's also possible to register a lambda function as the workflow + // or activity implementation instead of a class. + options.RegisterWorkflow(); + + // These are the activities that get invoked by the workflow(s). + options.RegisterActivity(); + options.RegisterActivity(); + options.RegisterActivity(); + options.RegisterActivity(); + }); +}; + +//... + +// Generate a unique ID for the workflow +string orderId = Guid.NewGuid().ToString()[..8]; +string itemToPurchase = "Cars"; +int ammountToPurchase = 10; + +// Construct the order +OrderPayload orderInfo = new OrderPayload(itemToPurchase, 15000, ammountToPurchase); + +// Start the workflow +Console.WriteLine("Starting workflow {0} purchasing {1} {2}", orderId, ammountToPurchase, itemToPurchase); + +await daprClient.StartWorkflowAsync( + workflowComponent: DaprWorkflowComponent, + workflowName: nameof(OrderProcessingWorkflow), + input: orderInfo, + instanceId: orderId); + +// Wait for the workflow to start and confirm the input +GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync( + instanceId: orderId, + workflowComponent: DaprWorkflowComponent); + +Console.WriteLine("Your workflow has started. Here is the status of the workflow: {0}", state.RuntimeStatus); + +// Wait for the workflow to complete +state = await daprClient.WaitForWorkflowCompletionAsync( + instanceId: orderId, + workflowComponent: DaprWorkflowComponent); + +Console.WriteLine("Workflow Status: {0}", state.RuntimeStatus); +``` + +#### `order-processor/Workflows/OrderProcessingWorkflow.cs` + +In `OrderProcessingWorkflow.cs`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). + +```csharp +using Dapr.Workflow; +//... + +class OrderProcessingWorkflow : Workflow + { + public override async Task RunAsync(WorkflowContext context, OrderPayload order) + { + string orderId = context.InstanceId; + + // Notify the user that an order has come through + await context.CallActivityAsync( + nameof(NotifyActivity), + new Notification($"Received order {orderId} for {order.Quantity} {order.Name} at ${order.TotalCost}")); + + string requestId = context.InstanceId; + + // Determine if there is enough of the item available for purchase by checking the inventory + InventoryResult result = await context.CallActivityAsync( + nameof(ReserveInventoryActivity), + new InventoryRequest(RequestId: orderId, order.Name, order.Quantity)); + + // If there is insufficient inventory, fail and let the user know + if (!result.Success) + { + // End the workflow here since we don't have sufficient inventory + await context.CallActivityAsync( + nameof(NotifyActivity), + new Notification($"Insufficient inventory for {order.Name}")); + return new OrderResult(Processed: false); + } + + // There is enough inventory available so the user can purchase the item(s). Process their payment + await context.CallActivityAsync( + nameof(ProcessPaymentActivity), + new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost)); + + try + { + // There is enough inventory available so the user can purchase the item(s). Process their payment + await context.CallActivityAsync( + nameof(UpdateInventoryActivity), + new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost)); + } + catch (TaskFailedException) + { + // Let them know their payment was processed + await context.CallActivityAsync( + nameof(NotifyActivity), + new Notification($"Order {orderId} Failed! You are now getting a refund")); + return new OrderResult(Processed: false); + } + + // Let them know their payment was processed + await context.CallActivityAsync( + nameof(NotifyActivity), + new Notification($"Order {orderId} has completed!")); + + // End the workflow with a success result + return new OrderResult(Processed: true); + } + } +``` + +#### `order-processor/Activities` directory + +The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files: +- `NotifyActivity.cs` +- `ReserveInventoryActivity.cs` +- `ProcessPaymentActivity.cs` +- `UpdateInventoryActivity.cs` + +{{% /codetab %}} + + +{{% codetab %}} + +The `order-processor` console app starts and manages the lifecycle of an order processing workflow that stores and retrieves data in a state store. The workflow consists of four workflow activities, or tasks: +- + + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- Java JDK 11 (or greater): + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or + - OpenJDK +- [Apache Maven](https://maven.apache.org/install.html), version 3.x. + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/workflows). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +In a new terminal window, navigate to the `order-processor` directory: + +```bash +need +``` + +### Step 3: Run the order processor app + +In the terminal, start the order processor app alongside a Dapr sidecar: + +```bash +need +``` + +This starts the `order-processor` app with unique workflow ID and runs the workflow activities. + +Expected output: + +``` +need +``` + +### (Optional) Step 4: View in Zipkin + +If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). + + + +### What happened? + +When you ran `need`: + +1. A unique order ID for the workflow is generated (in the above example, `need`) and the workflow is scheduled. +1. The `need` workflow activity sends a notification saying an order for 10 cars has been received. +1. The `need` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. +1. Your workflow starts and notifies you of its status. +1. The `need` workflow activity begins processing payment for order `need` and confirms if successful. +1. The `need` workflow activity updates the inventory with the current available cars after the order has been processed. +1. The `need` workflow activity sends a notification saying that order `need` has completed. +1. The workflow terminates as completed. + +#### `order-processor/... need` + +In the application's program file: +- The unique workflow order ID is generated +- The workflow is scheduled +- The workflow status is retrieved +- The workflow and the workflow activities it invokes are registered + +```java +need +``` + +#### `order-processor/... need` + +In `need`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). + +```java +need +``` + +#### `order-processor/... need` directory + +The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files: +- `need` + +{{% /codetab %}} {{< /tabs >}}