From 3fdf6d598bee302c2b64b04711a3bd8c36bb873e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 2 Aug 2023 15:09:07 -0400 Subject: [PATCH] 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