diff --git a/daprdocs/content/en/concepts/building-blocks-concept.md b/daprdocs/content/en/concepts/building-blocks-concept.md
index 4719626f3..1841dd584 100644
--- a/daprdocs/content/en/concepts/building-blocks-concept.md
+++ b/daprdocs/content/en/concepts/building-blocks-concept.md
@@ -28,5 +28,5 @@ Dapr provides the following building blocks:
| [**Secrets**]({{< ref "secrets-overview.md" >}}) | `/v1.0/secrets` | Dapr provides a secrets building block API and integrates with secret stores such as public cloud stores, local stores and Kubernetes to store the secrets. Services can call the secrets API to retrieve secrets, for example to get a connection string to a database.
| [**Configuration**]({{< ref "configuration-api-overview.md" >}}) | `/v1.0/configuration` | The Configuration API enables you to retrieve and subscribe to application configuration items for supported configuration stores. This enables an application to retrieve specific configuration information, for example, at start up or when configuration changes are made in the store.
| [**Distributed lock**]({{< ref "distributed-lock-api-overview.md" >}}) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource so that multiple instances of an application can access the resource without conflicts and provide consistency guarantees.
-| [**Workflows**]({{< ref "workflow-overview.md" >}}) | `/v1.0-alpha1/workflow` | The Workflow API enables you to define long running, persistent processes or data flows that span multiple microservices using Dapr workflows or workflow components. The Workflow API can be combined with other Dapr API building blocks. For example, a workflow can call another service with service invocation or retrieve secrets, providing flexibility and portability.
+| [**Workflows**]({{< ref "workflow-overview.md" >}}) | `/v1.0-beta1/workflow` | The Workflow API enables you to define long running, persistent processes or data flows that span multiple microservices using Dapr workflows or workflow components. The Workflow API can be combined with other Dapr API building blocks. For example, a workflow can call another service with service invocation or retrieve secrets, providing flexibility and portability.
| [**Cryptography**]({{< ref "cryptography-overview.md" >}}) | `/v1.0-alpha1/crypto` | The Cryptography API enables you to perform cryptographic operations, such as encrypting and decrypting messages, without exposing keys to your application.
\ No newline at end of file
diff --git a/daprdocs/content/en/concepts/dapr-services/placement.md b/daprdocs/content/en/concepts/dapr-services/placement.md
index 5cb1d9995..7db47a374 100644
--- a/daprdocs/content/en/concepts/dapr-services/placement.md
+++ b/daprdocs/content/en/concepts/dapr-services/placement.md
@@ -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 >}})
\ No newline at end of file
diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md
index 41c9ac23b..ed6b72cc3 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.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.
diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md
new file mode 100644
index 000000000..c53930f2a
--- /dev/null
+++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md
@@ -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.
+
+
+
+## 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: ""
+ - 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: ""
+```
+
+#### 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: ""
+ - 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):
+
+
+
diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md
index afc6bd5f1..89c31dc5e 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md
@@ -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:
diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md
index 6caa7f3ff..ada6335ec 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md
@@ -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 %}}
+
+
+
+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
{{% codetab %}}
-
+
-Define the workflow activities you'd like your workflow to perform. Activities are a function definition and can take inputs and outputs. The following example creates a counter (activity) called `hello_act` that notifies users of the current counter value. `hello_act` is a function derived from a class called `WorkflowActivityContext`.
+Define the workflow activities you'd like your workflow to perform. 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 %}}
+
+
+
+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 %}}
-
+
-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 %}}
-
-
-
-[In the following `Program.cs` example](https://github.com/dapr/dotnet-sdk/blob/master/examples/Workflow/WorkflowConsoleApp/Program.cs), for a basic ASP.NET order processing application using the .NET SDK, your project code would include:
-
-- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities
-- A builder with an extension method called `AddDaprWorkflow`
- - This will allow you to register workflows and workflow activities (tasks that workflows can schedule)
-- HTTP API calls
- - One for submitting a new order
- - One for checking the status of an existing order
-
-```csharp
-using Dapr.Workflow;
-//...
-
-// Dapr Workflows are registered as part of the service configuration
-builder.Services.AddDaprWorkflow(options =>
-{
- // Note that it's also possible to register a lambda function as the workflow
- // or activity implementation instead of a class.
- options.RegisterWorkflow();
-
- // These are the activities that get invoked by the workflow(s).
- options.RegisterActivity();
- options.RegisterActivity();
- options.RegisterActivity();
-});
-
-WebApplication app = builder.Build();
-
-// POST starts new order workflow instance
-app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) =>
-{
- if (orderInfo?.Name == null)
- {
- return Results.BadRequest(new
- {
- message = "Order data was missing from the request",
- example = new OrderPayload("Paperclips", 99.95),
- });
- }
-
-//...
-});
-
-// GET fetches state for order workflow to report status
-app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) =>
-{
- WorkflowState state = await client.GetWorkflowStateAsync(orderId, true);
- if (!state.Exists)
- {
- return Results.NotFound($"No order with ID = '{orderId}' was found.");
- }
-
- var httpResponsePayload = new
- {
- details = state.ReadInputAs(),
- status = state.RuntimeStatus.ToString(),
- result = state.ReadOutputAs(),
- };
-
-//...
-}).WithName("GetOrderInfoEndpoint");
-
-app.Run();
-```
-
-{{% /codetab %}}
+{{< tabs Python ".NET" Java >}}
{{% codetab %}}
@@ -356,6 +364,124 @@ if __name__ == '__main__':
```
+{{% /codetab %}}
+
+{{% codetab %}}
+
+
+
+[In the following `Program.cs` example](https://github.com/dapr/dotnet-sdk/blob/master/examples/Workflow/WorkflowConsoleApp/Program.cs), for a basic ASP.NET order processing application using the .NET SDK, your project code would include:
+
+- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities
+- A builder with an extension method called `AddDaprWorkflow`
+ - This will allow you to register workflows and workflow activities (tasks that workflows can schedule)
+- HTTP API calls
+ - One for submitting a new order
+ - One for checking the status of an existing order
+
+```csharp
+using Dapr.Workflow;
+//...
+
+// Dapr Workflows are registered as part of the service configuration
+builder.Services.AddDaprWorkflow(options =>
+{
+ // Note that it's also possible to register a lambda function as the workflow
+ // or activity implementation instead of a class.
+ options.RegisterWorkflow();
+
+ // These are the activities that get invoked by the workflow(s).
+ options.RegisterActivity();
+ options.RegisterActivity();
+ options.RegisterActivity();
+});
+
+WebApplication app = builder.Build();
+
+// POST starts new order workflow instance
+app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) =>
+{
+ if (orderInfo?.Name == null)
+ {
+ return Results.BadRequest(new
+ {
+ message = "Order data was missing from the request",
+ example = new OrderPayload("Paperclips", 99.95),
+ });
+ }
+
+//...
+});
+
+// GET fetches state for order workflow to report status
+app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) =>
+{
+ WorkflowState state = await client.GetWorkflowStateAsync(orderId, true);
+ if (!state.Exists)
+ {
+ return Results.NotFound($"No order with ID = '{orderId}' was found.");
+ }
+
+ var httpResponsePayload = new
+ {
+ details = state.ReadInputAs(),
+ status = state.RuntimeStatus.ToString(),
+ result = state.ReadOutputAs(),
+ };
+
+//...
+}).WithName("GetOrderInfoEndpoint");
+
+app.Run();
+```
+
+{{% /codetab %}}
+
+{{% codetab %}}
+
+
+
+[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)
diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md
index 99cb87c9b..fb7ad9d57 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md
@@ -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 >}}
-
-
-{{% codetab %}}
-
-Manage your workflow within your code. In the `OrderProcessingWorkflow` example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code. You can now start, terminate, and get information about a running workflow:
-
-```csharp
-string orderId = "exampleOrderId";
-string workflowComponent = "dapr";
-string workflowName = "OrderProcessingWorkflow";
-OrderPayload input = new OrderPayload("Paperclips", 99.95);
-Dictionary workflowOptions; // This is an optional parameter
-
-// Start the workflow. This returns back a "StartWorkflowResponse" which contains the instance ID for the particular workflow instance.
-StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions);
-
-// Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more!
-GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, workflowName);
-
-// Terminate the workflow
-await daprClient.TerminateWorkflowAsync(orderId, workflowComponent);
-
-// Raise an event (an incoming purchase order) that your workflow will wait for. This returns the item waiting to be purchased.
-await daprClient.RaiseWorkflowEventAsync(orderId, workflowComponent, workflowName, input);
-
-// Pause
-await daprClient.PauseWorkflowAsync(orderId, workflowComponent);
-
-// Resume
-await daprClient.ResumeWorkflowAsync(orderId, workflowComponent);
-
-// Purge
-await daprClient.PurgeWorkflowAsync(orderId, workflowComponent);
-```
-
-{{% /codetab %}}
+{{< tabs Python ".NET" Java HTTP >}}
{{% codetab %}}
@@ -95,6 +63,107 @@ d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponen
{{% /codetab %}}
+
+{{% codetab %}}
+
+Manage your workflow within your code. In the `OrderProcessingWorkflow` example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code. You can now start, terminate, and get information about a running workflow:
+
+```csharp
+string orderId = "exampleOrderId";
+string workflowComponent = "dapr";
+string workflowName = "OrderProcessingWorkflow";
+OrderPayload input = new OrderPayload("Paperclips", 99.95);
+Dictionary workflowOptions; // This is an optional parameter
+
+// Start the workflow. This returns back a "StartWorkflowResponse" which contains the instance ID for the particular workflow instance.
+StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions);
+
+// Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more!
+GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, 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 %}}
+
+
+{{% 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 %}}
+
{{% 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///raiseEvent/
+POST http://localhost:3500/v1.0-beta1/workflows///raiseEvent/
```
> An `eventName` can be any function.
@@ -134,13 +203,13 @@ POST http://localhost:3500/v1.0-alpha1/workflows//}}).
@@ -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 >}})
diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md
index da8bf0a44..18ec9110b 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md
@@ -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)
\ No newline at end of file
diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md
index c8e3de131..e957ade3d 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md
@@ -6,6 +6,10 @@ weight: 2000
description: "Learn more about the Dapr Workflow features and concepts"
---
+{{% alert title="Note" color="primary" %}}
+Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}).
+{{% /alert %}}
+
Now that you've learned about the [workflow building block]({{< ref workflow-overview.md >}}) at a high level, let's deep dive into the features and concepts included with the Dapr Workflow engine and SDKs. Dapr Workflow exposes several core features and concepts which are common across all supported languages.
{{% alert title="Note" color="primary" %}}
diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md
index 9f70500c0..923bc3794 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md
@@ -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)
diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md
index 4ff10782b..49b5fc2db 100644
--- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md
+++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md
@@ -25,42 +25,7 @@ While the pattern is simple, there are many complexities hidden in the implement
Dapr Workflow solves these complexities by allowing you to implement the task chaining pattern concisely as a simple function in the programming language of your choice, as shown in the following example.
-{{< tabs ".NET" Python >}}
-
-{{% codetab %}}
-
-
-```csharp
-// Expotential backoff retry policy that survives long outages
-var retryOptions = new WorkflowTaskOptions
-{
- RetryPolicy = new WorkflowRetryPolicy(
- firstRetryInterval: TimeSpan.FromMinutes(1),
- backoffCoefficient: 2.0,
- maxRetryInterval: TimeSpan.FromHours(1),
- maxNumberOfAttempts: 10),
-};
-
-try
-{
- var result1 = await context.CallActivityAsync("Step1", wfInput, retryOptions);
- var result2 = await context.CallActivityAsync("Step2", result1, retryOptions);
- var result3 = await context.CallActivityAsync("Step3", result2, retryOptions);
- return string.Join(", ", result4);
-}
-catch (TaskFailedException) // Task failures are surfaced as TaskFailedException
-{
- // Retries expired - apply custom compensation logic
- await context.CallActivityAsync("MyCompensation", options: retryOptions);
- throw;
-}
-```
-
-{{% alert title="Note" color="primary" %}}
-In the example above, `"Step1"`, `"Step2"`, `"Step3"`, and `"MyCompensation"` represent workflow activities, which are functions in your code that actually implement the steps of the workflow. For brevity, these activity implementations are left out of this example.
-{{% /alert %}}
-
-{{% /codetab %}}
+{{< tabs Python ".NET" Java >}}
{{% codetab %}}
@@ -103,9 +68,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 %}}
+
+
+```csharp
+// Expotential backoff retry policy that survives long outages
+var retryOptions = new WorkflowTaskOptions
+{
+ RetryPolicy = new WorkflowRetryPolicy(
+ firstRetryInterval: TimeSpan.FromMinutes(1),
+ backoffCoefficient: 2.0,
+ maxRetryInterval: TimeSpan.FromHours(1),
+ maxNumberOfAttempts: 10),
+};
+
+try
+{
+ var result1 = await context.CallActivityAsync("Step1", wfInput, retryOptions);
+ var result2 = await context.CallActivityAsync("Step2", result1, retryOptions);
+ var result3 = await context.CallActivityAsync("Step3", result2, retryOptions);
+ return string.Join(", ", result4);
+}
+catch (TaskFailedException) // Task failures are surfaced as TaskFailedException
+{
+ // Retries expired - apply custom compensation logic
+ await context.CallActivityAsync("MyCompensation", options: retryOptions);
+ throw;
+}
+```
+
+> **Note** In the example above, `"Step1"`, `"Step2"`, `"Step3"`, and `"MyCompensation"` represent workflow activities, which are functions in your code that actually implement the steps of the workflow. For brevity, these activity implementations are left out of this example.
+
+{{% /codetab %}}
+
+{{% codetab %}}
+
+
+```java
+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 %}}
-
-
-```csharp
-// Get a list of N work items to process in parallel.
-object[] workBatch = await context.CallActivityAsync