From 0211dfef6e424d042695e5588ec828c63605c99e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 2 Aug 2023 14:56:59 -0400 Subject: [PATCH 01/19] scaffold out docs for workflow java sdk Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 253 +++++--- .../workflow/howto-manage-workflow.md | 89 +-- .../workflow/workflow-overview.md | 7 +- .../quickstarts/workflow-quickstart.md | 565 ++++++++++-------- 4 files changed, 544 insertions(+), 370 deletions(-) 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 >}} From 3fdf6d598bee302c2b64b04711a3bd8c36bb873e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 2 Aug 2023 15:09:07 -0400 Subject: [PATCH 02/19] add java tabs Signed-off-by: Hannah Hunter --- .../workflow/workflow-architecture.md | 5 +- .../workflow/workflow-patterns.md | 376 ++++++++++-------- 2 files changed, 213 insertions(+), 168 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md index da8bf0a44..9835725c4 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md @@ -189,4 +189,7 @@ See the [Reminder usage and execution guarantees section]({{< ref "workflow-arch - [Workflow overview]({{< ref workflow-overview.md >}}) - [Workflow API reference]({{< ref workflow_api.md >}}) - [Try out the Workflow quickstart]({{< ref workflow-quickstart.md >}}) -- [Try out the .NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) +- Try out the following examples: + - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java](todo) \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 4ff10782b..29048c683 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -25,42 +25,7 @@ While the pattern is simple, there are many complexities hidden in the implement Dapr Workflow solves these complexities by allowing you to implement the task chaining pattern concisely as a simple function in the programming language of your choice, as shown in the following example. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -// Expotential backoff retry policy that survives long outages -var retryOptions = new WorkflowTaskOptions -{ - RetryPolicy = new WorkflowRetryPolicy( - firstRetryInterval: TimeSpan.FromMinutes(1), - backoffCoefficient: 2.0, - maxRetryInterval: TimeSpan.FromHours(1), - maxNumberOfAttempts: 10), -}; - -try -{ - var result1 = await context.CallActivityAsync("Step1", wfInput, retryOptions); - var result2 = await context.CallActivityAsync("Step2", result1, retryOptions); - var result3 = await context.CallActivityAsync("Step3", result2, retryOptions); - return string.Join(", ", result4); -} -catch (TaskFailedException) // Task failures are surfaced as TaskFailedException -{ - // Retries expired - apply custom compensation logic - await context.CallActivityAsync("MyCompensation", options: retryOptions); - throw; -} -``` - -{{% alert title="Note" color="primary" %}} -In the example above, `"Step1"`, `"Step2"`, `"Step3"`, and `"MyCompensation"` represent workflow activities, which are functions in your code that actually implement the steps of the workflow. For brevity, these activity implementations are left out of this example. -{{% /alert %}} - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -103,9 +68,49 @@ def error_handler(ctx, error): # Do some compensating work ``` -{{% alert title="Note" color="primary" %}} -Workflow retry policies will be available in a future version of the Python SDK. -{{% /alert %}} +> **Note** Workflow retry policies will be available in a future version of the Python SDK. + +{{% /codetab %}} + +{{% codetab %}} + + +```csharp +// Expotential backoff retry policy that survives long outages +var retryOptions = new WorkflowTaskOptions +{ + RetryPolicy = new WorkflowRetryPolicy( + firstRetryInterval: TimeSpan.FromMinutes(1), + backoffCoefficient: 2.0, + maxRetryInterval: TimeSpan.FromHours(1), + maxNumberOfAttempts: 10), +}; + +try +{ + var result1 = await context.CallActivityAsync("Step1", wfInput, retryOptions); + var result2 = await context.CallActivityAsync("Step2", result1, retryOptions); + var result3 = await context.CallActivityAsync("Step3", result2, retryOptions); + return string.Join(", ", result4); +} +catch (TaskFailedException) // Task failures are surfaced as TaskFailedException +{ + // Retries expired - apply custom compensation logic + await context.CallActivityAsync("MyCompensation", options: retryOptions); + throw; +} +``` + +> **Note** In the example above, `"Step1"`, `"Step2"`, `"Step3"`, and `"MyCompensation"` represent workflow activities, which are functions in your code that actually implement the steps of the workflow. For brevity, these activity implementations are left out of this example. + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` {{% /codetab %}} @@ -135,32 +140,7 @@ In addition to the challenges mentioned in [the previous pattern]({{< ref "workf Dapr Workflows provides a way to express the fan-out/fan-in pattern as a simple function, as shown in the following example: -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -// Get a list of N work items to process in parallel. -object[] workBatch = await context.CallActivityAsync("GetWorkBatch", null); - -// Schedule the parallel tasks, but don't wait for them to complete yet. -var parallelTasks = new List>(workBatch.Length); -for (int i = 0; i < workBatch.Length; i++) -{ - Task task = context.CallActivityAsync("ProcessWorkItem", workBatch[i]); - parallelTasks.Add(task); -} - -// Everything is scheduled. Wait here until all parallel tasks have completed. -await Task.WhenAll(parallelTasks); - -// Aggregate all N outputs and publish the result. -int sum = parallelTasks.Sum(t => t.Result); -await context.CallActivityAsync("PostResults", sum); -``` - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -202,6 +182,40 @@ def process_results(ctx, final_result: int): {{% /codetab %}} +{{% codetab %}} + + +```csharp +// Get a list of N work items to process in parallel. +object[] workBatch = await context.CallActivityAsync("GetWorkBatch", null); + +// Schedule the parallel tasks, but don't wait for them to complete yet. +var parallelTasks = new List>(workBatch.Length); +for (int i = 0; i < workBatch.Length; i++) +{ + Task task = context.CallActivityAsync("ProcessWorkItem", workBatch[i]); + parallelTasks.Add(task); +} + +// Everything is scheduled. Wait here until all parallel tasks have completed. +await Task.WhenAll(parallelTasks); + +// Aggregate all N outputs and publish the result. +int sum = parallelTasks.Sum(t => t.Result); +await context.CallActivityAsync("PostResults", sum); +``` + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` + +{{% /codetab %}} + {{< /tabs >}} The key takeaways from this example are: @@ -302,48 +316,7 @@ Depending on the business needs, there may be a single monitor or there may be m Dapr Workflow supports this pattern natively by allowing you to implement _eternal workflows_. Rather than writing infinite while-loops ([which is an anti-pattern]({{< ref "workflow-features-concepts.md#infinite-loops-and-eternal-workflows" >}})), Dapr Workflow exposes a _continue-as-new_ API that workflow authors can use to restart a workflow function from the beginning with a new input. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -public override async Task RunAsync(WorkflowContext context, MyEntityState myEntityState) -{ - TimeSpan nextSleepInterval; - - var status = await context.CallActivityAsync("GetStatus"); - if (status == "healthy") - { - myEntityState.IsHealthy = true; - - // Check less frequently when in a healthy state - nextSleepInterval = TimeSpan.FromMinutes(60); - } - else - { - if (myEntityState.IsHealthy) - { - myEntityState.IsHealthy = false; - await context.CallActivityAsync("SendAlert", myEntityState); - } - - // Check more frequently when in an unhealthy state - nextSleepInterval = TimeSpan.FromMinutes(5); - } - - // Put the workflow to sleep until the determined time - await context.CreateTimer(nextSleepInterval); - - // Restart from the beginning with the updated state - context.ContinueAsNew(myEntityState); - return null; -} -``` - -> This example assumes you have a predefined `MyEntityState` class with a boolean `IsHealthy` property. - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -392,6 +365,56 @@ def send_alert(ctx, message: str): {{% /codetab %}} +{{% codetab %}} + + +```csharp +public override async Task RunAsync(WorkflowContext context, MyEntityState myEntityState) +{ + TimeSpan nextSleepInterval; + + var status = await context.CallActivityAsync("GetStatus"); + if (status == "healthy") + { + myEntityState.IsHealthy = true; + + // Check less frequently when in a healthy state + nextSleepInterval = TimeSpan.FromMinutes(60); + } + else + { + if (myEntityState.IsHealthy) + { + myEntityState.IsHealthy = false; + await context.CallActivityAsync("SendAlert", myEntityState); + } + + // Check more frequently when in an unhealthy state + nextSleepInterval = TimeSpan.FromMinutes(5); + } + + // Put the workflow to sleep until the determined time + await context.CreateTimer(nextSleepInterval); + + // Restart from the beginning with the updated state + context.ContinueAsNew(myEntityState); + return null; +} +``` + +> This example assumes you have a predefined `MyEntityState` class with a boolean `IsHealthy` property. + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` + +{{% /codetab %}} + {{< /tabs >}} A workflow implementing the monitor pattern can loop forever or it can terminate itself gracefully by not calling _continue-as-new_. @@ -420,53 +443,7 @@ The following diagram illustrates this flow. The following example code shows how this pattern can be implemented using Dapr Workflow. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -public override async Task RunAsync(WorkflowContext context, OrderPayload order) -{ - // ...(other steps)... - - // Require orders over a certain threshold to be approved - if (order.TotalCost > OrderApprovalThreshold) - { - try - { - // Request human approval for this order - await context.CallActivityAsync(nameof(RequestApprovalActivity), order); - - // Pause and wait for a human to approve the order - ApprovalResult approvalResult = await context.WaitForExternalEventAsync( - eventName: "ManagerApproval", - timeout: TimeSpan.FromDays(3)); - if (approvalResult == ApprovalResult.Rejected) - { - // The order was rejected, end the workflow here - return new OrderResult(Processed: false); - } - } - catch (TaskCanceledException) - { - // An approval timeout results in automatic order cancellation - return new OrderResult(Processed: false); - } - } - - // ...(other steps)... - - // End the workflow with a success result - return new OrderResult(Processed: true); -} -``` - -{{% alert title="Note" color="primary" %}} -In the example above, `RequestApprovalActivity` is the name of a workflow activity to invoke and `ApprovalResult` is an enumeration defined by the workflow app. For brevity, these definitions were left out of the example code. -{{% /alert %}} - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -527,26 +504,65 @@ def place_order(_, order: Order) -> None: {{% /codetab %}} -{{< /tabs >}} - -The code that delivers the event to resume the workflow execution is external to the workflow. Workflow events can be delivered to a waiting workflow instance using the [raise event]({{< ref "howto-manage-workflow.md#raise-an-event" >}}) workflow management API, as shown in the following example: - -{{< tabs ".NET" Python >}} - {{% codetab %}} ```csharp -// Raise the workflow event to the waiting workflow -await daprClient.RaiseWorkflowEventAsync( - instanceId: orderId, - workflowComponent: "dapr", - eventName: "ManagerApproval", - eventData: ApprovalResult.Approved); +public override async Task RunAsync(WorkflowContext context, OrderPayload order) +{ + // ...(other steps)... + + // Require orders over a certain threshold to be approved + if (order.TotalCost > OrderApprovalThreshold) + { + try + { + // Request human approval for this order + await context.CallActivityAsync(nameof(RequestApprovalActivity), order); + + // Pause and wait for a human to approve the order + ApprovalResult approvalResult = await context.WaitForExternalEventAsync( + eventName: "ManagerApproval", + timeout: TimeSpan.FromDays(3)); + if (approvalResult == ApprovalResult.Rejected) + { + // The order was rejected, end the workflow here + return new OrderResult(Processed: false); + } + } + catch (TaskCanceledException) + { + // An approval timeout results in automatic order cancellation + return new OrderResult(Processed: false); + } + } + + // ...(other steps)... + + // End the workflow with a success result + return new OrderResult(Processed: true); +} +``` + +> **Note** In the example above, `RequestApprovalActivity` is the name of a workflow activity to invoke and `ApprovalResult` is an enumeration defined by the workflow app. For brevity, these definitions were left out of the example code. + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo ``` {{% /codetab %}} +{{< /tabs >}} + +The code that delivers the event to resume the workflow execution is external to the workflow. Workflow events can be delivered to a waiting workflow instance using the [raise event]({{< ref "howto-manage-workflow.md#raise-an-event" >}}) workflow management API, as shown in the following example: + +{{< tabs Python ".NET" Java >}} + {{% codetab %}} @@ -564,6 +580,29 @@ with DaprClient() as d: {{% /codetab %}} +{{% codetab %}} + + +```csharp +// Raise the workflow event to the waiting workflow +await daprClient.RaiseWorkflowEventAsync( + instanceId: orderId, + workflowComponent: "dapr", + eventName: "ManagerApproval", + eventData: ApprovalResult.Approved); +``` + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` + +{{% /codetab %}} + {{< /tabs >}} External events don't have to be directly triggered by humans. They can also be triggered by other systems. For example, a workflow may need to pause and wait for a payment to be received. In this case, a payment system might publish an event to a pub/sub topic on receipt of a payment, and a listener on that topic can raise an event to the workflow using the raise event workflow API. @@ -577,4 +616,7 @@ External events don't have to be directly triggered by humans. They can also be - [Try out Dapr Workflows using the quickstart]({{< ref workflow-quickstart.md >}}) - [Workflow overview]({{< ref workflow-overview.md >}}) - [Workflow API reference]({{< ref workflow_api.md >}}) -- [Try out the .NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) +- Try out the following examples: + - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java](todo) \ No newline at end of file From 51a97d8000ff9f16cc4df0876736a0eea84f0293 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 14:17:00 -0400 Subject: [PATCH 03/19] start adding content for java how to Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) 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 3ca68fde5..eaf16c8cf 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 @@ -122,33 +122,36 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. - -The activities called in the example below are: -- `need`: Receive notification of a new order. - -### [activity] +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an `ActivityWrapper` class for the `TaskActivityFactory`. In the following example, the activity wrapper and its constructor are defined: ```java -todo +public class ActivityWrapper implements TaskActivityFactory { + private final Constructor activityConstructor; + private final String name; + + public ActivityWrapper(Class clazz) { + this.name = clazz.getCanonicalName(); + try { + this.activityConstructor = clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + String.format("No constructor found for activity class '%s'.", this.name), e + ); + } + } ``` -[See the full `todo` workflow activity example.](todo) - -### [activity] +Register a workflow activity object: ```java -todo -``` -[See the full `todo` workflow activity example.](todo) - -### [todo] - -```java -todo + public void registerActivity(Class clazz) { + this.builder = this.builder.addActivity( + new ActivityWrapper<>(clazz) + ); + } ``` -[See the full `todo` workflow activity example.](todo) +[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows) {{% /codetab %}} From d030a7bf4d1d9da521a76a0e9d97e7adcb49ced8 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 17:25:32 -0400 Subject: [PATCH 04/19] continue adding Java Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 168 +++++++++++++++--- 1 file changed, 144 insertions(+), 24 deletions(-) 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 eaf16c8cf..7c90990fa 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 @@ -122,36 +122,27 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an `ActivityWrapper` class for the `TaskActivityFactory`. In the following example, the activity wrapper and its constructor are defined: +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an wrapper class, which is called `OrchestrationWrapper` in the following exmaple. ```java -public class ActivityWrapper implements TaskActivityFactory { - private final Constructor activityConstructor; +class OrchestratorWrapper implements TaskOrchestrationFactory { + private final Constructor workflowConstructor; private final String name; - public ActivityWrapper(Class clazz) { + public OrchestratorWrapper(Class clazz) { this.name = clazz.getCanonicalName(); try { - this.activityConstructor = clazz.getDeclaredConstructor(); + this.workflowConstructor = clazz.getDeclaredConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException( - String.format("No constructor found for activity class '%s'.", this.name), e + String.format("No constructor found for workflow class '%s'.", this.name), e ); } } +} ``` -Register a workflow activity object: - -```java - public void registerActivity(Class clazz) { - this.builder = this.builder.addActivity( - new ActivityWrapper<>(clazz) - ); - } -``` - -[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows) +[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java) {{% /codetab %}} @@ -232,13 +223,19 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo -Intro - +The `WorkflowActivity` interface executes the activity logic and returns a value to be serialized and returned to the caller. The following code registers a workflow activity object: + ```java -todo +public WorkflowRuntimeBuilder registerWorkflow(Class clazz) { + this.builder = this.builder.addOrchestration( + new OrchestratorWrapper<>(clazz) + ); + + return this; +} ``` -[See the `todo` workflow in context.](todo) +[See the Java SDK workflow in context.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java) {{% /codetab %}} @@ -415,12 +412,135 @@ app.Run(); -[In the following example](todo), for a basic Java hello world application using the Java SDK, your project code would include: +[In the following example](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java), for a basic Java application using workflows for Java SDK, your project code would include: -- A Java package called `todo` to receive the Java SDK capabilities. +- A Java package called `io.dapr.workflows.client` to receive the Java SDK client capabilities. +- An import of `io.dapr.workflows.Workflow` +- A wrapper extending `Workflow` and implementing tasks/activities +- A declared `WorkflowRuntimeBuilder` builder +- API calls. In the example below, these calls start and terminate the workflow. ```java -todo +package io.dapr.workflows.client; + +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; +import io.dapr.utils.NetworkUtils; +import io.dapr.workflows.Workflow; +import io.grpc.ManagedChannel; + +import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; + +public class DaprWorkflowClient implements AutoCloseable { + + private DurableTaskClient innerClient; + private ManagedChannel grpcChannel; + + /** + * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. + */ + public DaprWorkflowClient() { + this(NetworkUtils.buildGrpcManagedChannel()); + } + + /** + * Private Constructor that passes a created DurableTaskClient and the new GRPC channel. + * + * @param grpcChannel ManagedChannel for GRPC channel. + */ + private DaprWorkflowClient(ManagedChannel grpcChannel) { + this(createDurableTaskClient(grpcChannel), grpcChannel); + } + + /** + * Private Constructor for DaprWorkflowClient. + * + * @param innerClient DurableTaskGrpcClient with GRPC Channel set up. + * @param grpcChannel ManagedChannel for instance variable setting. + * + */ + private DaprWorkflowClient(DurableTaskClient innerClient, ManagedChannel grpcChannel) { + this.innerClient = innerClient; + this.grpcChannel = grpcChannel; + } + + /** + * Static method to create the DurableTaskClient. + * + * @param grpcChannel ManagedChannel for GRPC. + * @return a new instance of a DurableTaskClient with a GRPC channel. + */ + private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChannel) { + return new DurableTaskGrpcClientBuilder() + .grpcChannel(grpcChannel) + .build(); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. + * @return A String with the randomly-generated instance ID for new Workflow instance. + */ + public String scheduleNewWorkflow(Class clazz) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. + * @param input the input to pass to the scheduled orchestration instance. Must be serializable. + * @return A String with the randomly-generated instance ID for new Workflow instance. + */ + public String scheduleNewWorkflow(Class clazz, Object input) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. + * @param input the input to pass to the scheduled orchestration instance. Must be serializable. + * @param instanceId the unique ID of the orchestration instance to schedule + * @return A String with the instanceId parameter value. + */ + public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); + } + + /** + * Terminates the workflow associated with the provided instance id. + * + * @param workflowInstanceId Workflow instance id to terminate. + * @param output the optional output to set for the terminated orchestration instance. + */ + public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { + this.innerClient.terminate(workflowInstanceId, output); + } + + /** + * Closes the inner DurableTask client and shutdown the GRPC channel. + * + */ + public void close() throws InterruptedException { + try { + if (this.innerClient != null) { + this.innerClient.close(); + this.innerClient = null; + } + } finally { + if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { + this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + this.grpcChannel = null; + } + } + } +} ``` {{% /codetab %}} From 448d0de43e603948aaea8eaa58c4a4cab0fdfa47 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 18:03:42 -0400 Subject: [PATCH 05/19] add activities Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/howto-manage-workflow.md | 8 ++++++++ 1 file changed, 8 insertions(+) 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 27d5d75cb..0bc194302 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 @@ -100,6 +100,14 @@ await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); 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: +- **raiseEvent**: Raise an event on a workflow +- **getInstanceMetadata**: Get information on the status of the workflow +- **waitForInstanceStart**: Pauses or suspends a workflow instance that can later be resumed +- **waitForInstanceCompletion**: Resumes a paused workflow instance and waits for completion +- **createTaskHub** +- **deleteTaskHub** +- **purgeInstance**: Removes all metadata related to a specific workflow + ```java todo ``` From 7a1b51b7a3252f270b3928d4977e34444c308b39 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 13 Sep 2023 17:43:02 -0400 Subject: [PATCH 06/19] add ref Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/howto-author-workflow.md | 4 ++++ .../building-blocks/workflow/howto-manage-workflow.md | 4 ++++ .../building-blocks/workflow/workflow-architecture.md | 4 ++++ .../workflow/workflow-features-concepts.md | 4 ++++ .../building-blocks/workflow/workflow-overview.md | 8 +++++++- .../en/getting-started/quickstarts/workflow-quickstart.md | 2 +- daprdocs/content/en/reference/api/workflow_api.md | 4 ++++ 7 files changed, 28 insertions(+), 2 deletions(-) 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 7c90990fa..b1a44ed25 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 @@ -6,6 +6,10 @@ weight: 5000 description: "Learn how to develop and author workflows" --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + This article provides a high-level overview of how to author workflows that are executed by the Dapr Workflow engine. {{% alert title="Note" color="primary" %}} 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 0bc194302..1b242f281 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 @@ -6,6 +6,10 @@ weight: 6000 description: Manage and run workflows --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + 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 Python ".NET" Java HTTP >}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md index 9835725c4..1322ea94f 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md @@ -6,6 +6,10 @@ weight: 4000 description: "The Dapr Workflow engine architecture" --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + [Dapr Workflows]({{< ref "workflow-overview.md" >}}) allow developers to define workflows using ordinary code in a variety of programming languages. The workflow engine runs inside of the Dapr sidecar and orchestrates workflow code deployed as part of your application. This article describes: - The architecture of the Dapr Workflow engine diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md index c8e3de131..e957ade3d 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md @@ -6,6 +6,10 @@ weight: 2000 description: "Learn more about the Dapr Workflow features and concepts" --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + Now that you've learned about the [workflow building block]({{< ref workflow-overview.md >}}) at a high level, let's deep dive into the features and concepts included with the Dapr Workflow engine and SDKs. Dapr Workflow exposes several core features and concepts which are common across all supported languages. {{% alert title="Note" color="primary" %}} 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 998e0cccc..cf7326497 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 @@ -7,7 +7,7 @@ description: "Overview of Dapr Workflow" --- {{% alert title="Note" color="primary" %}} -Dapr Workflow is currently in alpha. +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "#limitations" >}}). {{% /alert %}} Dapr workflow makes it easy for developers to write business logic and integrations in a reliable way. Since Dapr workflows are stateful, they support long-running and fault-tolerant applications, ideal for orchestrating microservices. Dapr workflow works seamlessly with other Dapr building blocks, such as service invocation, pub/sub, state management, and bindings. @@ -105,6 +105,12 @@ Want to put workflows to the test? Walk through the following quickstart and tut Want to skip the quickstarts? Not a problem. You can try out the workflow building block directly in your application. After [Dapr is installed]({{< ref install-dapr-cli.md >}}), you can begin using workflows, starting with [how to author a workflow]({{< ref howto-author-workflow.md >}}). +## Limitations + +With Dapr Workflow in beta stage comes the following limitation(s): + +- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported. NoSQL database support is planned for future minor releases. + ## Watch the demo Watch [this video for an overview on Dapr Workflow](https://youtu.be/s1p9MNl4VGo?t=131): diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index 69d94afea..fba93d75d 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -7,7 +7,7 @@ description: Get started with the Dapr Workflow building block --- {{% alert title="Note" color="primary" %}} -The workflow building block is currently in **alpha**. +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). {{% /alert %}} Let's take a look at the Dapr [Workflow building block]({{< ref workflow >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. diff --git a/daprdocs/content/en/reference/api/workflow_api.md b/daprdocs/content/en/reference/api/workflow_api.md index 80814852e..7b74de78a 100644 --- a/daprdocs/content/en/reference/api/workflow_api.md +++ b/daprdocs/content/en/reference/api/workflow_api.md @@ -6,6 +6,10 @@ description: "Detailed documentation on the workflow API" weight: 900 --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + Dapr provides users with the ability to interact with workflows and comes with a built-in `dapr` component. ## Start workflow request From e9e0e75d9d5e7f6d5cc18fdf1d0691942797100e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 14 Sep 2023 09:57:18 -0400 Subject: [PATCH 07/19] update to beta1 Signed-off-by: Hannah Hunter --- .../content/en/concepts/building-blocks-concept.md | 2 +- .../workflow/howto-manage-workflow.md | 14 +++++++------- .../building-blocks/workflow/workflow-patterns.md | 4 ++-- .../content/en/operations/support/alpha-apis.md | 2 +- daprdocs/content/en/reference/api/workflow_api.md | 14 +++++++------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/daprdocs/content/en/concepts/building-blocks-concept.md b/daprdocs/content/en/concepts/building-blocks-concept.md index 4719626f3..1841dd584 100644 --- a/daprdocs/content/en/concepts/building-blocks-concept.md +++ b/daprdocs/content/en/concepts/building-blocks-concept.md @@ -28,5 +28,5 @@ Dapr provides the following building blocks: | [**Secrets**]({{< ref "secrets-overview.md" >}}) | `/v1.0/secrets` | Dapr provides a secrets building block API and integrates with secret stores such as public cloud stores, local stores and Kubernetes to store the secrets. Services can call the secrets API to retrieve secrets, for example to get a connection string to a database. | [**Configuration**]({{< ref "configuration-api-overview.md" >}}) | `/v1.0/configuration` | The Configuration API enables you to retrieve and subscribe to application configuration items for supported configuration stores. This enables an application to retrieve specific configuration information, for example, at start up or when configuration changes are made in the store. | [**Distributed lock**]({{< ref "distributed-lock-api-overview.md" >}}) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource so that multiple instances of an application can access the resource without conflicts and provide consistency guarantees. -| [**Workflows**]({{< ref "workflow-overview.md" >}}) | `/v1.0-alpha1/workflow` | The Workflow API enables you to define long running, persistent processes or data flows that span multiple microservices using Dapr workflows or workflow components. The Workflow API can be combined with other Dapr API building blocks. For example, a workflow can call another service with service invocation or retrieve secrets, providing flexibility and portability. +| [**Workflows**]({{< ref "workflow-overview.md" >}}) | `/v1.0-beta1/workflow` | The Workflow API enables you to define long running, persistent processes or data flows that span multiple microservices using Dapr workflows or workflow components. The Workflow API can be combined with other Dapr API building blocks. For example, a workflow can call another service with service invocation or retrieve secrets, providing flexibility and portability. | [**Cryptography**]({{< ref "cryptography-overview.md" >}}) | `/v1.0-alpha1/crypto` | The Cryptography API enables you to perform cryptographic operations, such as encrypting and decrypting messages, without exposing keys to your application. \ No newline at end of file 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 1b242f281..e4827ae74 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 @@ -129,7 +129,7 @@ Manage your workflow using HTTP calls. The example below plugs in the properties To start your workflow with an ID `12345678`, run: ```http -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 +POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 ``` Note that workflow instance IDs can only contain alphanumeric characters, underscores, and dashes. @@ -139,7 +139,7 @@ Note that workflow instance IDs can only contain alphanumeric characters, unders To terminate your workflow with an ID `12345678`, run: ```http -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/terminate +POST http://localhost:3500/v1.0-beta1/workflows/dapr/12345678/terminate ``` ### Raise an event @@ -147,7 +147,7 @@ POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/terminate For workflow components that support subscribing to external events, such as the Dapr Workflow engine, you can use the following "raise event" API to deliver a named event to a specific workflow instance. ```http -POST http://localhost:3500/v1.0-alpha1/workflows///raiseEvent/ +POST http://localhost:3500/v1.0-beta1/workflows///raiseEvent/ ``` > An `eventName` can be any function. @@ -157,13 +157,13 @@ POST http://localhost:3500/v1.0-alpha1/workflows//}}). diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 29048c683..5460d14c2 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -247,7 +247,7 @@ The Dapr workflow HTTP API supports the asynchronous request-reply pattern out-o The following `curl` commands illustrate how the workflow APIs support this pattern. ```bash -curl -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 -d '{"Name":"Paperclips","Quantity":1,"TotalCost":9.95}' +curl -X POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 -d '{"Name":"Paperclips","Quantity":1,"TotalCost":9.95}' ``` The previous command will result in the following response JSON: @@ -259,7 +259,7 @@ The previous command will result in the following response JSON: The HTTP client can then construct the status query URL using the workflow instance ID and poll it repeatedly until it sees the "COMPLETE", "FAILURE", or "TERMINATED" status in the payload. ```bash -curl http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678 +curl http://localhost:3500/v1.0-beta1/workflows/dapr/12345678 ``` The following is an example of what an in-progress workflow status might look like. diff --git a/daprdocs/content/en/operations/support/alpha-apis.md b/daprdocs/content/en/operations/support/alpha-apis.md index 3e2e26354..9dcc81ab1 100644 --- a/daprdocs/content/en/operations/support/alpha-apis.md +++ b/daprdocs/content/en/operations/support/alpha-apis.md @@ -10,7 +10,7 @@ description: "List of current alpha APIs" | ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | | Query State | [Query State proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L44) | `v1.0-alpha1/state/statestore/query` | The state query API enables you to retrieve, filter, and sort the key/value data stored in state store components. | [Query State API]({{< ref "howto-state-query-api.md" >}}) | v1.5 | | Distributed Lock | [Lock proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L112) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource. | [Distributed Lock API]({{< ref "distributed-lock-api-overview.md" >}}) | v1.8 | -| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-alpha1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | +| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | | Bulk Publish | [Bulk publish proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L59) | `v1.0-alpha1/publish/bulk` | The bulk publish API allows you to publish multiple messages to a topic in a single request. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Bulk Subscribe | [Bulk subscribe proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/appcallback.proto#L57) | N/A | The bulk subscribe application callback receives multiple messages from a topic in a single call. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Cryptography | [Crypto proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L118) | `v1.0-alpha1/crypto` | The cryptography API enables you to perform **high level** cryptography operations for encrypting and decrypting messages. | [Cryptography API]({{< ref "cryptography-overview.md" >}}) | v1.11 | diff --git a/daprdocs/content/en/reference/api/workflow_api.md b/daprdocs/content/en/reference/api/workflow_api.md index 7b74de78a..a2c0d3b1b 100644 --- a/daprdocs/content/en/reference/api/workflow_api.md +++ b/daprdocs/content/en/reference/api/workflow_api.md @@ -17,7 +17,7 @@ Dapr provides users with the ability to interact with workflows and comes with a Start a workflow instance with the given name and optionally, an instance ID. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///start[?instanceID=] +POST http://localhost:3500/v1.0-beta1/workflows///start[?instanceID=] ``` Note that workflow instance IDs can only contain alphanumeric characters, underscores, and dashes. @@ -57,7 +57,7 @@ The API call will provide a response similar to this: Terminate a running workflow instance with the given name and instance ID. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///terminate +POST http://localhost:3500/v1.0-beta1/workflow///terminate ``` ### URL parameters @@ -84,7 +84,7 @@ This API does not return any content. For workflow components that support subscribing to external events, such as the Dapr Workflow engine, you can use the following "raise event" API to deliver a named event to a specific workflow instance. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///raiseEvent/ +POST http://localhost:3500/v1.0-beta1/workflows///raiseEvent/ ``` {{% alert title="Note" color="primary" %}} @@ -117,7 +117,7 @@ None. Pause a running workflow instance. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///pause +POST http://localhost:3500/v1.0-beta1/workflows///pause ``` ### URL parameters @@ -144,7 +144,7 @@ None. Resume a paused workflow instance. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///resume +POST http://localhost:3500/v1.0-beta1/workflow///resume ``` ### URL parameters @@ -171,7 +171,7 @@ None. Purge the workflow state from your state store with the workflow's instance ID. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///purge +POST http://localhost:3500/v1.0-beta1/workflows///purge ``` ### URL parameters @@ -198,7 +198,7 @@ None. Get information about a given workflow instance. ``` -GET http://localhost:3500/v1.0-alpha1/workflows// +GET http://localhost:3500/v1.0-beta1/workflows// ``` ### URL parameters From 7ff09b565cd2dd96ca0e2e112135729966c69616 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 14 Sep 2023 10:25:00 -0400 Subject: [PATCH 08/19] add beta apis and move workflow to that table Signed-off-by: Hannah Hunter --- .../{alpha-apis.md => alpha-beta-apis.md} | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) rename daprdocs/content/en/operations/support/{alpha-apis.md => alpha-beta-apis.md} (87%) diff --git a/daprdocs/content/en/operations/support/alpha-apis.md b/daprdocs/content/en/operations/support/alpha-beta-apis.md similarity index 87% rename from daprdocs/content/en/operations/support/alpha-apis.md rename to daprdocs/content/en/operations/support/alpha-beta-apis.md index 9dcc81ab1..0f028dfd0 100644 --- a/daprdocs/content/en/operations/support/alpha-apis.md +++ b/daprdocs/content/en/operations/support/alpha-beta-apis.md @@ -1,16 +1,24 @@ --- type: docs -title: "Alpha APIs" -linkTitle: "Alpha APIs" +title: "Alpha and Beta APIs" +linkTitle: "Alpha & Beta APIs" weight: 5000 -description: "List of current alpha APIs" +description: "List of current alpha and beta APIs" --- +## Alpha APIs + | Building block/API | gRPC | HTTP | Description | Documentation | Version introduced | | ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | | Query State | [Query State proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L44) | `v1.0-alpha1/state/statestore/query` | The state query API enables you to retrieve, filter, and sort the key/value data stored in state store components. | [Query State API]({{< ref "howto-state-query-api.md" >}}) | v1.5 | | Distributed Lock | [Lock proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L112) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource. | [Distributed Lock API]({{< ref "distributed-lock-api-overview.md" >}}) | v1.8 | -| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | | Bulk Publish | [Bulk publish proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L59) | `v1.0-alpha1/publish/bulk` | The bulk publish API allows you to publish multiple messages to a topic in a single request. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Bulk Subscribe | [Bulk subscribe proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/appcallback.proto#L57) | N/A | The bulk subscribe application callback receives multiple messages from a topic in a single call. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Cryptography | [Crypto proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L118) | `v1.0-alpha1/crypto` | The cryptography API enables you to perform **high level** cryptography operations for encrypting and decrypting messages. | [Cryptography API]({{< ref "cryptography-overview.md" >}}) | v1.11 | + +## Beta APIs + +| Building block/API | gRPC | HTTP | Description | Documentation | Version introduced | +| ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | +| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | + From adbe4f75030886230f8ba00bd2fc278926d6e67e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 14 Sep 2023 15:09:16 -0400 Subject: [PATCH 09/19] link to lifecycles Signed-off-by: Hannah Hunter --- daprdocs/content/en/operations/support/alpha-beta-apis.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daprdocs/content/en/operations/support/alpha-beta-apis.md b/daprdocs/content/en/operations/support/alpha-beta-apis.md index 0f028dfd0..c35d4fc42 100644 --- a/daprdocs/content/en/operations/support/alpha-beta-apis.md +++ b/daprdocs/content/en/operations/support/alpha-beta-apis.md @@ -22,3 +22,6 @@ description: "List of current alpha and beta APIs" | ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | | Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | +## Related links + +[Learn more about the Alpha, Beta, and Stable lifecycle stages.]({{< ref "certification-lifecycle.md#certification-levels" >}}) \ No newline at end of file From f261ac56718dac0755eb26eabf2678018d0303c2 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 19 Sep 2023 09:47:38 -0400 Subject: [PATCH 10/19] updates from Ryan Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/howto-author-workflow.md | 2 +- .../building-blocks/workflow/howto-manage-workflow.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 b1a44ed25..52be5dba1 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 @@ -126,7 +126,7 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an wrapper class, which is called `OrchestrationWrapper` in the following exmaple. +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in a wrapper class, which is called `OrchestrationWrapper` in the following exmaple. ```java class OrchestratorWrapper implements TaskOrchestrationFactory { 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 e4827ae74..2491d9934 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 @@ -79,7 +79,7 @@ Dictionary workflowOptions; // This is an optional parameter 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); +GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, eventName); // Terminate the workflow await daprClient.TerminateWorkflowAsync(orderId, workflowComponent); @@ -93,7 +93,7 @@ await daprClient.PauseWorkflowAsync(orderId, workflowComponent); // Resume await daprClient.ResumeWorkflowAsync(orderId, workflowComponent); -// Purge +// Purge the workflow, removing all inbox and history information from associated instance await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); ``` From 52ed2b667b3b3d15426de6836b22177d1ebc683e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 12:22:09 -0400 Subject: [PATCH 11/19] update examples in how tos Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 211 ++++++------------ .../workflow/howto-manage-workflow.md | 62 ++++- 2 files changed, 127 insertions(+), 146 deletions(-) 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 52be5dba1..ada6335ec 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 @@ -126,27 +126,42 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. Activities are wrapped in a wrapper class, which is called `OrchestrationWrapper` in the following exmaple. +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in the public `DemoWorkflowActivity` class, which implements the workflow activities. ```java -class OrchestratorWrapper implements TaskOrchestrationFactory { - private final Constructor workflowConstructor; - private final String name; +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +public class DemoWorkflowActivity implements WorkflowActivity { + + @Override + public DemoActivityOutput run(WorkflowActivityContext ctx) { + Logger logger = LoggerFactory.getLogger(DemoWorkflowActivity.class); + logger.info("Starting Activity: " + ctx.getName()); + + var message = ctx.getInput(DemoActivityInput.class).getMessage(); + var newMessage = message + " World!, from Activity"; + logger.info("Message Received from input: " + message); + logger.info("Sending message to output: " + newMessage); + + logger.info("Sleeping for 5 seconds to simulate long running operation..."); - public OrchestratorWrapper(Class clazz) { - this.name = clazz.getCanonicalName(); try { - this.workflowConstructor = clazz.getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new RuntimeException( - String.format("No constructor found for workflow class '%s'.", this.name), e - ); + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + throw new RuntimeException(e); } + + + logger.info("Activity finished"); + + var output = new DemoActivityOutput(message, newMessage); + logger.info("Activity returned: " + output); + + return output; } } ``` -[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java) +[See the Java SDK workflow activity example in context.](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowActivity.java) {{% /codetab %}} @@ -227,19 +242,29 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo -The `WorkflowActivity` interface executes the activity logic and returns a value to be serialized and returned to the caller. The following code registers a workflow activity object: +Next, register the workflow with the `WorkflowRuntimeBuilder` and start the workflow runtime. ```java -public WorkflowRuntimeBuilder registerWorkflow(Class clazz) { - this.builder = this.builder.addOrchestration( - new OrchestratorWrapper<>(clazz) - ); +public class DemoWorkflowWorker { - return this; + public static void main(String[] args) throws Exception { + + // Register the Workflow with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoWorkflow.class); + builder.registerActivity(DemoWorkflowActivity.class); + + // Build and then start the workflow runtime pulling and executing tasks + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start(); + } + + System.exit(0); + } } ``` -[See the Java SDK workflow in context.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java) +[See the Java SDK workflow in context.](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java) {{% /codetab %}} @@ -416,137 +441,47 @@ app.Run(); -[In the following example](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java), for a basic Java application using workflows for Java SDK, your project code would include: +[As in the following example](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java), a hello-world application using the Java SDK and Dapr Workflow would include: - A Java package called `io.dapr.workflows.client` to receive the Java SDK client capabilities. - An import of `io.dapr.workflows.Workflow` -- A wrapper extending `Workflow` and implementing tasks/activities -- A declared `WorkflowRuntimeBuilder` builder -- API calls. In the example below, these calls start and terminate the workflow. +- The `DemoWorkflow` class which extends `Workflow` +- Creating the workflow with input and output. +- API calls. In the example below, these calls start and call the workflow activities. ```java -package io.dapr.workflows.client; +package io.dapr.examples.workflows; -import com.microsoft.durabletask.DurableTaskClient; -import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; -import io.dapr.utils.NetworkUtils; +import com.microsoft.durabletask.CompositeTaskFailedException; +import com.microsoft.durabletask.Task; +import com.microsoft.durabletask.TaskCanceledException; import io.dapr.workflows.Workflow; -import io.grpc.ManagedChannel; +import io.dapr.workflows.WorkflowStub; -import javax.annotation.Nullable; -import java.util.concurrent.TimeUnit; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; -public class DaprWorkflowClient implements AutoCloseable { - - private DurableTaskClient innerClient; - private ManagedChannel grpcChannel; - - /** - * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. - */ - public DaprWorkflowClient() { - this(NetworkUtils.buildGrpcManagedChannel()); - } - - /** - * Private Constructor that passes a created DurableTaskClient and the new GRPC channel. - * - * @param grpcChannel ManagedChannel for GRPC channel. - */ - private DaprWorkflowClient(ManagedChannel grpcChannel) { - this(createDurableTaskClient(grpcChannel), grpcChannel); - } - - /** - * Private Constructor for DaprWorkflowClient. - * - * @param innerClient DurableTaskGrpcClient with GRPC Channel set up. - * @param grpcChannel ManagedChannel for instance variable setting. - * - */ - private DaprWorkflowClient(DurableTaskClient innerClient, ManagedChannel grpcChannel) { - this.innerClient = innerClient; - this.grpcChannel = grpcChannel; - } - - /** - * Static method to create the DurableTaskClient. - * - * @param grpcChannel ManagedChannel for GRPC. - * @return a new instance of a DurableTaskClient with a GRPC channel. - */ - private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChannel) { - return new DurableTaskGrpcClientBuilder() - .grpcChannel(grpcChannel) - .build(); - } - - /** - * Schedules a new workflow using DurableTask client. - * - * @param any Workflow type - * @param clazz Class extending Workflow to start an instance of. - * @return A String with the randomly-generated instance ID for new Workflow instance. - */ - public String scheduleNewWorkflow(Class clazz) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); - } - - /** - * Schedules a new workflow using DurableTask client. - * - * @param any Workflow type - * @param clazz Class extending Workflow to start an instance of. - * @param input the input to pass to the scheduled orchestration instance. Must be serializable. - * @return A String with the randomly-generated instance ID for new Workflow instance. - */ - public String scheduleNewWorkflow(Class clazz, Object input) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); - } - - /** - * Schedules a new workflow using DurableTask client. - * - * @param any Workflow type - * @param clazz Class extending Workflow to start an instance of. - * @param input the input to pass to the scheduled orchestration instance. Must be serializable. - * @param instanceId the unique ID of the orchestration instance to schedule - * @return A String with the instanceId parameter value. - */ - public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); - } - - /** - * Terminates the workflow associated with the provided instance id. - * - * @param workflowInstanceId Workflow instance id to terminate. - * @param output the optional output to set for the terminated orchestration instance. - */ - public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { - this.innerClient.terminate(workflowInstanceId, output); - } - - /** - * Closes the inner DurableTask client and shutdown the GRPC channel. - * - */ - public void close() throws InterruptedException { - try { - if (this.innerClient != null) { - this.innerClient.close(); - this.innerClient = null; - } - } finally { - if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { - this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); - this.grpcChannel = null; - } - } +/** + * Implementation of the DemoWorkflow for the server side. + */ +public class DemoWorkflow extends Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + // ... + ctx.getLogger().info("Calling Activity..."); + var input = new DemoActivityInput("Hello Activity!"); + var output = ctx.callActivity(DemoWorkflowActivity.class.getName(), input, DemoActivityOutput.class).await(); + // ... + }; } } ``` +[See the full Java SDK workflow example in context.](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java) + {{% /codetab %}} @@ -570,4 +505,4 @@ Now that you've authored a workflow, learn how to manage it. - Try out the full SDK examples: - [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) + - [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) 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 2491d9934..fb7ad9d57 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 @@ -102,18 +102,64 @@ await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); {{% 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: +Manage your workflow within your code. [In the workflow example from the Java SDK](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java), the workflow is registered in the code using the following APIs: -- **raiseEvent**: Raise an event on a workflow -- **getInstanceMetadata**: Get information on the status of the workflow +- **scheduleNewWorkflow**: Starts a new workflow instance +- **getInstanceState**: Get information on the status of the workflow - **waitForInstanceStart**: Pauses or suspends a workflow instance that can later be resumed -- **waitForInstanceCompletion**: Resumes a paused workflow instance and waits for completion -- **createTaskHub** -- **deleteTaskHub** +- **raiseEvent**: Raises events/tasks for the running workflow instance +- **waitForInstanceCompletion**: Waits for the workflow to complete its tasks +- **purgeInstance**: Removes all metadata related to a specific workflow instance +- **terminateWorkflow**: Terminates the workflow - **purgeInstance**: Removes all metadata related to a specific workflow ```java -todo +package io.dapr.examples.workflows; + +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; + +// ... +public class DemoWorkflowClient { + + // ... + public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + // Start a workflow + String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class, "input data"); + + // Get status information on the workflow + WorkflowInstanceStatus workflowMetadata = client.getInstanceState(instanceId, true); + + // Wait or pause for the workflow instance start + try { + WorkflowInstanceStatus waitForInstanceStartResult = + client.waitForInstanceStart(instanceId, Duration.ofSeconds(60), true); + } + + // Raise an event for the workflow; you can raise several events in parallel + client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); + client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); + client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); + + // Wait for workflow to complete running through tasks + try { + WorkflowInstanceStatus waitForInstanceCompletionResult = + client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(60), true); + } + + // Purge the workflow instance, removing all metadata associated with it + boolean purgeResult = client.purgeInstance(instanceId); + + // Terminate the workflow instance + client.terminateWorkflow(instanceToTerminateId, null); + + System.exit(0); + } +} ``` {{% /codetab %}} @@ -197,6 +243,6 @@ Learn more about these HTTP calls in the [workflow API reference guide]({{< ref - Try out the full SDK examples: - [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) + - [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) - [Workflow API reference]({{< ref workflow_api.md >}}) From d3618b2bf2046ca9b7b52358bfb34d7fe4e14418 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 13:25:46 -0400 Subject: [PATCH 12/19] add workflow patterns Signed-off-by: Hannah Hunter --- .../workflow/workflow-patterns.md | 96 ++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 5460d14c2..8e45921c5 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -109,7 +109,21 @@ catch (TaskFailedException) // Task failures are surfaced as TaskFailedException ```java -todo +public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + + System.out.println(separatorStr); + System.out.println("** Registering parallel Events to be captured by allOf(t1,t2,t3) **"); + client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); + client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); + client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); + System.out.printf("Events raised for workflow with instanceId: %s\n", instanceId); + + } +} ``` {{% /codetab %}} @@ -211,7 +225,47 @@ await context.CallActivityAsync("PostResults", sum); ```java -todo +public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + + System.out.println(separatorStr); + System.out.println("**SendExternalMessage**"); + client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + + // Get events to process in parallel + System.out.println(separatorStr); + System.out.println("** Registering parallel Events to be captured by allOf(t1,t2,t3) **"); + client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); + client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); + client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); + System.out.printf("Events raised for workflow with instanceId: %s\n", instanceId); + + // Register the raised events to be captured + System.out.println(separatorStr); + System.out.println("** Registering Event to be captured by anyOf(t1,t2,t3) **"); + client.raiseEvent(instanceId, "e2", "event 2 Payload"); + System.out.printf("Event raised for workflow with instanceId: %s\n", instanceId); + + // Wait for all tasks to complete and aggregate results + System.out.println(separatorStr); + System.out.println("**WaitForInstanceCompletion**"); + try { + WorkflowInstanceStatus waitForInstanceCompletionResult = + client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(60), true); + System.out.printf("Result: %s%n", waitForInstanceCompletionResult); + } catch (TimeoutException ex) { + System.out.printf("waitForInstanceCompletion has an exception:%s%n", ex); + } + + System.out.println(separatorStr); + System.out.println("**purgeInstance**"); + boolean purgeResult = client.purgeInstance(instanceId); + System.out.printf("purgeResult: %s%n", purgeResult); + + } +} ``` {{% /codetab %}} @@ -552,7 +606,43 @@ public override async Task RunAsync(WorkflowContext context, OrderP ```java -todo +public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + String eventInstanceId = client.scheduleNewWorkflow(DemoWorkflow.class); + System.out.printf("Started new workflow instance with random ID: %s%n", eventInstanceId); + client.raiseEvent(eventInstanceId, "TestException", null); + System.out.printf("Event raised for workflow with instanceId: %s\n", eventInstanceId); + + System.out.println(separatorStr); + String instanceToTerminateId = "terminateMe"; + client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); + System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); + + TimeUnit.SECONDS.sleep(5); + System.out.println("Terminate this workflow instance manually before the timeout is reached"); + client.terminateWorkflow(instanceToTerminateId, null); + System.out.println(separatorStr); + + String restartingInstanceId = "restarting"; + client.scheduleNewWorkflow(DemoWorkflow.class, null, restartingInstanceId); + System.out.printf("Started new workflow instance with ID: %s%n", restartingInstanceId); + System.out.println("Sleeping 30 seconds to restart the workflow"); + TimeUnit.SECONDS.sleep(30); + + System.out.println("**SendExternalMessage: RestartEvent**"); + client.raiseEvent(restartingInstanceId, "RestartEvent", "RestartEventPayload"); + + System.out.println("Sleeping 30 seconds to terminate the eternal workflow"); + TimeUnit.SECONDS.sleep(30); + client.terminateWorkflow(restartingInstanceId, null); + } + + System.out.println("Exiting DemoWorkflowClient."); + System.exit(0); + +} ``` {{% /codetab %}} From ae71e14c312ea8c309dbd5aa747a29629c629755 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 13:49:09 -0400 Subject: [PATCH 13/19] nyemade limitation edit Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-overview.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 cf7326497..60edbbe6b 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 @@ -109,7 +109,8 @@ Want to skip the quickstarts? Not a problem. You can try out the workflow buildi With Dapr Workflow in beta stage comes the following limitation(s): -- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported. NoSQL database support is planned for future minor releases. +- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported in the latest release. +- **Application instances:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, only a maximum of 2 application instances is supported. ## Watch the demo From 37e78d70d79b24e22b42be1316b8c0a54a1f99cf Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 14:41:37 -0400 Subject: [PATCH 14/19] address all but one todo Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-architecture.md | 2 +- .../building-blocks/workflow/workflow-overview.md | 5 +++-- .../building-blocks/workflow/workflow-patterns.md | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md index 1322ea94f..18ec9110b 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md @@ -196,4 +196,4 @@ See the [Reminder usage and execution guarantees section]({{< ref "workflow-arch - Try out the following examples: - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - - [Java](todo) \ No newline at end of file + - [Java](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) \ No newline at end of file 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 60edbbe6b..1e32756ff 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 @@ -95,10 +95,10 @@ 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 quickstart]({{< ref workflow-quickstart.md >}}) | Run a workflow application with four workflow activities to see Dapr Workflow in action | | [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. | +| [Workflow Java SDK example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) | Learn how to create a Dapr Workflow and invoke it using the Java `io.dapr.workflows` package. | ### Start using workflows directly in your app @@ -128,3 +128,4 @@ Watch [this video for an overview on Dapr Workflow](https://youtu.be/s1p9MNl4VGo - 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) + - [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 8e45921c5..49b5fc2db 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -688,7 +688,8 @@ await daprClient.RaiseWorkflowEventAsync( ```java -todo +System.out.println("**SendExternalMessage: RestartEvent**"); +client.raiseEvent(restartingInstanceId, "RestartEvent", "RestartEventPayload"); ``` {{% /codetab %}} @@ -709,4 +710,4 @@ External events don't have to be directly triggered by humans. They can also be - Try out the following examples: - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - - [Java](todo) \ No newline at end of file + - [Java](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) \ No newline at end of file From bac59579af05de5e47649d800ae25033607a27d3 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 15:06:03 -0400 Subject: [PATCH 15/19] dummy link for workflow package Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e32756ff..923bc3794 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 @@ -85,7 +85,7 @@ You can use the following SDKs to author a workflow. | - | - | | 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 | +| Java | [io.dapr.workflows](https://dapr.github.io/java-sdk/io/dapr/workflows/package-summary.html) | ## Try out workflows From e3af25cbb54f20c2679ed00799ce370248f0fdd8 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 16:32:17 -0400 Subject: [PATCH 16/19] add java to quickstart Signed-off-by: Hannah Hunter --- .../quickstarts/workflow-quickstart.md | 335 ++++++++++++++++-- 1 file changed, 300 insertions(+), 35 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index fba93d75d..7895d4a92 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -23,7 +23,7 @@ In this guide, you'll: {{< tabs "Python" ".NET" "Java" >}} - + {{% 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: @@ -103,7 +103,19 @@ Expected output: 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/`). - +### (Optional) Step 4: View in Zipkin + +If you have Zipkin configured for Dapr locally on your machine, you can + +Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: + +``` +docker run -d -p 9411:9411 openzipkin/zipkin +``` + +View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). + + ### What happened? @@ -337,7 +349,15 @@ Expected output: ### (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/`). +If you have Zipkin configured for Dapr locally on your machine, you can + +Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: + +``` +docker run -d -p 9411:9411 openzipkin/zipkin +``` + +View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). @@ -492,13 +512,23 @@ The `Activities` directory holds the four workflow activities used by the workfl - `ProcessPaymentActivity.cs` - `UpdateInventoryActivity.cs` +## Watch the demo + +Watch [this video to walk through the Dapr Workflow .NET demo](https://youtu.be/BxiKpEmchgQ?t=2564): + + + {{% /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 +- `RequestApprovalActivity`: Requests approval for processing payment +- `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 @@ -507,9 +537,10 @@ 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. + - [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) + - [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) + - [OpenJDK 11](https://jdk.java.net/11/) +- [Apache Maven](https://maven.apache.org/install.html) version 3.x. - [Docker Desktop](https://www.docker.com/products/docker-desktop) @@ -522,10 +553,16 @@ Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quic git clone https://github.com/dapr/quickstarts.git ``` -In a new terminal window, navigate to the `order-processor` directory: +Navigate to the `order-processor` directory: ```bash -need +cd workflows/java/sdk/order-processor +``` + +Install the dependencies: + +```bash +mvn clean install ``` ### Step 3: Run the order processor app @@ -533,7 +570,7 @@ need In the terminal, start the order processor app alongside a Dapr sidecar: ```bash -need +dapr run --app-id WorkflowConsoleApp --resources-path ../../../components/ --dapr-grpc-port 50001 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar io.dapr.quickstarts.workflows.WorkflowConsoleApp ``` This starts the `order-processor` app with unique workflow ID and runs the workflow activities. @@ -541,29 +578,64 @@ This starts the `order-processor` app with unique workflow ID and runs the workf Expected output: ``` -need +== APP == *** Welcome to the Dapr Workflow console app sample! +== APP == *** Using this app, you can place orders that start workflows. +== APP == Start workflow runtime +== APP == Sep 20, 2023 3:23:05 PM com.microsoft.durabletask.DurableTaskGrpcWorker startAndBlock +== APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001. + +== APP == ==========Begin the purchase of item:========== +== APP == Starting order workflow, purchasing 10 of cars + +== APP == scheduled new workflow instance of OrderProcessingWorkflow with instance ID: edceba90-9c45-4be8-ad40-60d16e060797 +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Starting Workflow: io.dapr.quickstarts.workflows.OrderProcessingWorkflow +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Instance ID(order ID): edceba90-9c45-4be8-ad40-60d16e060797 +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Current Orchestration Time: 2023-09-20T19:23:09.755Z +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == workflow instance edceba90-9c45-4be8-ad40-60d16e060797 started +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserving inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - There are 100 cars available for purchase +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserved inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Approved requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Processing payment: edceba90-9c45-4be8-ad40-60d16e060797 for 10 cars at $150000 +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Payment for request ID 'edceba90-9c45-4be8-ad40-60d16e060797' processed successfully +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updating inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updated inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797': there are now 90 cars left in stock +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Order completed! : edceba90-9c45-4be8-ad40-60d16e060797 + +== APP == workflow instance edceba90-9c45-4be8-ad40-60d16e060797 completed, out is: {"processed":true} ``` ### (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/`). +If you have Zipkin configured for Dapr locally on your machine, you can + +Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: + +``` +docker run -d -p 9411:9411 openzipkin/zipkin +``` + +View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). ### What happened? -When you ran `need`: +When you ran `dapr run`: -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. A unique order ID for the workflow is generated (in the above example, `edceba90-9c45-4be8-ad40-60d16e060797`) 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. Once approved, your workflow starts and notifies you of its status. +1. The `ProcessPaymentActivity` workflow activity begins processing payment for order `edceba90-9c45-4be8-ad40-60d16e060797` 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 `edceba90-9c45-4be8-ad40-60d16e060797` has completed. 1. The workflow terminates as completed. -#### `order-processor/... need` +#### `order-processor/WorkflowConsoleApp.java` In the application's program file: - The unique workflow order ID is generated @@ -572,33 +644,226 @@ In the application's program file: - The workflow and the workflow activities it invokes are registered ```java -need +package io.dapr.quickstarts.workflows; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.workflows.client.DaprWorkflowClient; + +public class WorkflowConsoleApp { + + private static final String STATE_STORE_NAME = "statestore-actors"; + + // ... + public static void main(String[] args) throws Exception { + System.out.println("*** Welcome to the Dapr Workflow console app sample!"); + System.out.println("*** Using this app, you can place orders that start workflows."); + // Wait for the sidecar to become available + Thread.sleep(5 * 1000); + + // Register the OrderProcessingWorkflow and its activities with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(OrderProcessingWorkflow.class); + builder.registerActivity(NotifyActivity.class); + builder.registerActivity(ProcessPaymentActivity.class); + builder.registerActivity(RequestApprovalActivity.class); + builder.registerActivity(ReserveInventoryActivity.class); + builder.registerActivity(UpdateInventoryActivity.class); + + // Build the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start(false); + } + + InventoryItem inventory = prepareInventoryAndOrder(); + + DaprWorkflowClient workflowClient = new DaprWorkflowClient(); + try (workflowClient) { + executeWorkflow(workflowClient, inventory); + } + + } + + // Start the workflow runtime, pulling and executing tasks + private static void executeWorkflow(DaprWorkflowClient workflowClient, InventoryItem inventory) { + System.out.println("==========Begin the purchase of item:=========="); + String itemName = inventory.getName(); + int orderQuantity = inventory.getQuantity(); + int totalcost = orderQuantity * inventory.getPerItemCost(); + OrderPayload order = new OrderPayload(); + order.setItemName(itemName); + order.setQuantity(orderQuantity); + order.setTotalCost(totalcost); + System.out.println("Starting order workflow, purchasing " + orderQuantity + " of " + itemName); + + String instanceId = workflowClient.scheduleNewWorkflow(OrderProcessingWorkflow.class, order); + System.out.printf("scheduled new workflow instance of OrderProcessingWorkflow with instance ID: %s%n", + instanceId); + + // Check workflow instance start status + try { + workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false); + System.out.printf("workflow instance %s started%n", instanceId); + } catch (TimeoutException e) { + System.out.printf("workflow instance %s did not start within 10 seconds%n", instanceId); + return; + } + + // Check workflow instance complete status + try { + WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, + Duration.ofSeconds(30), + true); + if (workflowStatus != null) { + System.out.printf("workflow instance %s completed, out is: %s %n", instanceId, + workflowStatus.getSerializedOutput()); + } else { + System.out.printf("workflow instance %s not found%n", instanceId); + } + } catch (TimeoutException e) { + System.out.printf("workflow instance %s did not complete within 30 seconds%n", instanceId); + } + + } + + private static InventoryItem prepareInventoryAndOrder() { + // prepare 100 cars in inventory + InventoryItem inventory = new InventoryItem(); + inventory.setName("cars"); + inventory.setPerItemCost(15000); + inventory.setQuantity(100); + 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); + return order; + } + + private static void restockInventory(DaprClient daprClient, InventoryItem inventory) { + String key = inventory.getName(); + daprClient.saveState(STATE_STORE_NAME, key, inventory).block(); + } +} ``` -#### `order-processor/... need` +#### `OrderProcessingWorkflow.java` -In `need`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). +In `OrderProcessingWorkflow.java`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). ```java -need +package io.dapr.quickstarts.workflows; +import io.dapr.workflows.Workflow; + +public class OrderProcessingWorkflow extends Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + Logger logger = ctx.getLogger(); + String orderId = ctx.getInstanceId(); + logger.info("Starting Workflow: " + ctx.getName()); + logger.info("Instance ID(order ID): " + orderId); + logger.info("Current Orchestration Time: " + ctx.getCurrentInstant()); + + OrderPayload order = ctx.getInput(OrderPayload.class); + logger.info("Received Order: " + order.toString()); + OrderResult orderResult = new OrderResult(); + orderResult.setProcessed(false); + + // Notify the user that an order has come through + Notification notification = new Notification(); + notification.setMessage("Received Order: " + order.toString()); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + + // Determine if there is enough of the item available for purchase by checking + // the inventory + InventoryRequest inventoryRequest = new InventoryRequest(); + inventoryRequest.setRequestId(orderId); + inventoryRequest.setItemName(order.getItemName()); + inventoryRequest.setQuantity(order.getQuantity()); + InventoryResult inventoryResult = ctx.callActivity(ReserveInventoryActivity.class.getName(), + inventoryRequest, InventoryResult.class).await(); + + // If there is insufficient inventory, fail and let the user know + if (!inventoryResult.isSuccess()) { + notification.setMessage("Insufficient inventory for order : " + order.getItemName()); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + // Require orders over a certain threshold to be approved + if (order.getTotalCost() > 5000) { + ApprovalResult approvalResult = ctx.callActivity(RequestApprovalActivity.class.getName(), + order, ApprovalResult.class).await(); + if (approvalResult != ApprovalResult.Approved) { + notification.setMessage("Order " + order.getItemName() + " was not approved."); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + } + + // There is enough inventory available so the user can purchase the item(s). + // Process their payment + PaymentRequest paymentRequest = new PaymentRequest(); + paymentRequest.setRequestId(orderId); + paymentRequest.setItemBeingPurchased(order.getItemName()); + paymentRequest.setQuantity(order.getQuantity()); + paymentRequest.setAmount(order.getTotalCost()); + boolean isOK = ctx.callActivity(ProcessPaymentActivity.class.getName(), + paymentRequest, boolean.class).await(); + if (!isOK) { + notification.setMessage("Payment failed for order : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + inventoryResult = ctx.callActivity(UpdateInventoryActivity.class.getName(), + inventoryRequest, InventoryResult.class).await(); + if (!inventoryResult.isSuccess()) { + // If there is an error updating the inventory, refund the user + // paymentRequest.setAmount(-1 * paymentRequest.getAmount()); + // ctx.callActivity(ProcessPaymentActivity.class.getName(), + // paymentRequest).await(); + + // Let users know their payment processing failed + notification.setMessage("Order failed to update inventory! : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + // Let user know their order was processed + notification.setMessage("Order completed! : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + + // Complete the workflow with order result is processed + orderResult.setProcessed(true); + ctx.complete(orderResult); + }; + } + +} ``` -#### `order-processor/... need` directory +#### `activities` directory The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files: -- `need` +- [`NotifyActivity.java`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/NotifyActivity.java) +- [`RequestApprovalActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/RequestApprovalActivity.java) +- [`ReserveInventoryActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/ReserveInventoryActivity.java) +- [`ProcessPaymentActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/ProcessPaymentActivity.java) +- [`UpdateInventoryActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/UpdateInventoryActivity.java) {{% /codetab %}} {{< /tabs >}} -## Watch the demo - -Watch [this video to walk through the Dapr Workflow .NET demo](https://youtu.be/BxiKpEmchgQ?t=2564): - - - - ## Tell us what you think! We're continuously working to improve our Quickstart examples and value your feedback. Did you find this Quickstart helpful? Do you have suggestions for improvement? @@ -610,4 +875,4 @@ Join the discussion in our [discord channel](https://discord.com/channels/778680 - Walk through a more in-depth [.NET SDK example workflow](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - Learn more about [Workflow as a Dapr building block]({{< ref workflow-overview >}}) -{{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}} +{{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}} \ No newline at end of file From cd472f17a5b149b0fc195d989d2505905f234fdf Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 17:01:23 -0400 Subject: [PATCH 17/19] fix localized link Signed-off-by: Hannah Hunter --- .../en/getting-started/quickstarts/workflow-quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index 7895d4a92..a8d3f7c88 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -7,7 +7,7 @@ description: Get started with the Dapr Workflow building block --- {{% alert title="Note" color="primary" %}} -Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). {{% /alert %}} Let's take a look at the Dapr [Workflow building block]({{< ref workflow >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. @@ -537,7 +537,7 @@ For this example, you will need: - [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). - Java JDK 11 (or greater): - - [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) + - [Microsoft JDK 11](https://docs.microsoft.com/java/openjdk/download#openjdk-11) - [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) - [OpenJDK 11](https://jdk.java.net/11/) - [Apache Maven](https://maven.apache.org/install.html) version 3.x. From b06d0f28451adf785fdeeef2ee76c0ac6b184cdc Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 21 Sep 2023 07:52:57 -0400 Subject: [PATCH 18/19] removing some typos Signed-off-by: Hannah Hunter --- .../getting-started/quickstarts/workflow-quickstart.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index a8d3f7c88..2ac77f771 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -101,12 +101,6 @@ Expected output: ### (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/`). - -### (Optional) Step 4: View in Zipkin - -If you have Zipkin configured for Dapr locally on your machine, you can - Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: ``` @@ -349,8 +343,6 @@ Expected output: ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can - Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: ``` @@ -610,8 +602,6 @@ Expected output: ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can - Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: ``` From 6b143996796103001c7cbe632dd11b7e13ecca91 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 15:42:42 -0400 Subject: [PATCH 19/19] add ryan review Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/api/workflow_api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/api/workflow_api.md b/daprdocs/content/en/reference/api/workflow_api.md index a2c0d3b1b..9f9c34de8 100644 --- a/daprdocs/content/en/reference/api/workflow_api.md +++ b/daprdocs/content/en/reference/api/workflow_api.md @@ -57,7 +57,7 @@ The API call will provide a response similar to this: Terminate a running workflow instance with the given name and instance ID. ``` -POST http://localhost:3500/v1.0-beta1/workflow///terminate +POST http://localhost:3500/v1.0-beta1/workflows///terminate ``` ### URL parameters @@ -144,7 +144,7 @@ None. Resume a paused workflow instance. ``` -POST http://localhost:3500/v1.0-beta1/workflow///resume +POST http://localhost:3500/v1.0-beta1/workflows///resume ``` ### URL parameters