Skip to content

Commit c08077d

Browse files
cicoylemsfussellmarcduiker
authored andcommitted
Multi-app workflows: improve docs and images (#4878)
* add complex wf illustration and fix other image Signed-off-by: Cassandra Coyle <[email protected]> * cleanup Signed-off-by: Cassandra Coyle <[email protected]> * update howto author wfs Signed-off-by: Cassandra Coyle <[email protected]> * update go code to use vanity client Signed-off-by: Cassandra Coyle <[email protected]> * Apply suggestions from code review Co-authored-by: Mark Fussell <[email protected]> Signed-off-by: Cassie Coyle <[email protected]> * add scheduleworkflow note Signed-off-by: Cassandra Coyle <[email protected]> * update images Signed-off-by: Cassandra Coyle <[email protected]> --------- Signed-off-by: Cassandra Coyle <[email protected]> Signed-off-by: Cassie Coyle <[email protected]> Co-authored-by: Mark Fussell <[email protected]> Co-authored-by: Marc Duiker <[email protected]>
1 parent 2eb604f commit c08077d

File tree

4 files changed

+149
-30
lines changed

4 files changed

+149
-30
lines changed

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

Lines changed: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,12 @@ public class DemoWorkflowActivity implements WorkflowActivity {
197197

198198
<!--go-->
199199

200+
### Define workflow activities
201+
200202
Define each workflow activity you'd like your workflow to perform. The Activity input can be unmarshalled from the context with `ctx.GetInput`. Activities should be defined as taking a `ctx workflow.ActivityContext` parameter and returning an interface and error.
201203

202204
```go
203-
func TestActivity(ctx workflow.ActivityContext) (any, error) {
205+
func BusinessActivity(ctx workflow.ActivityContext) (any, error) {
204206
var input int
205207
if err := ctx.GetInput(&input); err != nil {
206208
return "", err
@@ -211,6 +213,87 @@ func TestActivity(ctx workflow.ActivityContext) (any, error) {
211213
}
212214
```
213215

216+
### Define the workflow
217+
218+
Define your workflow function with the parameter `ctx *workflow.WorkflowContext` and return any and error. Invoke your defined activities from within your workflow.
219+
220+
```go
221+
func BusinessWorkflow(ctx *workflow.WorkflowContext) (any, error) {
222+
var input int
223+
if err := ctx.GetInput(&input); err != nil {
224+
return nil, err
225+
}
226+
var output string
227+
if err := ctx.CallActivity(BusinessActivity, workflow.ActivityInput(input)).Await(&output); err != nil {
228+
return nil, err
229+
}
230+
if err := ctx.WaitForExternalEvent("businessEvent", time.Second*60).Await(&output); err != nil {
231+
return nil, err
232+
}
233+
234+
if err := ctx.CreateTimer(time.Second).Await(nil); err != nil {
235+
return nil, nil
236+
}
237+
return output, nil
238+
}
239+
```
240+
241+
### Register workflows and activities
242+
243+
Before your application can execute workflows, you must register both the workflow orchestrator and its activities with a workflow registry. This ensures Dapr knows which functions to call when executing your workflow.
244+
245+
```go
246+
func main() {
247+
// Create a workflow registry
248+
r := workflow.NewRegistry()
249+
250+
// Register the workflow orchestrator
251+
if err := r.AddWorkflow(BusinessWorkflow); err != nil {
252+
log.Fatal(err)
253+
}
254+
fmt.Println("BusinessWorkflow registered")
255+
256+
// Register the workflow activities
257+
if err := r.AddActivity(BusinessActivity); err != nil {
258+
log.Fatal(err)
259+
}
260+
fmt.Println("BusinessActivity registered")
261+
262+
// Create workflow client and start worker
263+
wclient, err := client.NewWorkflowClient()
264+
if err != nil {
265+
log.Fatal(err)
266+
}
267+
fmt.Println("Worker initialized")
268+
269+
ctx, cancel := context.WithCancel(context.Background())
270+
if err = wclient.StartWorker(ctx, r); err != nil {
271+
log.Fatal(err)
272+
}
273+
fmt.Println("runner started")
274+
275+
// Your application logic continues here...
276+
// Example: Start a workflow
277+
instanceID, err := wclient.ScheduleWorkflow(ctx, "BusinessWorkflow", workflow.WithInput(1))
278+
if err != nil {
279+
log.Fatalf("failed to start workflow: %v", err)
280+
}
281+
fmt.Printf("workflow started with id: %v\n", instanceID)
282+
283+
// Stop workflow worker when done
284+
cancel()
285+
fmt.Println("workflow worker successfully shutdown")
286+
}
287+
```
288+
289+
**Key points about registration:**
290+
- Use `workflow.NewRegistry()` to create a workflow registry
291+
- Use `r.AddWorkflow()` to register workflow functions
292+
- Use `r.AddActivity()` to register activity functions
293+
- Use `client.NewWorkflowClient()` to create a workflow client
294+
- Call `wclient.StartWorker()` to begin processing workflows
295+
- Use `wclient.ScheduleWorkflow` to schedule a named instance of a workflow
296+
214297
[See the Go SDK workflow activity example in context.](https://github.com/dapr/go-sdk/tree/main/examples/workflow/README.md)
215298

216299
{{% /tab %}}
@@ -383,16 +466,16 @@ public class DemoWorkflowWorker {
383466
Define your workflow function with the parameter `ctx *workflow.WorkflowContext` and return any and error. Invoke your defined activities from within your workflow.
384467

385468
```go
386-
func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) {
469+
func BusinessWorkflow(ctx *workflow.WorkflowContext) (any, error) {
387470
var input int
388471
if err := ctx.GetInput(&input); err != nil {
389472
return nil, err
390473
}
391474
var output string
392-
if err := ctx.CallActivity(TestActivity, workflow.ActivityInput(input)).Await(&output); err != nil {
475+
if err := ctx.CallActivity(BusinessActivity, workflow.ActivityInput(input)).Await(&output); err != nil {
393476
return nil, err
394477
}
395-
if err := ctx.WaitForExternalEvent("testEvent", time.Second*60).Await(&output); err != nil {
478+
if err := ctx.WaitForExternalEvent("businessEvent", time.Second*60).Await(&output); err != nil {
396479
return nil, err
397480
}
398481

@@ -864,7 +947,7 @@ public class DemoWorkflow extends Workflow {
864947
[As in the following example](https://github.com/dapr/go-sdk/tree/main/examples/workflow/README.md), a hello-world application using the Go SDK and Dapr Workflow would include:
865948
866949
- A Go package called `client` to receive the Go SDK client capabilities.
867-
- The `TestWorkflow` method
950+
- The `BusinessWorkflow` method
868951
- Creating the workflow with input and output.
869952
- API calls. In the example below, these calls start and call the workflow activities.
870953
@@ -889,15 +972,15 @@ var failActivityTries = 0
889972
func main() {
890973
r := workflow.NewRegistry()
891974

892-
if err := r.AddWorkflow(TestWorkflow); err != nil {
975+
if err := r.AddWorkflow(BusinessWorkflow); err != nil {
893976
log.Fatal(err)
894977
}
895-
fmt.Println("TestWorkflow registered")
978+
fmt.Println("BusinessWorkflow registered")
896979

897-
if err := r.AddActivity(TestActivity); err != nil {
980+
if err := r.AddActivity(BusinessActivity); err != nil {
898981
log.Fatal(err)
899982
}
900-
fmt.Println("TestActivity registered")
983+
fmt.Println("BusinessActivity registered")
901984

902985
if err := r.AddActivity(FailActivity); err != nil {
903986
log.Fatal(err)
@@ -921,7 +1004,7 @@ func main() {
9211004
// "start". This is useful for increasing the throughput of creating
9221005
// workflows.
9231006
// workflow.WithStartTime(time.Now())
924-
instanceID, err := wclient.ScheduleWorkflow(ctx, "TestWorkflow", workflow.WithInstanceID("a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9"), workflow.WithInput(1))
1007+
instanceID, err := wclient.ScheduleWorkflow(ctx, "BusinessWorkflow", workflow.WithInstanceID("a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9"), workflow.WithInput(1))
9251008
if err != nil {
9261009
log.Fatalf("failed to start workflow: %v", err)
9271010
}
@@ -963,9 +1046,8 @@ func main() {
9631046

9641047
fmt.Printf("stage: %d\n", stage)
9651048

966-
// Raise Event Test
967-
968-
err = wclient.RaiseEvent(ctx, instanceID, "testEvent", workflow.WithEventPayload("testData"))
1049+
// Raise Event
1050+
err = wclient.RaiseEvent(ctx, instanceID, "businessEvent", workflow.WithEventPayload("testData"))
9691051
if err != nil {
9701052
fmt.Printf("failed to raise event: %v", err)
9711053
}
@@ -1008,7 +1090,7 @@ func main() {
10081090
fmt.Printf("stage: %d\n", stage)
10091091

10101092
// Terminate workflow test
1011-
id, err := wclient.ScheduleWorkflow(ctx, "TestWorkflow", workflow.WithInstanceID("a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9"), workflow.WithInput(1))
1093+
id, err := wclient.ScheduleWorkflow(ctx, "BusinessWorkflow", workflow.WithInstanceID("a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9"), workflow.WithInput(1))
10121094
if err != nil {
10131095
log.Fatalf("failed to start workflow: %v", err)
10141096
}
@@ -1037,22 +1119,22 @@ func main() {
10371119
fmt.Println("workflow worker successfully shutdown")
10381120
}
10391121

1040-
func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) {
1122+
func BusinessWorkflow(ctx *workflow.WorkflowContext) (any, error) {
10411123
var input int
10421124
if err := ctx.GetInput(&input); err != nil {
10431125
return nil, err
10441126
}
10451127
var output string
1046-
if err := ctx.CallActivity(TestActivity, workflow.WithActivityInput(input)).Await(&output); err != nil {
1128+
if err := ctx.CallActivity(BusinessActivity, task.WithActivityInput(input)).Await(&output); err != nil {
10471129
return nil, err
10481130
}
10491131

1050-
err := ctx.WaitForExternalEvent("testEvent", time.Second*60).Await(&output)
1132+
err := ctx.WaitForSingleEvent("businessEvent", time.Second*60).Await(&output)
10511133
if err != nil {
10521134
return nil, err
10531135
}
10541136

1055-
if err := ctx.CallActivity(TestActivity, workflow.WithActivityInput(input)).Await(&output); err != nil {
1137+
if err := ctx.CallActivity(BusinessActivity, task.WithActivityInput(input)).Await(&output); err != nil {
10561138
return nil, err
10571139
}
10581140

@@ -1068,7 +1150,7 @@ func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) {
10681150
return output, nil
10691151
}
10701152

1071-
func TestActivity(ctx workflow.ActivityContext) (any, error) {
1153+
func BusinessActivity(ctx task.ActivityContext) (any, error) {
10721154
var input int
10731155
if err := ctx.GetInput(&input); err != nil {
10741156
return "", err

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

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,65 @@ Some scenarios where this is useful include:
1919
- Implementation of a workflow spans different programming languages based on team expertise or existing codebases.
2020
- Different team boundaries or microservice ownership.
2121

22+
<img src="/images/workflow-overview/workflow-multi-app-complex.png" width=800 alt="Diagram showing multi-application complex workflow">
23+
24+
The diagram below shows an example scenario of a complex workflow that orchestrates across multiple applications that are written in different languages. Each applications' main steps and activities are:
25+
26+
**App1: Main Workflow Service** - Top-level orchestrator that coordinates the entire ML pipeline
27+
- Starts the process
28+
- Calls data processing activities on App2
29+
- Calls ML training child workflow on App3
30+
- Calls model deployment on App4
31+
- Ends the complete workflow
32+
- **Language: Java**
33+
34+
**App2: Data Processing Pipeline** - **GPU activities** only
35+
- Data Ingesting Activity (GPU-accelerated)
36+
- Feature Engineering Activity (GPU-accelerated)
37+
- Returns completion signal to Main Workflow
38+
- **Language: Go**
39+
40+
**App3: ML Training Child Workflow** - Contains a child workflow and activities
41+
- Child workflow orchestrates:
42+
- Data Processing Activity
43+
- Model Training Activity (GPU-intensive)
44+
- Model Validation Activity
45+
- Triggered by App2's activities completing
46+
- Returns completion signal to Main Workflow
47+
- **Language: Java**
48+
49+
**App4: Model Serving Service** - **Beefy GPU app** with activities only
50+
- Model Loading Activity (GPU memory intensive)
51+
- Inference Setup Activity (GPU-accelerated inference)
52+
- Triggered by App3's workflow completing
53+
- Returns completion signal to Main Workflow
54+
- **Language: Go**
2255

2356
## Multi-application workflows
2457

25-
Like all building blocks in Dapr, workflow execution routing is based on the [App ID of the hosting Dapr application]({{% ref "security-concept.md#application-identity" %}}).
58+
Workflow execution routing is based on the [App ID of the hosting Dapr application]({{% ref "security-concept.md#application-identity" %}}).
2659
By default, the full workflow execution is hosted on the app ID that started the workflow. This workflow can be executed across any replicas of that app ID, not just the single replica which scheduled the workflow.
2760

2861

29-
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.
30-
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.
62+
It is possible to execute activities and child workflows on different app IDs by specifying the target app ID parameter, inside the workflow execution code.
63+
Upon execution, the target app ID executes the activity or child workflow, and returns the result to the parent workflow of the originating app ID.
3164

3265
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.
3366
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.
3467

3568
{{% alert title="Restrictions" color="primary" %}}
36-
Like other building blocks and resources in Dapr, workflows are scoped to a single namespace.
69+
Like other API building blocks and resources in Dapr, workflows are scoped to a single namespace.
3770
This means that all app IDs involved in a multi-application workflow must be in the same namespace.
38-
Similarly, all app IDs must use the same actor state store.
39-
Finally, the target app ID must have the activity or child workflow defined, otherwise the parent workflow will retry indefinitely.
71+
Similarly, all app IDs must use the same workflow (or actor) state store.
72+
Finally, the target app ID must have the activity or child workflow defined and registered, otherwise the parent workflow retries indefinitely.
4073
{{% /alert %}}
4174

4275
{{% 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.
76+
**SDKs supporting multi-application workflows** - Multi-application workflows are used via the SDKs.
77+
Currently the following are supported:
78+
- **Java** (**only** activity calls)
79+
- **Go** (**both** activities and child workflows calls)
80+
- The Python, .NET, JavaScript SDKs support are planned for future releases
4481
{{% /alert %}}
4582

4683
## Error handling
@@ -63,7 +100,7 @@ The following example shows how to execute the activity `ActivityA` on the targe
63100
{{% tab "Go" %}}
64101

65102
```go
66-
func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) {
103+
func BusinessWorkflow(ctx *workflow.WorkflowContext) (any, error) {
67104
var output string
68105
err := ctx.CallActivity("ActivityA",
69106
workflow.WithActivityInput("my-input"),
@@ -83,12 +120,12 @@ func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) {
83120
{{% tab "Java" %}}
84121

85122
```java
86-
public class CrossAppWorkflow implements Workflow {
123+
public class BusinessWorkflow implements Workflow {
87124
@Override
88125
public WorkflowStub create() {
89126
return ctx -> {
90127
String output = ctx.callActivity(
91-
"ActivityA",
128+
ActivityA.class.getName(),
92129
"my-input",
93130
new WorkflowTaskOptions("App2"), // Here we set the target app ID which will execute this activity.
94131
String.class
@@ -115,7 +152,7 @@ The following example shows how to execute the child workflow `Workflow2` on the
115152
{{% tab "Go" %}}
116153

117154
```go
118-
func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) {
155+
func BusinessWorkflow(ctx *workflow.WorkflowContext) (any, error) {
119156
var output string
120157
err := ctx.CallChildWorkflow("Workflow2",
121158
workflow.WithChildWorkflowInput("my-input"),
-48.4 KB
Loading
82.4 KB
Loading

0 commit comments

Comments
 (0)