mirror of https://github.com/dapr/docs.git
add java tabs
Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>
This commit is contained in:
parent
0211dfef6e
commit
3fdf6d598b
|
|
@ -189,4 +189,7 @@ See the [Reminder usage and execution guarantees section]({{< ref "workflow-arch
|
||||||
- [Workflow overview]({{< ref workflow-overview.md >}})
|
- [Workflow overview]({{< ref workflow-overview.md >}})
|
||||||
- [Workflow API reference]({{< ref workflow_api.md >}})
|
- [Workflow API reference]({{< ref workflow_api.md >}})
|
||||||
- [Try out the Workflow quickstart]({{< ref workflow-quickstart.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)
|
||||||
|
|
@ -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.
|
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 >}}
|
{{< tabs Python ".NET" Java >}}
|
||||||
|
|
||||||
{{% codetab %}}
|
|
||||||
<!--dotnet-->
|
|
||||||
|
|
||||||
```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<string>("Step1", wfInput, retryOptions);
|
|
||||||
var result2 = await context.CallActivityAsync<byte[]>("Step2", result1, retryOptions);
|
|
||||||
var result3 = await context.CallActivityAsync<long[]>("Step3", result2, retryOptions);
|
|
||||||
return string.Join(", ", result4);
|
|
||||||
}
|
|
||||||
catch (TaskFailedException) // Task failures are surfaced as TaskFailedException
|
|
||||||
{
|
|
||||||
// Retries expired - apply custom compensation logic
|
|
||||||
await context.CallActivityAsync<long[]>("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 %}}
|
|
||||||
|
|
||||||
{{% codetab %}}
|
{{% codetab %}}
|
||||||
<!--python-->
|
<!--python-->
|
||||||
|
|
@ -103,9 +68,49 @@ def error_handler(ctx, error):
|
||||||
# Do some compensating work
|
# Do some compensating work
|
||||||
```
|
```
|
||||||
|
|
||||||
{{% alert title="Note" color="primary" %}}
|
> **Note** Workflow retry policies will be available in a future version of the Python SDK.
|
||||||
Workflow retry policies will be available in a future version of the Python SDK.
|
|
||||||
{{% /alert %}}
|
{{% /codetab %}}
|
||||||
|
|
||||||
|
{{% codetab %}}
|
||||||
|
<!--dotnet-->
|
||||||
|
|
||||||
|
```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<string>("Step1", wfInput, retryOptions);
|
||||||
|
var result2 = await context.CallActivityAsync<byte[]>("Step2", result1, retryOptions);
|
||||||
|
var result3 = await context.CallActivityAsync<long[]>("Step3", result2, retryOptions);
|
||||||
|
return string.Join(", ", result4);
|
||||||
|
}
|
||||||
|
catch (TaskFailedException) // Task failures are surfaced as TaskFailedException
|
||||||
|
{
|
||||||
|
// Retries expired - apply custom compensation logic
|
||||||
|
await context.CallActivityAsync<long[]>("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-->
|
||||||
|
|
||||||
|
```java
|
||||||
|
todo
|
||||||
|
```
|
||||||
|
|
||||||
{{% /codetab %}}
|
{{% /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:
|
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 >}}
|
{{< tabs Python ".NET" Java >}}
|
||||||
|
|
||||||
{{% codetab %}}
|
|
||||||
<!--dotnet-->
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Get a list of N work items to process in parallel.
|
|
||||||
object[] workBatch = await context.CallActivityAsync<object[]>("GetWorkBatch", null);
|
|
||||||
|
|
||||||
// Schedule the parallel tasks, but don't wait for them to complete yet.
|
|
||||||
var parallelTasks = new List<Task<int>>(workBatch.Length);
|
|
||||||
for (int i = 0; i < workBatch.Length; i++)
|
|
||||||
{
|
|
||||||
Task<int> task = context.CallActivityAsync<int>("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 %}}
|
{{% codetab %}}
|
||||||
<!--python-->
|
<!--python-->
|
||||||
|
|
@ -202,6 +182,40 @@ def process_results(ctx, final_result: int):
|
||||||
|
|
||||||
{{% /codetab %}}
|
{{% /codetab %}}
|
||||||
|
|
||||||
|
{{% codetab %}}
|
||||||
|
<!--dotnet-->
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Get a list of N work items to process in parallel.
|
||||||
|
object[] workBatch = await context.CallActivityAsync<object[]>("GetWorkBatch", null);
|
||||||
|
|
||||||
|
// Schedule the parallel tasks, but don't wait for them to complete yet.
|
||||||
|
var parallelTasks = new List<Task<int>>(workBatch.Length);
|
||||||
|
for (int i = 0; i < workBatch.Length; i++)
|
||||||
|
{
|
||||||
|
Task<int> task = context.CallActivityAsync<int>("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-->
|
||||||
|
|
||||||
|
```java
|
||||||
|
todo
|
||||||
|
```
|
||||||
|
|
||||||
|
{{% /codetab %}}
|
||||||
|
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
The key takeaways from this example are:
|
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.
|
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 >}}
|
{{< tabs Python ".NET" Java >}}
|
||||||
|
|
||||||
{{% codetab %}}
|
|
||||||
<!--dotnet-->
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public override async Task<object> RunAsync(WorkflowContext context, MyEntityState myEntityState)
|
|
||||||
{
|
|
||||||
TimeSpan nextSleepInterval;
|
|
||||||
|
|
||||||
var status = await context.CallActivityAsync<string>("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 %}}
|
{{% codetab %}}
|
||||||
<!--python-->
|
<!--python-->
|
||||||
|
|
@ -392,6 +365,56 @@ def send_alert(ctx, message: str):
|
||||||
|
|
||||||
{{% /codetab %}}
|
{{% /codetab %}}
|
||||||
|
|
||||||
|
{{% codetab %}}
|
||||||
|
<!--dotnet-->
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public override async Task<object> RunAsync(WorkflowContext context, MyEntityState myEntityState)
|
||||||
|
{
|
||||||
|
TimeSpan nextSleepInterval;
|
||||||
|
|
||||||
|
var status = await context.CallActivityAsync<string>("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-->
|
||||||
|
|
||||||
|
```java
|
||||||
|
todo
|
||||||
|
```
|
||||||
|
|
||||||
|
{{% /codetab %}}
|
||||||
|
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
A workflow implementing the monitor pattern can loop forever or it can terminate itself gracefully by not calling _continue-as-new_.
|
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.
|
The following example code shows how this pattern can be implemented using Dapr Workflow.
|
||||||
|
|
||||||
{{< tabs ".NET" Python >}}
|
{{< tabs Python ".NET" Java >}}
|
||||||
|
|
||||||
{{% codetab %}}
|
|
||||||
<!--dotnet-->
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public override async Task<OrderResult> 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<ApprovalResult>(
|
|
||||||
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 %}}
|
|
||||||
|
|
||||||
{{% codetab %}}
|
{{% codetab %}}
|
||||||
<!--python-->
|
<!--python-->
|
||||||
|
|
@ -527,26 +504,65 @@ def place_order(_, order: Order) -> None:
|
||||||
|
|
||||||
{{% /codetab %}}
|
{{% /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 %}}
|
{{% codetab %}}
|
||||||
<!--dotnet-->
|
<!--dotnet-->
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Raise the workflow event to the waiting workflow
|
public override async Task<OrderResult> RunAsync(WorkflowContext context, OrderPayload order)
|
||||||
await daprClient.RaiseWorkflowEventAsync(
|
{
|
||||||
instanceId: orderId,
|
// ...(other steps)...
|
||||||
workflowComponent: "dapr",
|
|
||||||
eventName: "ManagerApproval",
|
// Require orders over a certain threshold to be approved
|
||||||
eventData: ApprovalResult.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<ApprovalResult>(
|
||||||
|
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-->
|
||||||
|
|
||||||
|
```java
|
||||||
|
todo
|
||||||
```
|
```
|
||||||
|
|
||||||
{{% /codetab %}}
|
{{% /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 %}}
|
{{% codetab %}}
|
||||||
<!--python-->
|
<!--python-->
|
||||||
|
|
||||||
|
|
@ -564,6 +580,29 @@ with DaprClient() as d:
|
||||||
|
|
||||||
{{% /codetab %}}
|
{{% /codetab %}}
|
||||||
|
|
||||||
|
{{% codetab %}}
|
||||||
|
<!--dotnet-->
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Raise the workflow event to the waiting workflow
|
||||||
|
await daprClient.RaiseWorkflowEventAsync(
|
||||||
|
instanceId: orderId,
|
||||||
|
workflowComponent: "dapr",
|
||||||
|
eventName: "ManagerApproval",
|
||||||
|
eventData: ApprovalResult.Approved);
|
||||||
|
```
|
||||||
|
|
||||||
|
{{% /codetab %}}
|
||||||
|
|
||||||
|
{{% codetab %}}
|
||||||
|
<!--java-->
|
||||||
|
|
||||||
|
```java
|
||||||
|
todo
|
||||||
|
```
|
||||||
|
|
||||||
|
{{% /codetab %}}
|
||||||
|
|
||||||
{{< /tabs >}}
|
{{< /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.
|
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 >}})
|
- [Try out Dapr Workflows using the quickstart]({{< ref workflow-quickstart.md >}})
|
||||||
- [Workflow overview]({{< ref workflow-overview.md >}})
|
- [Workflow overview]({{< ref workflow-overview.md >}})
|
||||||
- [Workflow API reference]({{< ref workflow_api.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)
|
||||||
Loading…
Reference in New Issue