Skip to content

Commit 323d32e

Browse files
cicoyleacrocamsfussell
authored
Multi-application Workflows (dapr#4847)
* cross-app docs Signed-off-by: Cassandra Coyle <[email protected]> * Extended cross-app docs Signed-off-by: Albert Callarisa <[email protected]> * called suborchestrators 'child workflow' for consistency Signed-off-by: Albert Callarisa <[email protected]> * Merge both multi-app pieces Signed-off-by: Albert Callarisa <[email protected]> * Changed link from workflow overview Signed-off-by: Albert Callarisa <[email protected]> * Applied suggestions from review Signed-off-by: Albert Callarisa <[email protected]> * Shortened sample code to show just the necessary pieces Signed-off-by: Albert Callarisa <[email protected]> * Update daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md Signed-off-by: Mark Fussell <[email protected]> * Update daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-multi-app.md Signed-off-by: Mark Fussell <[email protected]> --------- Signed-off-by: Cassandra Coyle <[email protected]> Signed-off-by: Albert Callarisa <[email protected]> Signed-off-by: Mark Fussell <[email protected]> Co-authored-by: Albert Callarisa <[email protected]> Co-authored-by: Mark Fussell <[email protected]>
1 parent 90136e7 commit 323d32e

File tree

5 files changed

+57
-139
lines changed

5 files changed

+57
-139
lines changed

daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-multi-app.md

Lines changed: 34 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ description: "Executing workflows across multiple applications"
88

99
It is often the case that a single workflow spans multiple applications, microservices, or programing languages.
1010
This is where an activity or a child workflow will be executed on a different application than the one hosting the parent workflow.
11+
1112
Some scenarios where this is useful include:
1213

1314
- A Machine Learning (ML) training activity must be executed on GPU-enabled machines, while the rest of the workflow runs on CPU-only orchestration machines.
@@ -16,6 +17,8 @@ Some scenarios where this is useful include:
1617
- Different parts of the workflow need to be executed in different geographic regions due to data residency requirements.
1718
- An involved business process spans multiple teams or departments, each owning their own application.
1819
- Implementation of a workflow spans different programming lanaguages based on team expertise or existing codebases.
20+
- Different team boundaries or microservice ownership.
21+
1922

2023
## Multi-application workflows
2124

@@ -25,8 +28,6 @@ This workflow will be executed across all replicas of that app ID, not just the
2528

2629
It is possible to execute activities or child workflows on different app IDs by specifying the target app ID parameter, inside the workflow execution code.
2730
Upon execution, the target app ID will execute the activity or child workflow, and return the result to the parent workflow of the originating app ID.
28-
Workflows being durable, if the target activity or child workflow app ID is not available or has not been defined, the parent workflow retry until the target app ID becomes available, indefinitely.
29-
It is paramount that their is co-ordination between the teams owning the different app IDs to ensure that the activities and child workflows are defined and available when needed.
3031

3132
The entire Workflow execution may be distributed across multiple app IDs with no limit, with each activity or child workflow specifying the target app ID.
3233
The final history of the workflow will be saved by the app ID that hosts the very parent (or can consider it the root) workflow.
@@ -38,62 +39,37 @@ Similarly, all app IDs must use the same actor state store.
3839
Finally, the target app ID must have the activity or child workflow defined, otherwise the parent workflow will retry indefinitely.
3940
{{% /alert %}}
4041

41-
## Multi-application activity examples
42-
43-
The following examples show how to execute activities on different target app IDs.
44-
45-
{{< tabpane text=true >}}
46-
47-
{{% tab "Go" %}}
42+
{{% alert title="Important Limitations" color="warning" %}}
43+
- **SDKs supporting multi-application workflows** - Multi-application workflows are used via the SDKs. Currently Java (activities calling) and Go (both activities and child workflows calling) SDKs are supported. The SDKs (Python, .NET, JavaScript) are planned for future releases.
44+
{{% /alert %}}
4845

49-
```go
50-
package main
46+
## Error handling
5147

52-
import (
53-
"context"
54-
"log"
48+
When calling multi-application activities or child workflows:
49+
- If the target application does not exist, the call will be retried using the provided retry policy.
50+
- If the target application exists but doesn't contain the specified activity or workflow, the call will return an error.
51+
- Standard workflow retry policies apply to multi-application calls.
5552

56-
"github.com/dapr/durabletask-go/backend"
57-
"github.com/dapr/durabletask-go/client"
58-
"github.com/dapr/durabletask-go/task"
59-
dapr "github.com/dapr/go-sdk/client"
60-
)
53+
It is paramount that there is co-ordination between the teams owning the different app IDs to ensure that the activities and child workflows are defined and available when needed.
6154

62-
func main() {
63-
ctx := context.Background()
55+
## Multi-application activity example
6456

65-
registry := task.NewTaskRegistry()
66-
if err := registry.AddOrchestrator(TestWorkflow); err != nil {
67-
log.Fatal(err)
68-
}
57+
<img src="/images/workflow-overview/workflow-multi-app-callactivity.png" width=800 alt="Diagram showing multi-application call activity workflow pattern">
6958

70-
daprClient, err := dapr.NewClient()
71-
if err != nil {
72-
log.Fatal(err)
73-
}
59+
The following example shows how to execute the activity `ActivityA` on the target app `App2`.
7460

75-
client := client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger())
76-
if err := client.StartWorkItemListener(ctx, registry); err != nil {
77-
log.Fatal(err)
78-
}
79-
80-
id, err := client.ScheduleNewOrchestration(ctx, "TestWorkflow")
81-
if err != nil {
82-
log.Fatal(err)
83-
}
61+
{{< tabpane text=true >}}
8462

85-
if _, err = client.WaitForOrchestrationCompletion(ctx, id); err != nil {
86-
log.Fatal(err)
87-
}
88-
}
63+
{{% tab "Go" %}}
8964

65+
```go
9066
func TestWorkflow(ctx *task.OrchestrationContext) (any, error) {
9167
var output string
92-
err := ctx.CallActivity("my-other-activity",
68+
err := ctx.CallActivity("ActivityA",
9369
task.WithActivityInput("my-input"),
94-
// Here we set custom target app ID which will execute this activity.
95-
task.WithActivityAppID("my-other-app-id"),
70+
task.WithActivityAppID("App2"), // Here we set the target app ID which will execute this activity.
9671
).Await(&output)
72+
9773
if err != nil {
9874
return nil, err
9975
}
@@ -111,41 +87,14 @@ public class CrossAppWorkflow implements Workflow {
11187
@Override
11288
public WorkflowStub create() {
11389
return ctx -> {
114-
var logger = ctx.getLogger();
115-
logger.info("=== WORKFLOW STARTING ===");
116-
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
117-
logger.info("Workflow name: {}", ctx.getName());
118-
logger.info("Workflow instance ID: {}", ctx.getInstanceId());
119-
120-
String input = ctx.getInput(String.class);
121-
logger.info("CrossAppWorkflow received input: {}", input);
122-
logger.info("Workflow input: {}", input);
123-
124-
// Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
125-
logger.info("Calling cross-app activity in 'app2'...");
126-
logger.info("About to call cross-app activity in app2...");
127-
String crossAppResult = ctx.callActivity(
128-
App2TransformActivity.class.getName(),
129-
input,
130-
new WorkflowTaskOptions("app2"),
90+
String output = ctx.callActivity(
91+
"ActivityA",
92+
"my-input",
93+
new WorkflowTaskOptions("App2"), // Here we set the target app ID which will execute this activity.
13194
String.class
13295
).await();
13396

134-
// Call another activity in a different app
135-
logger.info("Calling cross-app activity in 'app3'...");
136-
logger.info("About to call cross-app activity in app3...");
137-
String finalResult = ctx.callActivity(
138-
App3FinalizeActivity.class.getName(),
139-
crossAppResult,
140-
new WorkflowTaskOptions("app3"),
141-
String.class
142-
).await();
143-
logger.info("Final cross-app activity result: {}", finalResult);
144-
logger.info("Final cross-app activity result: {}", finalResult);
145-
146-
logger.info("CrossAppWorkflow finished with: {}", finalResult);
147-
logger.info("=== WORKFLOW COMPLETING WITH: {} ===" , finalResult);
148-
ctx.complete(finalResult);
97+
ctx.complete(output);
14998
};
15099
}
151100
}
@@ -155,60 +104,24 @@ public class CrossAppWorkflow implements Workflow {
155104

156105
{{< /tabpane >}}
157106

158-
The following examples show how to execute child workflows on different target app IDs.
107+
## Multi-application child workflow example
108+
109+
<img src="/images/workflow-overview/workflow-multi-app-child-workflow.png" width=800 alt="Diagram showing multi-application child workflow pattern">
110+
111+
The following example shows how to execute the child workflow `Workflow2` on the target app `App2`.
159112

160113
{{< tabpane text=true >}}
161114

162115
{{% tab "Go" %}}
163116

164117
```go
165-
package main
166-
167-
import (
168-
"context"
169-
"log"
170-
171-
"github.com/dapr/durabletask-go/backend"
172-
"github.com/dapr/durabletask-go/client"
173-
"github.com/dapr/durabletask-go/task"
174-
dapr "github.com/dapr/go-sdk/client"
175-
)
176-
177-
func main() {
178-
ctx := context.Background()
179-
180-
registry := task.NewTaskRegistry()
181-
if err := registry.AddOrchestrator(TestWorkflow); err != nil {
182-
log.Fatal(err)
183-
}
184-
185-
daprClient, err := dapr.NewClient()
186-
if err != nil {
187-
log.Fatal(err)
188-
}
189-
190-
client := client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger())
191-
if err := client.StartWorkItemListener(ctx, registry); err != nil {
192-
log.Fatal(err)
193-
}
194-
195-
id, err := client.ScheduleNewOrchestration(ctx, "TestWorkflow")
196-
if err != nil {
197-
log.Fatal(err)
198-
}
199-
200-
if _, err = client.WaitForOrchestrationCompletion(ctx, id); err != nil {
201-
log.Fatal(err)
202-
}
203-
}
204-
205118
func TestWorkflow(ctx *task.OrchestrationContext) (any, error) {
206119
var output string
207-
err := ctx.CallSubOrchestrator("my-sub-orchestration",
120+
err := ctx.CallSubOrchestrator("Workflow2",
208121
task.WithSubOrchestratorInput("my-input"),
209-
// Here we set custom target app ID which will execute this child workflow.
210-
task.WithSubOrchestratorAppID("my-sub-app-id"),
122+
task.WithSubOrchestratorAppID("App2"), // Here we set the target app ID which will execute this child workflow.
211123
).Await(&output)
124+
212125
if err != nil {
213126
return nil, err
214127
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ Child workflow also supports automatic retry policies.
4646

4747
[Learn more about child workflows.]({{% ref "workflow-features-concepts.md#child-workflows" %}})
4848

49+
### Multi-application workflows
50+
51+
Multi-application workflows, enable you to orchestrate complex business processes that span across multiple applications. This allows a workflow to call activities or start child workflows in different applications, distributing the workflow execution while maintaining the security, reliability and durability guarantees of Dapr's workflow engine.
52+
53+
[Learn more about multi-application workflows.]({{% ref "workflow-multi-app.md" %}})
54+
4955
### Timers and reminders
5056

5157
Same as Dapr actors, you can schedule reminder-like durable delays for any time range.

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

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,7 @@ await context.CallActivityAsync("PostResults", sum);
615615
616616
{{< /tabpane >}}
617617
618-
With the release of 1.16, it's even easier to process workflow activities in parallel while putting an upper cap on
619-
concurrency by using the following extension methods on the `WorkflowContext`:
618+
You can process workflow activities in parallel while putting an upper cap on concurrency by using the following extension methods on the `WorkflowContext`:
620619
621620
{{< tabpane text=true >}}
622621
@@ -1428,58 +1427,58 @@ The following diagram illustrates this flow.
14281427
14291428
```java
14301429
public class PaymentProcessingWorkflow implements Workflow {
1431-
1430+
14321431
@Override
14331432
public WorkflowStub create() {
14341433
return ctx -> {
14351434
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
14361435
var orderId = ctx.getInput(String.class);
14371436
List<String> compensations = new ArrayList<>();
1438-
1437+
14391438
try {
14401439
// Step 1: Reserve inventory
14411440
String reservationId = ctx.callActivity(ReserveInventoryActivity.class.getName(), orderId, String.class).await();
14421441
ctx.getLogger().info("Inventory reserved: {}", reservationId);
14431442
compensations.add("ReleaseInventory");
1444-
1443+
14451444
// Step 2: Process payment
14461445
String paymentId = ctx.callActivity(ProcessPaymentActivity.class.getName(), orderId, String.class).await();
14471446
ctx.getLogger().info("Payment processed: {}", paymentId);
14481447
compensations.add("RefundPayment");
1449-
1448+
14501449
// Step 3: Ship order
14511450
String shipmentId = ctx.callActivity(ShipOrderActivity.class.getName(), orderId, String.class).await();
14521451
ctx.getLogger().info("Order shipped: {}", shipmentId);
14531452
compensations.add("CancelShipment");
1454-
1453+
14551454
} catch (TaskFailedException e) {
14561455
ctx.getLogger().error("Activity failed: {}", e.getMessage());
1457-
1456+
14581457
// Execute compensations in reverse order
14591458
Collections.reverse(compensations);
14601459
for (String compensation : compensations) {
14611460
try {
14621461
switch (compensation) {
14631462
case "CancelShipment":
14641463
String shipmentCancelResult = ctx.callActivity(
1465-
CancelShipmentActivity.class.getName(),
1466-
orderId,
1464+
CancelShipmentActivity.class.getName(),
1465+
orderId,
14671466
String.class).await();
14681467
ctx.getLogger().info("Shipment cancellation completed: {}", shipmentCancelResult);
14691468
break;
1470-
1469+
14711470
case "RefundPayment":
14721471
String refundResult = ctx.callActivity(
1473-
RefundPaymentActivity.class.getName(),
1474-
orderId,
1472+
RefundPaymentActivity.class.getName(),
1473+
orderId,
14751474
String.class).await();
14761475
ctx.getLogger().info("Payment refund completed: {}", refundResult);
14771476
break;
1478-
1477+
14791478
case "ReleaseInventory":
14801479
String releaseResult = ctx.callActivity(
1481-
ReleaseInventoryActivity.class.getName(),
1482-
orderId,
1480+
ReleaseInventoryActivity.class.getName(),
1481+
orderId,
14831482
String.class).await();
14841483
ctx.getLogger().info("Inventory release completed: {}", releaseResult);
14851484
break;
@@ -1494,7 +1493,7 @@ public class PaymentProcessingWorkflow implements Workflow {
14941493
// Step 4: Send confirmation
14951494
ctx.callActivity(SendConfirmationActivity.class.getName(), orderId, Void.class).await();
14961495
ctx.getLogger().info("Confirmation sent for order: {}", orderId);
1497-
1496+
14981497
ctx.complete("Order processed successfully: " + orderId);
14991498
};
15001499
}
@@ -1597,7 +1596,7 @@ The compensation pattern ensures that your distributed workflows can maintain co
15971596
- [Try out Dapr Workflows using the quickstart]({{% ref workflow-quickstart.md %}})
15981597
- [Workflow overview]({{% ref workflow-overview.md %}})
15991598
- [Workflow API reference]({{% ref workflow_api.md %}})
1600-
- Try out the following examples:
1599+
- Try out the following examples:
16011600
- [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow)
16021601
- [JavaScript](https://github.com/dapr/js-sdk/tree/main/examples/workflow)
16031602
- [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow)
58.4 KB
Loading
81.4 KB
Loading

0 commit comments

Comments
 (0)