Skip to content

Commit 4d57fbe

Browse files
authored
Merge pull request #3662 from hhunter-ms/issue_3567
[docs] Workflow docs for Java SDK
2 parents 87ee428 + c905d41 commit 4d57fbe

File tree

10 files changed

+1219
-503
lines changed

10 files changed

+1219
-503
lines changed

daprdocs/content/en/concepts/building-blocks-concept.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ Dapr provides the following building blocks:
2828
| [**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.
2929
| [**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.
3030
| [**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.
31-
| [**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.
31+
| [**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.
3232
| [**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.

daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md

Lines changed: 223 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ weight: 5000
66
description: "Learn how to develop and author workflows"
77
---
88

9+
{{% alert title="Note" color="primary" %}}
10+
Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}).
11+
{{% /alert %}}
12+
913
This article provides a high-level overview of how to author workflows that are executed by the Dapr Workflow engine.
1014

1115
{{% alert title="Note" color="primary" %}}
@@ -30,7 +34,25 @@ The Dapr sidecar doesn’t load any workflow definitions. Rather, the sidecar si
3034

3135
[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.
3236

33-
{{< tabs ".NET" Python >}}
37+
{{< tabs Python ".NET" Java >}}
38+
39+
{{% codetab %}}
40+
41+
<!--python-->
42+
43+
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`.
44+
45+
```python
46+
def hello_act(ctx: WorkflowActivityContext, input):
47+
global counter
48+
counter += input
49+
print(f'New counter value is: {counter}!', flush=True)
50+
```
51+
52+
[See the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59)
53+
54+
55+
{{% /codetab %}}
3456

3557
{{% codetab %}}
3658

@@ -102,29 +124,76 @@ public class ProcessPaymentActivity : WorkflowActivity<PaymentRequest, object>
102124

103125
{{% codetab %}}
104126

105-
<!--python-->
127+
<!--java-->
106128

107-
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`.
129+
Define the workflow activities you'd like your workflow to perform. Activities are wrapped in the public `DemoWorkflowActivity` class, which implements the workflow activities.
108130

109-
```python
110-
def hello_act(ctx: WorkflowActivityContext, input):
111-
global counter
112-
counter += input
113-
print(f'New counter value is: {counter}!', flush=True)
114-
```
131+
```java
132+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
133+
public class DemoWorkflowActivity implements WorkflowActivity {
115134

116-
[See the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59)
135+
@Override
136+
public DemoActivityOutput run(WorkflowActivityContext ctx) {
137+
Logger logger = LoggerFactory.getLogger(DemoWorkflowActivity.class);
138+
logger.info("Starting Activity: " + ctx.getName());
117139

140+
var message = ctx.getInput(DemoActivityInput.class).getMessage();
141+
var newMessage = message + " World!, from Activity";
142+
logger.info("Message Received from input: " + message);
143+
logger.info("Sending message to output: " + newMessage);
144+
145+
logger.info("Sleeping for 5 seconds to simulate long running operation...");
146+
147+
try {
148+
TimeUnit.SECONDS.sleep(5);
149+
} catch (InterruptedException e) {
150+
throw new RuntimeException(e);
151+
}
152+
153+
154+
logger.info("Activity finished");
155+
156+
var output = new DemoActivityOutput(message, newMessage);
157+
logger.info("Activity returned: " + output);
158+
159+
return output;
160+
}
161+
}
162+
```
163+
164+
[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)
118165

119166
{{% /codetab %}}
120167

168+
121169
{{< /tabs >}}
122170

123171
## Write the workflow
124172

125173
Next, register and call the activites in a workflow.
126174

127-
{{< tabs ".NET" Python >}}
175+
{{< tabs Python ".NET" Java >}}
176+
177+
{{% codetab %}}
178+
179+
<!--python-->
180+
181+
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.
182+
183+
```python
184+
def hello_world_wf(ctx: DaprWorkflowContext, input):
185+
print(f'{input}')
186+
yield ctx.call_activity(hello_act, input=1)
187+
yield ctx.call_activity(hello_act, input=10)
188+
yield ctx.wait_for_external_event("event1")
189+
yield ctx.call_activity(hello_act, input=100)
190+
yield ctx.call_activity(hello_act, input=1000)
191+
```
192+
193+
[See the `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51)
194+
195+
196+
{{% /codetab %}}
128197

129198
{{% codetab %}}
130199

@@ -171,103 +240,42 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo
171240

172241
{{% codetab %}}
173242

174-
<!--python-->
175-
176-
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.
177-
178-
```python
179-
def hello_world_wf(ctx: DaprWorkflowContext, input):
180-
print(f'{input}')
181-
yield ctx.call_activity(hello_act, input=1)
182-
yield ctx.call_activity(hello_act, input=10)
183-
yield ctx.wait_for_external_event("event1")
184-
yield ctx.call_activity(hello_act, input=100)
185-
yield ctx.call_activity(hello_act, input=1000)
186-
```
187-
188-
[See the `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51)
189-
190-
191-
{{% /codetab %}}
243+
<!--java-->
192244

193-
{{< /tabs >}}
245+
Next, register the workflow with the `WorkflowRuntimeBuilder` and start the workflow runtime.
194246

195-
## Write the application
247+
```java
248+
public class DemoWorkflowWorker {
196249

197-
Finally, compose the application using the workflow.
250+
public static void main(String[] args) throws Exception {
198251

199-
{{< tabs ".NET" Python >}}
252+
// Register the Workflow with the builder.
253+
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoWorkflow.class);
254+
builder.registerActivity(DemoWorkflowActivity.class);
200255

201-
{{% codetab %}}
202-
203-
<!--csharp-->
204-
205-
[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:
206-
207-
- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities
208-
- A builder with an extension method called `AddDaprWorkflow`
209-
- This will allow you to register workflows and workflow activities (tasks that workflows can schedule)
210-
- HTTP API calls
211-
- One for submitting a new order
212-
- One for checking the status of an existing order
213-
214-
```csharp
215-
using Dapr.Workflow;
216-
//...
256+
// Build and then start the workflow runtime pulling and executing tasks
257+
try (WorkflowRuntime runtime = builder.build()) {
258+
System.out.println("Start workflow runtime");
259+
runtime.start();
260+
}
217261

218-
// Dapr Workflows are registered as part of the service configuration
219-
builder.Services.AddDaprWorkflow(options =>
220-
{
221-
// Note that it's also possible to register a lambda function as the workflow
222-
// or activity implementation instead of a class.
223-
options.RegisterWorkflow<OrderProcessingWorkflow>();
262+
System.exit(0);
263+
}
264+
}
265+
```
224266

225-
// These are the activities that get invoked by the workflow(s).
226-
options.RegisterActivity<NotifyActivity>();
227-
options.RegisterActivity<ReserveInventoryActivity>();
228-
options.RegisterActivity<ProcessPaymentActivity>();
229-
});
267+
[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)
230268

231-
WebApplication app = builder.Build();
232269

233-
// POST starts new order workflow instance
234-
app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) =>
235-
{
236-
if (orderInfo?.Name == null)
237-
{
238-
return Results.BadRequest(new
239-
{
240-
message = "Order data was missing from the request",
241-
example = new OrderPayload("Paperclips", 99.95),
242-
});
243-
}
244-
245-
//...
246-
});
247-
248-
// GET fetches state for order workflow to report status
249-
app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) =>
250-
{
251-
WorkflowState state = await client.GetWorkflowStateAsync(orderId, true);
252-
if (!state.Exists)
253-
{
254-
return Results.NotFound($"No order with ID = '{orderId}' was found.");
255-
}
270+
{{% /codetab %}}
256271

257-
var httpResponsePayload = new
258-
{
259-
details = state.ReadInputAs<OrderPayload>(),
260-
status = state.RuntimeStatus.ToString(),
261-
result = state.ReadOutputAs<OrderResult>(),
262-
};
272+
{{< /tabs >}}
263273

264-
//...
265-
}).WithName("GetOrderInfoEndpoint");
274+
## Write the application
266275

267-
app.Run();
268-
```
276+
Finally, compose the application using the workflow.
269277

270-
{{% /codetab %}}
278+
{{< tabs Python ".NET" Java >}}
271279

272280
{{% codetab %}}
273281

@@ -356,6 +364,124 @@ if __name__ == '__main__':
356364
```
357365

358366

367+
{{% /codetab %}}
368+
369+
{{% codetab %}}
370+
371+
<!--csharp-->
372+
373+
[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:
374+
375+
- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities
376+
- A builder with an extension method called `AddDaprWorkflow`
377+
- This will allow you to register workflows and workflow activities (tasks that workflows can schedule)
378+
- HTTP API calls
379+
- One for submitting a new order
380+
- One for checking the status of an existing order
381+
382+
```csharp
383+
using Dapr.Workflow;
384+
//...
385+
386+
// Dapr Workflows are registered as part of the service configuration
387+
builder.Services.AddDaprWorkflow(options =>
388+
{
389+
// Note that it's also possible to register a lambda function as the workflow
390+
// or activity implementation instead of a class.
391+
options.RegisterWorkflow<OrderProcessingWorkflow>();
392+
393+
// These are the activities that get invoked by the workflow(s).
394+
options.RegisterActivity<NotifyActivity>();
395+
options.RegisterActivity<ReserveInventoryActivity>();
396+
options.RegisterActivity<ProcessPaymentActivity>();
397+
});
398+
399+
WebApplication app = builder.Build();
400+
401+
// POST starts new order workflow instance
402+
app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) =>
403+
{
404+
if (orderInfo?.Name == null)
405+
{
406+
return Results.BadRequest(new
407+
{
408+
message = "Order data was missing from the request",
409+
example = new OrderPayload("Paperclips", 99.95),
410+
});
411+
}
412+
413+
//...
414+
});
415+
416+
// GET fetches state for order workflow to report status
417+
app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) =>
418+
{
419+
WorkflowState state = await client.GetWorkflowStateAsync(orderId, true);
420+
if (!state.Exists)
421+
{
422+
return Results.NotFound($"No order with ID = '{orderId}' was found.");
423+
}
424+
425+
var httpResponsePayload = new
426+
{
427+
details = state.ReadInputAs<OrderPayload>(),
428+
status = state.RuntimeStatus.ToString(),
429+
result = state.ReadOutputAs<OrderResult>(),
430+
};
431+
432+
//...
433+
}).WithName("GetOrderInfoEndpoint");
434+
435+
app.Run();
436+
```
437+
438+
{{% /codetab %}}
439+
440+
{{% codetab %}}
441+
442+
<!--java-->
443+
444+
[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:
445+
446+
- A Java package called `io.dapr.workflows.client` to receive the Java SDK client capabilities.
447+
- An import of `io.dapr.workflows.Workflow`
448+
- The `DemoWorkflow` class which extends `Workflow`
449+
- Creating the workflow with input and output.
450+
- API calls. In the example below, these calls start and call the workflow activities.
451+
452+
```java
453+
package io.dapr.examples.workflows;
454+
455+
import com.microsoft.durabletask.CompositeTaskFailedException;
456+
import com.microsoft.durabletask.Task;
457+
import com.microsoft.durabletask.TaskCanceledException;
458+
import io.dapr.workflows.Workflow;
459+
import io.dapr.workflows.WorkflowStub;
460+
461+
import java.time.Duration;
462+
import java.util.Arrays;
463+
import java.util.List;
464+
465+
/**
466+
* Implementation of the DemoWorkflow for the server side.
467+
*/
468+
public class DemoWorkflow extends Workflow {
469+
@Override
470+
public WorkflowStub create() {
471+
return ctx -> {
472+
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
473+
// ...
474+
ctx.getLogger().info("Calling Activity...");
475+
var input = new DemoActivityInput("Hello Activity!");
476+
var output = ctx.callActivity(DemoWorkflowActivity.class.getName(), input, DemoActivityOutput.class).await();
477+
// ...
478+
};
479+
}
480+
}
481+
```
482+
483+
[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)
484+
359485
{{% /codetab %}}
360486

361487

@@ -377,5 +503,6 @@ Now that you've authored a workflow, learn how to manage it.
377503
- [Workflow overview]({{< ref workflow-overview.md >}})
378504
- [Workflow API reference]({{< ref workflow_api.md >}})
379505
- Try out the full SDK examples:
380-
- [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow)
381506
- [Python example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow)
507+
- [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow)
508+
- [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows)

0 commit comments

Comments
 (0)