mirror of https://github.com/dapr/docs.git
Merge branch 'v1.12' into issue_3749
This commit is contained in:
commit
7b0d6bacf1
|
@ -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.
|
|
@ -17,7 +17,7 @@ The placement service is deployed as part of `dapr init -k`, or via the Dapr Hel
|
|||
|
||||
## Placement tables
|
||||
|
||||
There is an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm you just need to set `dapr_placement.metadataEnabled` to true.
|
||||
There is an [HTTP API `/placement/state` for placement service]({{< ref placement_api.md >}}) that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm you just need to set `dapr_placement.metadataEnabled` to true.
|
||||
|
||||
### Usecase:
|
||||
The placement table API can be used for retrieving the current placement table, which contains all the actors registered. This can be helpful for debugging and allows tools to extract and present information about actors.
|
||||
|
@ -83,3 +83,7 @@ updatedAt | timestamp | Timestamp of the actor registered/updated.
|
|||
"tableVersion": 1
|
||||
}
|
||||
```
|
||||
|
||||
## Related links
|
||||
|
||||
[Learn more about the Placement API.]({{< ref placement_api.md >}})
|
|
@ -96,6 +96,10 @@ For more information on message routing, read [Dapr pub/sub API reference]({{< r
|
|||
|
||||
Sometimes, messages can't be processed because of a variety of possible issues, such as erroneous conditions within the producer or consumer application or an unexpected state change that causes an issue with your application code. Dapr allows developers to set dead letter topics to deal with messages that cannot be delivered to an application. This feature is available on all pub/sub components and prevents consumer applications from endlessly retrying a failed message. For more information, read about [dead letter topics]({{< ref "pubsub-deadletter.md">}})
|
||||
|
||||
### Enabling the outbox pattern
|
||||
|
||||
Dapr enables developers to use the outbox pattern for achieving a single transaction across a transactional state store and any message broker. For more information, read [How to enable transactional outbox messaging]({{< ref howto-outbox.md >}})
|
||||
|
||||
### Namespace consumer groups
|
||||
|
||||
Dapr solves multi-tenancy at-scale with [namespaces for consumer groups]({{< ref howto-namespace >}}). Simply include the `"{namespace}"` value in your component metadata for consumer groups to allow multiple namespaces with applications of the same `app-id` to publish and subscribe to the same message broker.
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
type: docs
|
||||
title: "How-To: Enable the transactional outbox pattern"
|
||||
linkTitle: "How-To: Enable the transactional outbox pattern"
|
||||
weight: 400
|
||||
description: "Commit a single transaction across a state store and pub/sub message broker"
|
||||
---
|
||||
|
||||
The transactional outbox pattern is a well known design pattern for sending notifications regarding changes in an application's state. The transactional outbox pattern uses a single transaction that spans across the database and the message broker delivering the notification.
|
||||
|
||||
Developers are faced with many difficult technical challenges when trying to implement this pattern on their own, which often involves writing error-prone central coordination managers that, at most, support a combination of one or two databases and message brokers.
|
||||
|
||||
For example, you can use the outbox pattern to:
|
||||
1. Write a new user record to an account database.
|
||||
1. Send a notification message that the account was successfully created.
|
||||
|
||||
With Dapr's outbox support, you can notify subscribers when an application's state is created or updated when calling Dapr's [transactions API]({{< ref "state_api.md#state-transactions" >}}).
|
||||
|
||||
The diagram below is an overview of how the outbox feature works:
|
||||
|
||||
1) Service A saves/updates state to the state store using a transaction.
|
||||
2) A message is written to the broker under the same transaction. When the message is successfully delivered to the message broker, the transaction completes, ensuring the state and message are transacted together.
|
||||
3) The message broker delivers the message topic to any subscribers - in this case, Service B.
|
||||
|
||||
<img src="/images/state-management-outbox.png" width=800 alt="Diagram showing the steps of the outbox pattern">
|
||||
|
||||
## Requirements
|
||||
|
||||
The outbox feature can be used with using any [transactional state store]({{< ref supported-state-stores >}}) supported by Dapr. All [pub/sub brokers]({{< ref supported-pubsub >}}) are supported with the outbox feature.
|
||||
|
||||
{{% alert title="Note" color="primary" %}}
|
||||
Message brokers that work with the competing consumer pattern (for example, [Apache Kafka]({{< ref setup-apache-kafka>}}) are encouraged to reduce the chances of duplicate events.
|
||||
{{% /alert %}}
|
||||
|
||||
## Usage
|
||||
|
||||
To enable the outbox feature, add the following required and optional fields on a state store component:
|
||||
|
||||
```yaml
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: mysql-outbox
|
||||
spec:
|
||||
type: state.mysql
|
||||
version: v1
|
||||
metadata:
|
||||
- name: connectionString
|
||||
value: "<CONNECTION STRING>"
|
||||
- name: outboxPublishPubsub # Required
|
||||
value: "mypubsub"
|
||||
- name: outboxPublishTopic # Required
|
||||
value: "newOrder"
|
||||
- name: outboxPubsub # Optional
|
||||
value: "myOutboxPubsub"
|
||||
- name: outboxDiscardWhenMissingState #Optional. Defaults to false
|
||||
value: false
|
||||
```
|
||||
|
||||
### Metadata fields
|
||||
|
||||
| Name | Required | Default Value | Description |
|
||||
| --------------------|-------------|---------------|------------------------------------------------------- |
|
||||
| outboxPublishPubsub | Yes | N/A | Sets the name of the pub/sub component to deliver the notifications when publishing state changes
|
||||
| outboxPublishTopic | Yes | N/A | Sets the topic that receives the state changes on the pub/sub configured with `outboxPublishPubsub`. The message body will be a state transaction item for an `insert` or `update` operation
|
||||
| outboxPubsub | No | `outboxPublishPubsub` | Sets the pub/sub component used by Dapr to coordinate the state and pub/sub transactions. If not set, the pub/sub component configured with `outboxPublishPubsub` is used. This is useful if you want to separate the pub/sub component used to send the notification state changes from the one used to coordinate the transaction
|
||||
| outboxDiscardWhenMissingState | No | `false` | By setting `outboxDiscardWhenMissingState` to `true`, Dapr discards the transaction if it cannot find the state in the database and does not retry. This setting can be useful if the state store data has been deleted for any reason before Dapr was able to deliver the message and you would like Dapr to drop the items from the pub/sub and stop retrying to fetch the state
|
||||
|
||||
### Combining outbox and non-outbox messages on the same state store
|
||||
|
||||
If you want to use the same state store for sending both outbox and non-outbox messages, simply define two state store components that connect to the same state store, where one has the outbox feature and the other does not.
|
||||
|
||||
#### MySQL state store without outbox
|
||||
|
||||
```yaml
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: mysql
|
||||
spec:
|
||||
type: state.mysql
|
||||
version: v1
|
||||
metadata:
|
||||
- name: connectionString
|
||||
value: "<CONNECTION STRING>"
|
||||
```
|
||||
|
||||
#### MySQL state store with outbox
|
||||
|
||||
```yaml
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: mysql-outbox
|
||||
spec:
|
||||
type: state.mysql
|
||||
version: v1
|
||||
metadata:
|
||||
- name: connectionString
|
||||
value: "<CONNECTION STRING>"
|
||||
- name: outboxPublishPubsub # Required
|
||||
value: "mypubsub"
|
||||
- name: outboxPublishTopic # Required
|
||||
value: "newOrder"
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
Watch [this video for an overview of the outbox pattern](https://youtu.be/rTovKpG0rhY?t=1338):
|
||||
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<iframe width="360" height="315" src="https://youtu.be/rTovKpG0rhY?t=1338" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
@ -116,6 +116,10 @@ Dapr enables states to be:
|
|||
|
||||
For more details read [How-To: Share state between applications]({{< ref howto-share-state.md >}}),
|
||||
|
||||
### Enabling the outbox pattern
|
||||
|
||||
Dapr enables developers to use the outbox pattern for achieving a single transaction across a transactional state store and any message broker. For more information, read [How to enable transactional outbox messaging]({{< ref howto-outbox.md >}})
|
||||
|
||||
### Querying state
|
||||
|
||||
There are two ways to query the state:
|
||||
|
|
|
@ -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" %}}
|
||||
|
@ -30,7 +34,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 %}}
|
||||
|
||||
<!--python-->
|
||||
|
||||
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 +124,76 @@ public class ProcessPaymentActivity : WorkflowActivity<PaymentRequest, object>
|
|||
|
||||
{{% codetab %}}
|
||||
|
||||
<!--python-->
|
||||
<!--java-->
|
||||
|
||||
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. Activities are wrapped in the public `DemoWorkflowActivity` class, which implements the workflow activities.
|
||||
|
||||
```python
|
||||
def hello_act(ctx: WorkflowActivityContext, input):
|
||||
global counter
|
||||
counter += input
|
||||
print(f'New counter value is: {counter}!', flush=True)
|
||||
```java
|
||||
@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...");
|
||||
|
||||
try {
|
||||
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 the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59)
|
||||
|
||||
[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 %}}
|
||||
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## Write the workflow
|
||||
|
||||
Next, register and call the activites in a workflow.
|
||||
|
||||
{{< tabs ".NET" Python >}}
|
||||
{{< tabs Python ".NET" Java >}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
<!--python-->
|
||||
|
||||
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 +240,31 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo
|
|||
|
||||
{{% codetab %}}
|
||||
|
||||
<!--python-->
|
||||
<!--java-->
|
||||
|
||||
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)
|
||||
Next, register the workflow with the `WorkflowRuntimeBuilder` and start the workflow runtime.
|
||||
|
||||
```java
|
||||
public class DemoWorkflowWorker {
|
||||
|
||||
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 `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51)
|
||||
[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 %}}
|
||||
|
@ -196,78 +275,7 @@ def hello_world_wf(ctx: DaprWorkflowContext, input):
|
|||
|
||||
Finally, compose the application using the workflow.
|
||||
|
||||
{{< tabs ".NET" Python >}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
<!--csharp-->
|
||||
|
||||
[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<OrderProcessingWorkflow>();
|
||||
|
||||
// These are the activities that get invoked by the workflow(s).
|
||||
options.RegisterActivity<NotifyActivity>();
|
||||
options.RegisterActivity<ReserveInventoryActivity>();
|
||||
options.RegisterActivity<ProcessPaymentActivity>();
|
||||
});
|
||||
|
||||
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<OrderPayload>(),
|
||||
status = state.RuntimeStatus.ToString(),
|
||||
result = state.ReadOutputAs<OrderResult>(),
|
||||
};
|
||||
|
||||
//...
|
||||
}).WithName("GetOrderInfoEndpoint");
|
||||
|
||||
app.Run();
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
{{< tabs Python ".NET" Java >}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
|
@ -356,6 +364,124 @@ if __name__ == '__main__':
|
|||
```
|
||||
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
<!--csharp-->
|
||||
|
||||
[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<OrderProcessingWorkflow>();
|
||||
|
||||
// These are the activities that get invoked by the workflow(s).
|
||||
options.RegisterActivity<NotifyActivity>();
|
||||
options.RegisterActivity<ReserveInventoryActivity>();
|
||||
options.RegisterActivity<ProcessPaymentActivity>();
|
||||
});
|
||||
|
||||
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<OrderPayload>(),
|
||||
status = state.RuntimeStatus.ToString(),
|
||||
result = state.ReadOutputAs<OrderResult>(),
|
||||
};
|
||||
|
||||
//...
|
||||
}).WithName("GetOrderInfoEndpoint");
|
||||
|
||||
app.Run();
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
<!--java-->
|
||||
|
||||
[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`
|
||||
- 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.examples.workflows;
|
||||
|
||||
import com.microsoft.durabletask.CompositeTaskFailedException;
|
||||
import com.microsoft.durabletask.Task;
|
||||
import com.microsoft.durabletask.TaskCanceledException;
|
||||
import io.dapr.workflows.Workflow;
|
||||
import io.dapr.workflows.WorkflowStub;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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 %}}
|
||||
|
||||
|
||||
|
@ -377,5 +503,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](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows)
|
||||
|
|
|
@ -6,45 +6,13 @@ 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 ".NET" Python HTTP >}}
|
||||
|
||||
<!--NET-->
|
||||
{{% 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<string, string> 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 >}}
|
||||
|
||||
<!--Python-->
|
||||
{{% codetab %}}
|
||||
|
@ -95,6 +63,107 @@ d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponen
|
|||
|
||||
{{% /codetab %}}
|
||||
|
||||
<!--NET-->
|
||||
{{% 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<string, string> 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, eventName);
|
||||
|
||||
// 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 the workflow, removing all inbox and history information from associated instance
|
||||
await daprClient.PurgeWorkflowAsync(orderId, workflowComponent);
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
<!--Python-->
|
||||
{{% codetab %}}
|
||||
|
||||
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:
|
||||
|
||||
- **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
|
||||
- **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
|
||||
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 %}}
|
||||
|
||||
|
||||
<!--HTTP-->
|
||||
{{% codetab %}}
|
||||
|
@ -106,7 +175,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.
|
||||
|
@ -116,7 +185,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
|
||||
|
@ -124,7 +193,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/<workflowComponentName>/<instanceID>/raiseEvent/<eventName>
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceID>/raiseEvent/<eventName>
|
||||
```
|
||||
|
||||
> An `eventName` can be any function.
|
||||
|
@ -134,13 +203,13 @@ POST http://localhost:3500/v1.0-alpha1/workflows/<workflowComponentName>/<instan
|
|||
To plan for down-time, wait for inputs, and more, you can pause and then resume a workflow. To pause a workflow with an ID `12345678` until triggered to resume, run:
|
||||
|
||||
```http
|
||||
POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/pause
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/dapr/12345678/pause
|
||||
```
|
||||
|
||||
To resume a workflow with an ID `12345678`, run:
|
||||
|
||||
```http
|
||||
POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/resume
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/dapr/12345678/resume
|
||||
```
|
||||
|
||||
### Purge a workflow
|
||||
|
@ -150,7 +219,7 @@ The purge API can be used to permanently delete workflow metadata from the under
|
|||
Only workflow instances in the COMPLETED, FAILED, or TERMINATED state can be purged. If the workflow is in any other state, calling purge returns an error.
|
||||
|
||||
```http
|
||||
POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/purge
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/dapr/12345678/purge
|
||||
```
|
||||
|
||||
### Get information about a workflow
|
||||
|
@ -158,7 +227,7 @@ POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/purge
|
|||
To fetch workflow information (outputs and inputs) with an ID `12345678`, run:
|
||||
|
||||
```http
|
||||
GET http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678
|
||||
GET http://localhost:3500/v1.0-beta1/workflows/dapr/12345678
|
||||
```
|
||||
|
||||
Learn more about these HTTP calls in the [workflow API reference guide]({{< ref workflow_api.md >}}).
|
||||
|
@ -172,6 +241,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](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows)
|
||||
|
||||
- [Workflow API reference]({{< ref workflow_api.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
|
||||
|
@ -189,4 +193,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](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows)
|
|
@ -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" %}}
|
||||
|
|
|
@ -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.
|
||||
|
@ -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 | [io.dapr.workflows](https://dapr.github.io/java-sdk/io/dapr/workflows/package-summary.html) |
|
||||
|
||||
## Try out workflows
|
||||
|
||||
|
@ -95,15 +95,23 @@ 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 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](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
|
||||
|
||||
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 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
|
||||
|
||||
Watch [this video for an overview on Dapr Workflow](https://youtu.be/s1p9MNl4VGo?t=131):
|
||||
|
@ -120,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)
|
||||
|
|
|
@ -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 %}}
|
||||
<!--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 %}}
|
||||
{{< tabs Python ".NET" Java >}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--python-->
|
||||
|
@ -103,9 +68,63 @@ 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 %}}
|
||||
<!--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
|
||||
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 %}}
|
||||
|
||||
|
@ -135,32 +154,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 %}}
|
||||
<!--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 %}}
|
||||
{{< tabs Python ".NET" Java >}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--python-->
|
||||
|
@ -202,6 +196,80 @@ def process_results(ctx, final_result: int):
|
|||
|
||||
{{% /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
|
||||
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 %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
The key takeaways from this example are:
|
||||
|
@ -233,7 +301,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:
|
||||
|
@ -245,7 +313,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.
|
||||
|
@ -302,48 +370,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 %}}
|
||||
<!--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 %}}
|
||||
{{< tabs Python ".NET" Java >}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--python-->
|
||||
|
@ -392,6 +419,56 @@ def send_alert(ctx, message: str):
|
|||
|
||||
{{% /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 >}}
|
||||
|
||||
A workflow implementing the monitor pattern can loop forever or it can terminate itself gracefully by not calling _continue-as-new_.
|
||||
|
@ -420,53 +497,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 %}}
|
||||
<!--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 %}}
|
||||
{{< tabs Python ".NET" Java >}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--python-->
|
||||
|
@ -527,26 +558,101 @@ 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 %}}
|
||||
<!--dotnet-->
|
||||
|
||||
```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<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);
|
||||
}
|
||||
```
|
||||
|
||||
> **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
|
||||
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 %}}
|
||||
|
||||
{{< /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 %}}
|
||||
<!--python-->
|
||||
|
||||
|
@ -564,6 +670,30 @@ with DaprClient() as d:
|
|||
|
||||
{{% /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
|
||||
System.out.println("**SendExternalMessage: RestartEvent**");
|
||||
client.raiseEvent(restartingInstanceId, "RestartEvent", "RestartEventPayload");
|
||||
```
|
||||
|
||||
{{% /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 +707,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](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows)
|
|
@ -7,23 +7,24 @@ description: Run multiple applications with one CLI command
|
|||
---
|
||||
|
||||
{{% alert title="Note" color="primary" %}}
|
||||
Multi-App Run is currently a preview feature only supported in Linux/MacOS.
|
||||
Multi-App Run for **Kubernetes** is currently a preview feature.
|
||||
{{% /alert %}}
|
||||
|
||||
Let's say you want to run several applications locally to test them together, similar to a production scenario. With a local Kubernetes cluster, you'd be able to do this with helm/deployment YAML files. You'd also have to build them as containers and set up Kubernetes, which can add some complexity.
|
||||
Let's say you want to run several applications locally to test them together, similar to a production scenario. Multi-App Run allows you to start and stop a set of applications simultaneously, either:
|
||||
- Locally/self-hosted with processes, or
|
||||
- By building container images and deploying to a Kubernetes cluster
|
||||
- You can use a local Kubernetes cluster (KiND) or one deploy to a Cloud (AKS, EKS, and GKE).
|
||||
|
||||
Instead, you simply want to run them as local executables in self-hosted mode. However, self-hosted mode requires you to:
|
||||
The Multi-App Run template file describes how to start multiple applications as if you had run many separate CLI `run` commands. By default, this template file is called `dapr.yaml`.
|
||||
|
||||
- Run multiple `dapr run` commands
|
||||
- Keep track of all ports opened (you cannot have duplicate ports for different applications).
|
||||
- Remember the resources folders and configuration files that each application refers to.
|
||||
- Recall all of the additional flags you used to tweak the `dapr run` command behavior (`--app-health-check-path`, `--dapr-grpc-port`, `--unix-domain-socket`, etc.)
|
||||
{{< tabs Self-hosted Kubernetes>}}
|
||||
|
||||
With Multi-App Run, you can start multiple applications in self-hosted mode using a single `dapr run -f` command using a template file. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`.
|
||||
{{% codetab %}}
|
||||
<!--selfhosted-->
|
||||
|
||||
## Multi-App Run template file
|
||||
|
||||
When you execute `dapr run -f .`, it uses the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications.
|
||||
When you execute `dapr run -f .`, it starts the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications.
|
||||
|
||||
You can name template file with preferred name other than the default. For example `dapr run -f ./<your-preferred-file-name>.yaml`.
|
||||
|
||||
|
@ -71,9 +72,9 @@ The run template provides two log destination fields for each application and it
|
|||
|
||||
1. `appLogDestination` : This field configures the log destination for the application. The possible values are `console`, `file` and `fileAndConsole`. The default value is `fileAndConsole` where application logs are written to both console and to a file by default.
|
||||
|
||||
2. `daprdLogDestination` : This field configures the log destination for the `daprd` process. The possible values are `console`, `file` and `fileAndConsole`. The default value is `file` where the `daprd` logs are written to a file by default.
|
||||
1. `daprdLogDestination` : This field configures the log destination for the `daprd` process. The possible values are `console`, `file` and `fileAndConsole`. The default value is `file` where the `daprd` logs are written to a file by default.
|
||||
|
||||
#### Log file format
|
||||
### Log file format
|
||||
|
||||
Logs for application and `daprd` are captured in separate files. These log files are created automatically under `.dapr/logs` directory under each application directory (`appDirPath` in the template). These log file names follow the pattern seen below:
|
||||
|
||||
|
@ -82,14 +83,90 @@ Logs for application and `daprd` are captured in separate files. These log files
|
|||
|
||||
Even if you've decided to rename your resources folder to something other than `.dapr`, the log files are written only to the `.dapr/logs` folder (created in the application directory).
|
||||
|
||||
|
||||
## Watch the demo
|
||||
|
||||
Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456):
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/s1p9MNl4VGo?start=2456" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--kubernetes-->
|
||||
|
||||
## Multi-App Run template file
|
||||
|
||||
When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` Multi-App Run template file starts in Kubernetes default namespace.
|
||||
|
||||
> **Note:** Currently, the Multi-App Run template can only start applications in the default Kubernetes namespace.
|
||||
|
||||
The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template.
|
||||
|
||||
If the `createService` field is set to `true` in the `dapr.yaml` template for an app, then the `service.yaml` file is generated in the `.dapr/deploy` folder of the app.
|
||||
|
||||
Otherwise, only the `deployment.yaml` file is generated for each app that has the `containerImage` field set.
|
||||
|
||||
The files `service.yaml` and `deployment.yaml` are used to deploy the applications in `default` namespace in Kubernetes. This feature is specifically targeted only for running multiple apps in a dev/test environment in Kubernetes.
|
||||
|
||||
You can name the template file with any preferred name other than the default. For example:
|
||||
|
||||
```bash
|
||||
dapr run -k -f ./<your-preferred-file-name>.yaml
|
||||
```
|
||||
|
||||
The following example includes some of the template properties you can customize for your applications. In the example, you can simultaneously launch 2 applications with app IDs of `nodeapp` and `pythonapp`.
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
common:
|
||||
apps:
|
||||
- appID: nodeapp
|
||||
appDirPath: ./nodeapp/
|
||||
appPort: 3000
|
||||
containerImage: ghcr.io/dapr/samples/hello-k8s-node:latest
|
||||
createService: true
|
||||
env:
|
||||
APP_PORT: 3000
|
||||
- appID: pythonapp
|
||||
appDirPath: ./pythonapp/
|
||||
containerImage: ghcr.io/dapr/samples/hello-k8s-python:latest
|
||||
```
|
||||
|
||||
> **Note:**
|
||||
> - If the `containerImage` field is not specified, `dapr run -k -f` produces an error.
|
||||
> - The `createService` field defines a basic service in Kubernetes (ClusterIP or LoadBalancer) that targets the `--app-port` specified in the template. If `createService` isn't specified, the application is not accessible from outside the cluster.
|
||||
|
||||
For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}).
|
||||
|
||||
## Logs
|
||||
|
||||
The run template provides two log destination fields for each application and its associated daprd process:
|
||||
|
||||
1. `appLogDestination` : This field configures the log destination for the application. The possible values are `console`, `file` and `fileAndConsole`. The default value is `fileAndConsole` where application logs are written to both console and to a file by default.
|
||||
|
||||
2. `daprdLogDestination` : This field configures the log destination for the `daprd` process. The possible values are `console`, `file` and `fileAndConsole`. The default value is `file` where the `daprd` logs are written to a file by default.
|
||||
|
||||
### Log file format
|
||||
|
||||
Logs for application and `daprd` are captured in separate files. These log files are created automatically under `.dapr/logs` directory under each application directory (`appDirPath` in the template). These log file names follow the pattern seen below:
|
||||
|
||||
- `<appID>_app_<timestamp>.log` (file name format for `app` log)
|
||||
- `<appID>_daprd_<timestamp>.log` (file name format for `daprd` log)
|
||||
|
||||
Even if you've decided to rename your resources folder to something other than `.dapr`, the log files are written only to the `.dapr/logs` folder (created in the application directory).
|
||||
|
||||
## Watch the demo
|
||||
|
||||
Watch [this video for an overview on Multi-App Run in Kubernetes](https://youtu.be/nWatANwaAik?si=O8XR-TUaiY0gclgO&t=1024):
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/nWatANwaAik?si=O8XR-TUaiY0gclgO&start=1024" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Learn the Multi-App Run template file structure and its properties]({{< ref multi-app-template.md >}})
|
||||
- [Try out the Multi-App Run template with the Service Invocation quickstart]({{< ref serviceinvocation-quickstart.md >}})
|
||||
- [Try out the self-hosted Multi-App Run template with the Service Invocation quickstart]({{< ref serviceinvocation-quickstart.md >}})
|
||||
- [Try out the Kubernetes Multi-App Run template with the `hello-kubernetes` tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/hello-kubernetes)
|
|
@ -7,7 +7,7 @@ description: Unpack the Multi-App Run template file and its properties
|
|||
---
|
||||
|
||||
{{% alert title="Note" color="primary" %}}
|
||||
Multi-App Run is currently a preview feature only supported in Linux/MacOS.
|
||||
Multi-App Run for **Kubernetes** is currently a preview feature.
|
||||
{{% /alert %}}
|
||||
|
||||
The Multi-App Run template file is a YAML file that you can use to run multiple applications at once. In this guide, you'll learn how to:
|
||||
|
@ -26,20 +26,53 @@ When you provide a directory path, the CLI will try to locate the Multi-App Run
|
|||
|
||||
Execute the following CLI command to read the Multi-App Run template file, named `dapr.yaml` by default:
|
||||
|
||||
{{< tabs Self-hosted Kubernetes>}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--selfhosted-->
|
||||
|
||||
```cmd
|
||||
# the template file needs to be called `dapr.yaml` by default if a directory path is given
|
||||
|
||||
dapr run -f <dir_path>
|
||||
```
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--kubernetes-->
|
||||
|
||||
```cmd
|
||||
dapr run -f -k <dir_path>
|
||||
```
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
### Execute by providing a file path
|
||||
|
||||
If the Multi-App Run template file is named something other than `dapr.yaml`, then you can provide the relative or absolute file path to the command:
|
||||
|
||||
{{< tabs Self-hosted Kubernetes>}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--selfhosted-->
|
||||
|
||||
```cmd
|
||||
dapr run -f ./path/to/<your-preferred-file-name>.yaml
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--kubernetes-->
|
||||
|
||||
```cmd
|
||||
dapr run -f -k ./path/to/<your-preferred-file-name>.yaml
|
||||
```
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## View the started applications
|
||||
|
||||
Once the multi-app template is running, you can view the started applications with the following command:
|
||||
|
@ -52,6 +85,11 @@ dapr list
|
|||
|
||||
Stop the multi-app run template anytime with either of the following commands:
|
||||
|
||||
{{< tabs Self-hosted Kubernetes>}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--selfhosted-->
|
||||
|
||||
```cmd
|
||||
# the template file needs to be called `dapr.yaml` by default if a directory path is given
|
||||
|
||||
|
@ -63,10 +101,36 @@ or:
|
|||
dapr stop -f ./path/to/<your-preferred-file-name>.yaml
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--kubernetes-->
|
||||
|
||||
```cmd
|
||||
# the template file needs to be called `dapr.yaml` by default if a directory path is given
|
||||
|
||||
dapr stop -f -k
|
||||
```
|
||||
or:
|
||||
|
||||
```cmd
|
||||
dapr stop -f -k ./path/to/<your-preferred-file-name>.yaml
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
## Template file structure
|
||||
|
||||
The Multi-App Run template file can include the following properties. Below is an example template showing two applications that are configured with some of the properties.
|
||||
|
||||
{{< tabs Self-hosted Kubernetes>}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--selfhosted-->
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
common: # optional section for variables shared across apps
|
||||
|
@ -96,19 +160,61 @@ apps:
|
|||
command: ["./backend"]
|
||||
```
|
||||
|
||||
{{% alert title="Important" color="warning" %}}
|
||||
The following rules apply for all the paths present in the template file:
|
||||
- If the path is absolute, it is used as is.
|
||||
- All relative paths under comman section should be provided relative to the template file path.
|
||||
- All relative paths under command section should be provided relative to the template file path.
|
||||
- `appDirPath` under apps section should be provided relative to the template file path.
|
||||
- All relative paths under app section should be provided relative to the appDirPath.
|
||||
- All relative paths under app section should be provided relative to the `appDirPath`.
|
||||
|
||||
{{% /alert %}}
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--kubernetes-->
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
common: # optional section for variables shared across apps
|
||||
env: # any environment variable shared across apps
|
||||
DEBUG: true
|
||||
apps:
|
||||
- appID: webapp # optional
|
||||
appDirPath: .dapr/webapp/ # REQUIRED
|
||||
appChannelAddress: 127.0.0.1 # network address where the app listens on. (optional) can be left to default value by convention.
|
||||
appProtocol: http
|
||||
appPort: 8080
|
||||
appHealthCheckPath: "/healthz"
|
||||
appLogDestination: file # (optional), can be file, console or fileAndConsole. default is fileAndConsole.
|
||||
daprdLogDestination: file # (optional), can be file, console or fileAndConsole. default is file.
|
||||
containerImage: ghcr.io/dapr/samples/hello-k8s-node:latest # (optional) URI of the container image to be used when deploying to Kubernetes dev/test environment.
|
||||
createService: true # (optional) Create a Kubernetes service for the application when deploying to dev/test environment.
|
||||
- appID: backend # optional
|
||||
appDirPath: .dapr/backend/ # REQUIRED
|
||||
appProtocol: grpc
|
||||
appPort: 3000
|
||||
unixDomainSocket: "/tmp/test-socket"
|
||||
env:
|
||||
- DEBUG: false
|
||||
```
|
||||
|
||||
The following rules apply for all the paths present in the template file:
|
||||
- If the path is absolute, it is used as is.
|
||||
- `appDirPath` under apps section should be provided relative to the template file path.
|
||||
- All relative paths under app section should be provided relative to the `appDirPath`.
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## Template properties
|
||||
|
||||
{{< tabs Self-hosted Kubernetes>}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--selfhosted-->
|
||||
|
||||
The properties for the Multi-App Run template align with the `dapr run` CLI flags, [listed in the CLI reference documentation]({{< ref "dapr-run.md#flags" >}}).
|
||||
|
||||
{{< table "table table-white table-striped table-bordered" >}}
|
||||
|
||||
| Properties | Required | Details | Example |
|
||||
|--------------------------|:--------:|--------|---------|
|
||||
|
@ -146,8 +252,66 @@ The properties for the Multi-App Run template align with the `dapr run` CLI flag
|
|||
| `appLogDestination` | N | Log destination for outputting app logs; Its value can be file, console or fileAndConsole. Default is fileAndConsole | `file`, `console`, `fileAndConsole` |
|
||||
| `daprdLogDestination` | N | Log destination for outputting daprd logs; Its value can be file, console or fileAndConsole. Default is file | `file`, `console`, `fileAndConsole` |
|
||||
|
||||
{{< /table >}}
|
||||
|
||||
## Next steps
|
||||
|
||||
Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456):
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/s1p9MNl4VGo?start=2456" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
<!--kubernetes-->
|
||||
|
||||
The properties for the Multi-App Run template align with the `dapr run -k` CLI flags, [listed in the CLI reference documentation]({{< ref "dapr-run.md#flags" >}}).
|
||||
|
||||
{{< table "table table-white table-striped table-bordered" >}}
|
||||
|
||||
| Properties | Required | Details | Example |
|
||||
|--------------------------|:--------:|--------|---------|
|
||||
| `appDirPath` | Y | Path to the your application code | `./webapp/`, `./backend/` |
|
||||
| `appID` | N | Application's app ID. If not provided, will be derived from `appDirPath` | `webapp`, `backend` |
|
||||
| `appChannelAddress` | N | The network address the application listens on. Can be left to the default value by convention. | `127.0.0.1` | `localhost` |
|
||||
| `appProtocol` | N | The protocol Dapr uses to talk to the application. | `http`, `grpc` |
|
||||
| `appPort` | N | The port your application is listening on | `8080`, `3000` |
|
||||
| `daprHTTPPort` | N | Dapr HTTP port | |
|
||||
| `daprGRPCPort` | N | Dapr GRPC port | |
|
||||
| `daprInternalGRPCPort` | N | gRPC port for the Dapr Internal API to listen on; used when parsing the value from a local DNS component | |
|
||||
| `metricsPort` | N | The port that Dapr sends its metrics information to | |
|
||||
| `unixDomainSocket` | N | Path to a unix domain socket dir mount. If specified, communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports. Not available on Windows. | `/tmp/test-socket` |
|
||||
| `profilePort` | N | The port for the profile server to listen on | |
|
||||
| `enableProfiling` | N | Enable profiling via an HTTP endpoint | |
|
||||
| `apiListenAddresses` | N | Dapr API listen addresses | |
|
||||
| `logLevel` | N | The log verbosity. | |
|
||||
| `appMaxConcurrency` | N | The concurrency level of the application; default is unlimited | |
|
||||
| `placementHostAddress` | N | | |
|
||||
| `appSSL` | N | Enable https when Dapr invokes the application | |
|
||||
| `daprHTTPMaxRequestSize` | N | Max size of the request body in MB. | |
|
||||
| `daprHTTPReadBufferSize` | N | Max size of the HTTP read buffer in KB. This also limits the maximum size of HTTP headers. The default 4 KB | |
|
||||
| `enableAppHealthCheck` | N | Enable the app health check on the application | `true`, `false` |
|
||||
| `appHealthCheckPath` | N | Path to the health check file | `/healthz` |
|
||||
| `appHealthProbeInterval` | N | Interval to probe for the health of the app in seconds
|
||||
| |
|
||||
| `appHealthProbeTimeout` | N | Timeout for app health probes in milliseconds | |
|
||||
| `appHealthThreshold` | N | Number of consecutive failures for the app to be considered unhealthy | |
|
||||
| `enableApiLogging` | N | Enable the logging of all API calls from application to Dapr | |
|
||||
| `env` | N | Map to environment variable; environment variables applied per application will overwrite environment variables shared across applications | `DEBUG`, `DAPR_HOST_ADD` |
|
||||
| `appLogDestination` | N | Log destination for outputting app logs; Its value can be file, console or fileAndConsole. Default is fileAndConsole | `file`, `console`, `fileAndConsole` |
|
||||
| `daprdLogDestination` | N | Log destination for outputting daprd logs; Its value can be file, console or fileAndConsole. Default is file | `file`, `console`, `fileAndConsole` |
|
||||
| `containerImage`| N | URI of the container image to be used when deploying to Kubernetes dev/test environment. | `ghcr.io/dapr/samples/hello-k8s-python:latest`
|
||||
| `createService`| N | Create a Kubernetes service for the application when deploying to dev/test environment. | `true`, `false` |
|
||||
|
||||
{{< /table >}}
|
||||
|
||||
## Next steps
|
||||
|
||||
Watch [this video for an overview on Multi-App Run in Kubernetes](https://youtu.be/nWatANwaAik?si=O8XR-TUaiY0gclgO&t=1024):
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/nWatANwaAik?si=O8XR-TUaiY0gclgO&start=1024" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -21,240 +21,7 @@ In this guide, you'll:
|
|||
<img src="/images/workflow-quickstart-overview.png" width=800 style="padding-bottom:15px;">
|
||||
|
||||
|
||||
{{< tabs ".NET" "Python" >}}
|
||||
|
||||
<!-- .NET -->
|
||||
{{% 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).
|
||||
<!-- IGNORE_LINKS -->
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop)
|
||||
<!-- END_IGNORE -->
|
||||
|
||||
### 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/`).
|
||||
|
||||
<img src="/images/workflow-trace-spans-zipkin.png" width=800 style="padding-bottom:15px;">
|
||||
|
||||
### 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<OrderProcessingWorkflow>();
|
||||
|
||||
// These are the activities that get invoked by the workflow(s).
|
||||
options.RegisterActivity<NotifyActivity>();
|
||||
options.RegisterActivity<ReserveInventoryActivity>();
|
||||
options.RegisterActivity<ProcessPaymentActivity>();
|
||||
options.RegisterActivity<UpdateInventoryActivity>();
|
||||
});
|
||||
};
|
||||
|
||||
//...
|
||||
|
||||
// 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<OrderPayload, OrderResult>
|
||||
{
|
||||
public override async Task<OrderResult> 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<InventoryResult>(
|
||||
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 %}}
|
||||
{{< tabs "Python" ".NET" "Java" >}}
|
||||
|
||||
<!-- Python -->
|
||||
{{% codetab %}}
|
||||
|
@ -334,9 +101,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/`).
|
||||
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:
|
||||
|
||||
<img src="/images/workflow-trace-spans-zipkin-python.png" width=900 style="padding-bottom:15px;">
|
||||
```
|
||||
docker run -d -p 9411:9411 openzipkin/zipkin
|
||||
```
|
||||
|
||||
View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`).
|
||||
|
||||
<img src="/images/workflow-trace-spans-zipkin.png" width=800 style="padding-bottom:15px;">
|
||||
|
||||
### What happened?
|
||||
|
||||
|
@ -494,8 +267,242 @@ In `workflow.py`, the workflow is defined as a class with all of its associated
|
|||
```
|
||||
{{% /codetab %}}
|
||||
|
||||
<!-- .NET -->
|
||||
{{% codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
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).
|
||||
<!-- IGNORE_LINKS -->
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop)
|
||||
<!-- END_IGNORE -->
|
||||
|
||||
### 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
|
||||
|
||||
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/`).
|
||||
|
||||
<img src="/images/workflow-trace-spans-zipkin.png" width=800 style="padding-bottom:15px;">
|
||||
|
||||
### 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<OrderProcessingWorkflow>();
|
||||
|
||||
// These are the activities that get invoked by the workflow(s).
|
||||
options.RegisterActivity<NotifyActivity>();
|
||||
options.RegisterActivity<ReserveInventoryActivity>();
|
||||
options.RegisterActivity<ProcessPaymentActivity>();
|
||||
options.RegisterActivity<UpdateInventoryActivity>();
|
||||
});
|
||||
};
|
||||
|
||||
//...
|
||||
|
||||
// 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<OrderPayload, OrderResult>
|
||||
{
|
||||
public override async Task<OrderResult> 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<InventoryResult>(
|
||||
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`
|
||||
|
||||
## Watch the demo
|
||||
|
||||
|
@ -503,6 +510,349 @@ Watch [this video to walk through the Dapr Workflow .NET demo](https://youtu.be/
|
|||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/BxiKpEmchgQ?start=2564" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
<!-- 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
|
||||
- `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
|
||||
|
||||
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/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.
|
||||
<!-- IGNORE_LINKS -->
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop)
|
||||
<!-- END_IGNORE -->
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
Navigate to the `order-processor` directory:
|
||||
|
||||
```bash
|
||||
cd workflows/java/sdk/order-processor
|
||||
```
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```bash
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
### Step 3: Run the order processor app
|
||||
|
||||
In the terminal, start the order processor app alongside a Dapr sidecar:
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
== 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
|
||||
|
||||
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/`).
|
||||
|
||||
<img src="/images/workflow-trace-spans-zipkin.png" width=800 style="padding-bottom:15px;">
|
||||
|
||||
### What happened?
|
||||
|
||||
When you ran `dapr run`:
|
||||
|
||||
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/WorkflowConsoleApp.java`
|
||||
|
||||
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
|
||||
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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `OrderProcessingWorkflow.java`
|
||||
|
||||
In `OrderProcessingWorkflow.java`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities).
|
||||
|
||||
```java
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### `activities` directory
|
||||
|
||||
The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files:
|
||||
- [`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 >}}
|
||||
|
||||
## 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?
|
||||
|
@ -515,4 +865,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" >}}
|
|
@ -1,16 +1,27 @@
|
|||
---
|
||||
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-alpha1/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 |
|
||||
|
||||
## Related links
|
||||
|
||||
[Learn more about the Alpha, Beta, and Stable lifecycle stages.]({{< ref "certification-lifecycle.md#certification-levels" >}})
|
|
@ -17,11 +17,12 @@ For CLI there is no explicit opt-in, just the version that this was first made a
|
|||
| --- | --- | --- | --- | --- |
|
||||
| **Streaming for HTTP service invocation** | Enables (partial) support for using streams in HTTP service invocation; see below for more details. | `ServiceInvocationStreaming` | [Details]({{< ref "support-preview-features.md#streaming-for-http-service-invocation" >}}) | v1.10 |
|
||||
| **Pluggable components** | Allows creating self-hosted gRPC-based components written in any language that supports gRPC. The following component APIs are supported: State stores, Pub/sub, Bindings | N/A | [Pluggable components concept]({{<ref "components-concept#pluggable-components" >}})| v1.9 |
|
||||
| **Multi-App Run** | Configure multiple Dapr applications from a single configuration file and run from a single command | `dapr run -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 |
|
||||
| **Multi-App Run for Kubernetes** | Configure multiple Dapr applications from a single configuration file and run from a single command on Kubernetes | `dapr run -k -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 |
|
||||
| **Workflows** | Author workflows as code to automate and orchestrate tasks within your application, like messaging, state management, and failure handling | N/A | [Workflows concept]({{< ref "components-concept#workflows" >}})| v1.10 |
|
||||
| **Cryptography** | Encrypt or decrypt data without having to manage secrets keys | N/A | [Cryptography concept]({{< ref "components-concept#cryptography" >}})| v1.11 |
|
||||
| **Service invocation for non-Dapr endpoints** | Allow the invocation of non-Dapr endpoints by Dapr using the [Service invocation API]({{< ref service_invocation_api.md >}}). Read ["How-To: Invoke Non-Dapr Endpoints using HTTP"]({{< ref howto-invoke-non-dapr-endpoints.md >}}) for more information. | N/A | [Service invocation API]({{< ref service_invocation_api.md >}}) | v1.11 |
|
||||
| **Actor State TTL** | Allow actors to save records to state stores with Time To Live (TTL) set to automatically clean up old data. In its current implementation, actor state with TTL may not be reflected correctly by clients, read [Actor State Transactions]({{< ref actors_api.md >}}) for more information. | `ActorStateTTL` | [Actor State Transactions]({{< ref actors_api.md >}}) | v1.11 |
|
||||
| **Transactional Outbox** | Allows state operations for inserts and updates to be published to a configured pub/sub topic using a single transaction across the state store and the pub/sub | N/A | [Transactional Outbox Feature]({{< ref howto-outbox.md >}}) | v1.12 |
|
||||
|
||||
### Streaming for HTTP service invocation
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ type: docs
|
|||
title: "Error codes returned by APIs"
|
||||
linkTitle: "Error codes"
|
||||
description: "Detailed reference of the Dapr API error codes"
|
||||
weight: 1200
|
||||
weight: 1300
|
||||
---
|
||||
|
||||
For http calls made to Dapr runtime, when an error is encountered, an error json is returned in http response body. The json contains an error code and an descriptive error message, e.g.
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
type: docs
|
||||
title: "Placement API reference"
|
||||
linkTitle: "Placement API"
|
||||
description: "Detailed documentation on the Placement API"
|
||||
weight: 1200
|
||||
---
|
||||
|
||||
Dapr has an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default.
|
||||
|
||||
To enable the placement metadata in self-hosted mode you can either set`DAPR_PLACEMENT_METADATA_ENABLED` environment variable or `metadata-enabled` command line args on the Placement service to `true` to. See [how to run the Placement service in self-hosted mode]({{< ref "self-hosted-no-docker.md#enable-actors" >}}).
|
||||
|
||||
If you are using Helm for deployment of the Placement service on Kubernetes then to enable the placement metadata, set `dapr_placement.metadataEnabled` to `true`.
|
||||
|
||||
## Usecase
|
||||
|
||||
The placement table API can be used for retrieving the current placement table, which contains all the actors registered. This can be helpful for debugging and allows tools to extract and present information about actors.
|
||||
|
||||
## HTTP Request
|
||||
|
||||
```
|
||||
GET http://localhost:<healthzPort>/placement/state
|
||||
```
|
||||
|
||||
## HTTP Response Codes
|
||||
|
||||
Code | Description
|
||||
---- | -----------
|
||||
200 | Placement tables information returned
|
||||
500 | Placement could not return the placement tables information
|
||||
|
||||
## HTTP Response Body
|
||||
|
||||
**Placement tables API Response Object**
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
tableVersion | int | The placement table version
|
||||
hostList | [Actor Host Info](#actorhostinfo)[] | A json array of registered actors host info.
|
||||
|
||||
<a id="actorhostinfo"></a>**Actor Host Info**
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
name | string | The host:port address of the actor.
|
||||
appId | string | app id.
|
||||
actorTypes | json string array | List of actor types it hosts.
|
||||
updatedAt | timestamp | Timestamp of the actor registered/updated.
|
||||
|
||||
## Examples
|
||||
|
||||
```shell
|
||||
curl localhost:8080/placement/state
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"hostList": [{
|
||||
"name": "198.18.0.1:49347",
|
||||
"appId": "actor1",
|
||||
"actorTypes": ["testActorType1", "testActorType3"],
|
||||
"updatedAt": 1690274322325260000
|
||||
},
|
||||
{
|
||||
"name": "198.18.0.2:49347",
|
||||
"appId": "actor2",
|
||||
"actorTypes": ["testActorType2"],
|
||||
"updatedAt": 1690274322325260000
|
||||
},
|
||||
{
|
||||
"name": "198.18.0.3:49347",
|
||||
"appId": "actor2",
|
||||
"actorTypes": ["testActorType2"],
|
||||
"updatedAt": 1690274322325260000
|
||||
}
|
||||
],
|
||||
"tableVersion": 1
|
||||
}
|
||||
```
|
|
@ -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
|
||||
|
@ -13,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/<workflowComponentName>/<workflowName>/start[?instanceID=<instanceID>]
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<workflowName>/start[?instanceID=<instanceID>]
|
||||
```
|
||||
|
||||
Note that workflow instance IDs can only contain alphanumeric characters, underscores, and dashes.
|
||||
|
@ -53,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/<workflowComponentName>/<instanceId>/terminate
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceId>/terminate
|
||||
```
|
||||
|
||||
### URL parameters
|
||||
|
@ -80,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/<workflowComponentName>/<instanceID>/raiseEvent/<eventName>
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceID>/raiseEvent/<eventName>
|
||||
```
|
||||
|
||||
{{% alert title="Note" color="primary" %}}
|
||||
|
@ -113,7 +117,7 @@ None.
|
|||
Pause a running workflow instance.
|
||||
|
||||
```
|
||||
POST http://localhost:3500/v1.0-alpha1/workflows/<workflowComponentName>/<instanceId>/pause
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceId>/pause
|
||||
```
|
||||
|
||||
### URL parameters
|
||||
|
@ -140,7 +144,7 @@ None.
|
|||
Resume a paused workflow instance.
|
||||
|
||||
```
|
||||
POST http://localhost:3500/v1.0-alpha1/workflows/<workflowComponentName>/<instanceId>/resume
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceId>/resume
|
||||
```
|
||||
|
||||
### URL parameters
|
||||
|
@ -167,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/<workflowComponentName>/<instanceId>/purge
|
||||
POST http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceId>/purge
|
||||
```
|
||||
|
||||
### URL parameters
|
||||
|
@ -194,7 +198,7 @@ None.
|
|||
Get information about a given workflow instance.
|
||||
|
||||
```
|
||||
GET http://localhost:3500/v1.0-alpha1/workflows/<workflowComponentName>/<instanceId>
|
||||
GET http://localhost:3500/v1.0-beta1/workflows/<workflowComponentName>/<instanceId>
|
||||
```
|
||||
|
||||
### URL parameters
|
||||
|
|
|
@ -39,7 +39,7 @@ This table is meant to help users understand the equivalent options for running
|
|||
| `--profiling-port` | `--profiling-port` | | not supported | The port for the profile server (default `7777`) |
|
||||
| `--app-protocol` | `--app-protocol` | `-P` | `dapr.io/app-protocol` | Configures the protocol Dapr uses to communicate with your app. Valid options are `http`, `grpc`, `https` (HTTP with TLS), `grpcs` (gRPC with TLS), `h2c` (HTTP/2 Cleartext). Note that Dapr does not validate TLS certificates presented by the app. Default is `http` |
|
||||
| `--enable-app-health-check` | `--enable-app-health-check` | | `dapr.io/enable-app-health-check` | Boolean that enables the [health checks]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `false`. |
|
||||
| `--app-health-check-path` | `--app-health-check-path` | | `dapr.io/app-health-check-path` | Path that Dapr invokes for health probes when the app channel is HTTP (this value is ignored if the app channel is using gRPC). Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `/health`. |
|
||||
| `--app-health-check-path` | `--app-health-check-path` | | `dapr.io/app-health-check-path` | Path that Dapr invokes for health probes when the app channel is HTTP (this value is ignored if the app channel is using gRPC). Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `/healthz`. |
|
||||
| `--app-health-probe-interval` | `--app-health-probe-interval` | | `dapr.io/app-health-probe-interval` | Number of *seconds* between each health probe. Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `5` |
|
||||
| `--app-health-probe-timeout` | `--app-health-probe-timeout` | | `dapr.io/app-health-probe-timeout` | Timeout in *milliseconds* for health probe requests. Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `500` |
|
||||
| `--app-health-threshold` | `--app-health-threshold` | | `dapr.io/app-health-threshold"` | Max number of consecutive failures before the app is considered unhealthy. Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `3` |
|
||||
|
|
|
@ -44,6 +44,9 @@ dapr init [flags]
|
|||
| N/A | DAPR_HELM_REPO_USERNAME | A username for a private Helm chart | The username required to access the private Dapr Helm chart. If it can be accessed publicly, this env variable does not need to be set|
|
||||
| N/A | DAPR_HELM_REPO_PASSWORD | A password for a private Helm chart |The password required to access the private Dapr Helm chart. If it can be accessed publicly, this env variable does not need to be set| |
|
||||
| `--container-runtime` | | `docker` | Used to pass in a different container runtime other than Docker. Supported container runtimes are: `docker`, `podman` |
|
||||
| `--dev` | | | Creates Redis and Zipkin deployments when run in Kubernetes. |
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
#### Self hosted environment
|
||||
|
|
|
@ -50,6 +50,7 @@ dapr run [flags] [command]
|
|||
| `--unix-domain-socket`, `-u` | | | Path to a unix domain socket dir mount. If specified, communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports. Not available on Windows. |
|
||||
| `--dapr-http-max-request-size` | | `4` | Max size of the request body in MB. |
|
||||
| `--dapr-http-read-buffer-size` | | `4` | Max size of the HTTP read buffer in KB. This also limits the maximum size of HTTP headers. The default 4 KB |
|
||||
| `--kubernetes`, `-k` | | | Running Dapr on Kubernetes, and used for [Multi-App Run template files on Kubernetes]({{< ref multi-app-dapr-run >}}). |
|
||||
| `--components-path`, `-d` | | Linux/Mac: `$HOME/.dapr/components` <br/>Windows: `%USERPROFILE%\.dapr\components` | **Deprecated** in favor of `--resources-path` |
|
||||
|
||||
### Examples
|
||||
|
@ -81,4 +82,10 @@ dapr run --app-id myapp --app-port 3000 --enable-api-logging -- node myapp.js
|
|||
|
||||
# Pass multiple resource paths
|
||||
dapr run --app-id myapp --resources-path path1 --resources-path path2
|
||||
```
|
||||
|
||||
# Run the multi-app run template file
|
||||
dapr run -f dapr.yaml
|
||||
|
||||
# Run the multi-app run template file on Kubernetes
|
||||
dapr run -k -f dapr.yaml
|
||||
```
|
|
@ -26,4 +26,5 @@ The following table lists the environment variables used by the Dapr runtime, CL
|
|||
| OTEL_EXPORTER_OTLP_INSECURE | OpenTelemetry Tracing | Sets the connection to the endpoint as unencrypted. (`true`, `false`) |
|
||||
| OTEL_EXPORTER_OTLP_PROTOCOL | OpenTelemetry Tracing | The OTLP protocol to use Transport protocol. (`grpc`, `http/protobuf`, `http/json`) |
|
||||
| DAPR_COMPONENTS_SOCKETS_FOLDER | Dapr runtime and the .NET, Go, and Java pluggable component SDKs | The location or path where Dapr looks for Pluggable Components Unix Domain Socket files. If unset this location defaults to `/tmp/dapr-components-sockets` |
|
||||
| DAPR_COMPONENTS_SOCKETS_EXTENSION | .NET and Java pluggable component SDKs | A per-SDK configuration that indicates the default file extension applied to socket files created by the SDKs. Not a Dapr-enforced behavior. |
|
||||
| DAPR_COMPONENTS_SOCKETS_EXTENSION | .NET and Java pluggable component SDKs | A per-SDK configuration that indicates the default file extension applied to socket files created by the SDKs. Not a Dapr-enforced behavior. |
|
||||
| DAPR_PLACEMENT_METADATA_ENABLED | Dapr placement | Enable an endpoint for the Placement service that exposes placement table information on actor usage. Set to `true` to enable in self-hosted mode. [Learn more about the Placement API]({{< ref placement_api.md >}}) |
|
Binary file not shown.
After Width: | Height: | Size: 367 KiB |
Loading…
Reference in New Issue