From e074c9ee2e4468eb3b0517a53c000af3800e5a32 Mon Sep 17 00:00:00 2001 From: Albert Callarisa Date: Mon, 8 Sep 2025 17:13:17 +0200 Subject: [PATCH 1/4] feat: Adds support for cross-app calls. Signed-off-by: Albert Callarisa --- dev-requirements.txt | 3 +- examples/workflow/README.md | 44 ++++++++++++++- examples/workflow/cross-app1.py | 46 ++++++++++++++++ examples/workflow/cross-app2.py | 33 ++++++++++++ examples/workflow/cross-app3.py | 29 ++++++++++ .../ext/workflow/dapr_workflow_context.py | 53 ++++++++++++++++--- .../dapr/ext/workflow/workflow_context.py | 23 +++++--- ext/dapr-ext-workflow/setup.cfg | 5 +- .../tests/test_dapr_workflow_context.py | 4 +- 9 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 examples/workflow/cross-app1.py create mode 100644 examples/workflow/cross-app2.py create mode 100644 examples/workflow/cross-app3.py diff --git a/dev-requirements.txt b/dev-requirements.txt index 3e7ca471..8a9a2a03 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -15,7 +15,8 @@ Flask>=1.1 # needed for auto fix ruff===0.2.2 # needed for dapr-ext-workflow -durabletask-dapr >= 0.2.0a8 +# durabletask-dapr >= 0.2.0a8 +durabletask-dapr @ git+https://github.com/dapr/durabletask-python@main # needed for .env file loading in examples python-dotenv>=1.0.0 # needed for enhanced schema generation from function features diff --git a/examples/workflow/README.md b/examples/workflow/README.md index f5b901d1..5b642e92 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -20,7 +20,7 @@ pip3 install -r requirements.txt Each of the examples in this directory can be run directly from the command line. ### Simple Workflow -This example represents a workflow that manages counters through a series of activities and child workflows. +This example represents a workflow that manages counters through a series of activities and child workflows. It shows several Dapr Workflow features including: - Basic activity execution with counter increments - Retryable activities with configurable retry policies @@ -269,4 +269,44 @@ When you run the example, you will see output like this: *** Calling child workflow 29a7592a1e874b07aad2bb58de309a51-child *** Child workflow 6feadc5370184b4998e50875b20084f6 called ... -``` \ No newline at end of file +``` + + +### Cross-app Workflow + +This example demonstrates how to call child workflows and activities in different apps. The multiple Dapr CLI instances can be started using the following commands: + + + +```sh +pip install ./ext/dapr-ext-workflow +dapr run --app-id wfexample3 --dapr-grpc-port 50003 python3 cross-app3.py & +dapr run --app-id wfexample2 --dapr-grpc-port 50002 python3 cross-app2.py & +dapr run --app-id wfexample1 --dapr-grpc-port 50001 python3 cross-app1.py +``` + + +When you run the apps, you will see output like this: +``` +... +app1 - triggering app2 workflow +app2 - triggering app3 activity +... +``` +among others. This shows that the workflow calls are working as expected. diff --git a/examples/workflow/cross-app1.py b/examples/workflow/cross-app1.py new file mode 100644 index 00000000..775d60bf --- /dev/null +++ b/examples/workflow/cross-app1.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dapr.ext.workflow as wf +import time + +wfr = wf.WorkflowRuntime() + + +@wfr.workflow +def app1_workflow(ctx: wf.DaprWorkflowContext): + print(f'app1 - received workflow call', flush=True) + print(f'app1 - triggering app2 workflow', flush=True) + + yield ctx.call_child_workflow( + workflow='app2_workflow', + input=None, + app_id='wfexample2', + ) + print(f'app1 - received workflow result', flush=True) + print(f'app1 - returning workflow result', flush=True) + + return 1 + + +if __name__ == '__main__': + wfr.start() + time.sleep(10) # wait for workflow runtime to start + + wf_client = wf.DaprWorkflowClient() + print(f'app1 - triggering app1 workflow', flush=True) + instance_id = wf_client.schedule_new_workflow(workflow=app1_workflow) + + # Wait for the workflow to complete + time.sleep(5) + + wfr.shutdown() diff --git a/examples/workflow/cross-app2.py b/examples/workflow/cross-app2.py new file mode 100644 index 00000000..4f54aefb --- /dev/null +++ b/examples/workflow/cross-app2.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dapr.ext.workflow as wf +import time + +wfr = wf.WorkflowRuntime() + + +@wfr.workflow +def app2_workflow(ctx: wf.DaprWorkflowContext): + print(f'app2 - received workflow call', flush=True) + print(f'app2 - triggering app3 activity', flush=True) + yield ctx.call_activity('app3_activity', input=None, app_id='wfexample3') + print(f'app2 - received activity result', flush=True) + print(f'app2 - returning workflow result', flush=True) + + return 2 + + +if __name__ == '__main__': + wfr.start() + time.sleep(15) # wait for workflow runtime to start + wfr.shutdown() diff --git a/examples/workflow/cross-app3.py b/examples/workflow/cross-app3.py new file mode 100644 index 00000000..e138e2a5 --- /dev/null +++ b/examples/workflow/cross-app3.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dapr.ext.workflow as wf +import time + +wfr = wf.WorkflowRuntime() + + +@wfr.activity +def app3_activity(ctx: wf.DaprWorkflowContext) -> int: + print(f'app3 - received activity call', flush=True) + print(f'app3 - returning activity result', flush=True) + return 3 + + +if __name__ == '__main__': + wfr.start() + time.sleep(15) # wait for workflow runtime to start + wfr.shutdown() diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py b/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py index 2dee46fe..476ab765 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py @@ -63,11 +63,29 @@ def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task: def call_activity( self, - activity: Callable[[WorkflowActivityContext, TInput], TOutput], + activity: Union[Callable[[WorkflowActivityContext, TInput], TOutput], str], *, input: TInput = None, retry_policy: Optional[RetryPolicy] = None, + app_id: Optional[str] = None, ) -> task.Task[TOutput]: + # Handle string activity names for cross-app scenarios + if isinstance(activity, str): + activity_name = activity + if app_id is not None: + self._logger.debug( + f'{self.instance_id}: Creating cross-app activity {activity_name} for app {app_id}' + ) + else: + self._logger.debug(f'{self.instance_id}: Creating activity {activity_name}') + + if retry_policy is None: + return self.__obj.call_activity(activity=activity_name, input=input, app_id=app_id) + return self.__obj.call_activity( + activity=activity_name, input=input, retry_policy=retry_policy.obj, app_id=app_id + ) + + # Handle function activity objects (original behavior) self._logger.debug(f'{self.instance_id}: Creating activity {activity.__name__}') if hasattr(activity, '_dapr_alternate_name'): act = activity.__dict__['_dapr_alternate_name'] @@ -75,17 +93,38 @@ def call_activity( # this case should ideally never happen act = activity.__name__ if retry_policy is None: - return self.__obj.call_activity(activity=act, input=input) - return self.__obj.call_activity(activity=act, input=input, retry_policy=retry_policy.obj) + return self.__obj.call_activity(activity=act, input=input, app_id=app_id) + return self.__obj.call_activity( + activity=act, input=input, retry_policy=retry_policy.obj, app_id=app_id + ) def call_child_workflow( self, - workflow: Workflow, + workflow: Union[Workflow, str], *, input: Optional[TInput] = None, instance_id: Optional[str] = None, retry_policy: Optional[RetryPolicy] = None, + app_id: Optional[str] = None, ) -> task.Task[TOutput]: + # Handle string workflow names for cross-app scenarios + if isinstance(workflow, str): + workflow_name = workflow + self._logger.debug(f'{self.instance_id}: Creating child workflow {workflow_name}') + + if retry_policy is None: + return self.__obj.call_sub_orchestrator( + workflow_name, input=input, instance_id=instance_id, app_id=app_id + ) + return self.__obj.call_sub_orchestrator( + workflow_name, + input=input, + instance_id=instance_id, + retry_policy=retry_policy.obj, + app_id=app_id, + ) + + # Handle function workflow objects (original behavior) self._logger.debug(f'{self.instance_id}: Creating child workflow {workflow.__name__}') def wf(ctx: task.OrchestrationContext, inp: TInput): @@ -100,9 +139,11 @@ def wf(ctx: task.OrchestrationContext, inp: TInput): # this case should ideally never happen wf.__name__ = workflow.__name__ if retry_policy is None: - return self.__obj.call_sub_orchestrator(wf, input=input, instance_id=instance_id) + return self.__obj.call_sub_orchestrator( + wf, input=input, instance_id=instance_id, app_id=app_id + ) return self.__obj.call_sub_orchestrator( - wf, input=input, instance_id=instance_id, retry_policy=retry_policy.obj + wf, input=input, instance_id=instance_id, retry_policy=retry_policy.obj, app_id=app_id ) def wait_for_external_event(self, name: str) -> task.Task: diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py index b4c85f6a..d6e6ba07 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py @@ -107,18 +107,22 @@ def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task: @abstractmethod def call_activity( - self, activity: Activity[TOutput], *, input: Optional[TInput] = None + self, + activity: Union[Activity[TOutput], str], + *, + input: Optional[TInput] = None, + app_id: Optional[str] = None, ) -> task.Task[TOutput]: """Schedule an activity for execution. Parameters ---------- - activity: Activity[TInput, TOutput] - A reference to the activity function to call. + activity: Activity[TInput, TOutput] | str + A reference to the activity function to call, or a string name for cross-app activities. input: TInput | None The JSON-serializable input (or None) to pass to the activity. - return_type: task.Task[TOutput] - The JSON-serializable output type to expect from the activity result. + app_id: str | None + The AppID that will execute the activity. Returns ------- @@ -130,22 +134,25 @@ def call_activity( @abstractmethod def call_child_workflow( self, - orchestrator: Workflow[TOutput], + orchestrator: Union[Workflow[TOutput], str], *, input: Optional[TInput] = None, instance_id: Optional[str] = None, + app_id: Optional[str] = None, ) -> task.Task[TOutput]: """Schedule child-workflow function for execution. Parameters ---------- - orchestrator: Orchestrator[TInput, TOutput] - A reference to the orchestrator function to call. + orchestrator: Orchestrator[TInput, TOutput] | str + A reference to the orchestrator function to call, or a string name for cross-app workflows. input: TInput The optional JSON-serializable input to pass to the orchestrator function. instance_id: str A unique ID to use for the sub-orchestration instance. If not specified, a random UUID will be used. + app_id: str + The AppID that will execute the workflow. Returns ------- diff --git a/ext/dapr-ext-workflow/setup.cfg b/ext/dapr-ext-workflow/setup.cfg index edc914a1..ff3d85d3 100644 --- a/ext/dapr-ext-workflow/setup.cfg +++ b/ext/dapr-ext-workflow/setup.cfg @@ -25,11 +25,12 @@ packages = find_namespace: include_package_data = True install_requires = dapr >= 1.16.0 - durabletask-dapr >= 0.2.0a8 + # durabletask-dapr >= 0.2.0a8 + durabletask-dapr @ git+https://github.com/dapr/durabletask-python@main [options.packages.find] include = dapr.* -exclude = +exclude = tests diff --git a/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py b/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py index 9fdfe044..3ae5fdaf 100644 --- a/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py +++ b/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py @@ -36,10 +36,10 @@ def __init__(self): def create_timer(self, fire_at): return mock_create_timer - def call_activity(self, activity, input): + def call_activity(self, activity, input, app_id): return mock_call_activity - def call_sub_orchestrator(self, orchestrator, input, instance_id): + def call_sub_orchestrator(self, orchestrator, input, instance_id, app_id): return mock_call_sub_orchestrator def set_custom_status(self, custom_status): From 822f241b51e711a78c14d2213fd50934bf5f5b94 Mon Sep 17 00:00:00 2001 From: Albert Callarisa Date: Thu, 25 Sep 2025 07:44:56 +0200 Subject: [PATCH 2/4] Use durabletask alpha.9 Signed-off-by: Albert Callarisa --- dev-requirements.txt | 3 +-- ext/dapr-ext-workflow/setup.cfg | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 8a9a2a03..461d9239 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -15,8 +15,7 @@ Flask>=1.1 # needed for auto fix ruff===0.2.2 # needed for dapr-ext-workflow -# durabletask-dapr >= 0.2.0a8 -durabletask-dapr @ git+https://github.com/dapr/durabletask-python@main +durabletask-dapr >= 0.2.0a9 # needed for .env file loading in examples python-dotenv>=1.0.0 # needed for enhanced schema generation from function features diff --git a/ext/dapr-ext-workflow/setup.cfg b/ext/dapr-ext-workflow/setup.cfg index ff3d85d3..1d54cc08 100644 --- a/ext/dapr-ext-workflow/setup.cfg +++ b/ext/dapr-ext-workflow/setup.cfg @@ -25,8 +25,7 @@ packages = find_namespace: include_package_data = True install_requires = dapr >= 1.16.0 - # durabletask-dapr >= 0.2.0a8 - durabletask-dapr @ git+https://github.com/dapr/durabletask-python@main + durabletask-dapr >= 0.2.0a9 [options.packages.find] include = From 9a5b2aef8bd67a6ee167e8d332edf636046683c6 Mon Sep 17 00:00:00 2001 From: Albert Callarisa Date: Thu, 2 Oct 2025 10:06:57 +0200 Subject: [PATCH 3/4] Added examples for error scenarios in multi-app workflow Signed-off-by: Albert Callarisa --- examples/workflow/README.md | 89 ++++++++++++++++++++++++++++++++- examples/workflow/cross-app1.py | 28 ++++++++--- examples/workflow/cross-app2.py | 23 +++++++-- examples/workflow/cross-app3.py | 5 +- 4 files changed, 131 insertions(+), 14 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 5b642e92..d1bc306e 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -291,11 +291,10 @@ expected_stdout_lines: - '== APP == app1 - received workflow result' - '== APP == app1 - returning workflow result' background: true -sleep: 5 +sleep: 20 --> ```sh -pip install ./ext/dapr-ext-workflow dapr run --app-id wfexample3 --dapr-grpc-port 50003 python3 cross-app3.py & dapr run --app-id wfexample2 --dapr-grpc-port 50002 python3 cross-app2.py & dapr run --app-id wfexample1 --dapr-grpc-port 50001 python3 cross-app1.py @@ -310,3 +309,89 @@ app2 - triggering app3 activity ... ``` among others. This shows that the workflow calls are working as expected. + + +#### Error handling on activity calls + +This example demonstrates how the error handling works on activity calls across apps. + +Error handling on activity calls across apps works as normal workflow activity calls. + +In this example we run `app3` in failing mode, which makes the activity call return error constantly. The activity call from `app2` will fail after the retry policy is exhausted. + + + +```sh +export ERROR_ACTIVITY_MODE=true +dapr run --app-id wfexample3 --dapr-grpc-port 50013 python3 cross-app3.py & +dapr run --app-id wfexample2 --dapr-grpc-port 50012 python3 cross-app2.py & +dapr run --app-id wfexample1 --dapr-grpc-port 50011 python3 cross-app1.py +``` + + + +When you run the apps with the `ERROR_ACTIVITY_MODE` environment variable set, you will see output like this: +``` +... +app3 - received activity call +app3 - raising error in activity due to error mode being enabled +app2 - received activity error from app3 +... +``` +among others. This shows that the activity calls are failing as expected, and they are being handled as expected too. + + +#### Error handling on workflow calls + +This example demonstrates how the error handling works on workflow calls across apps. + +Error handling on workflow calls across apps works as normal workflow calls. + +In this example we run `app2` in failing mode, which makes the workflow call return error constantly. The workflow call from `app1` will fail after the retry policy is exhausted. + + + +```sh +export ERROR_WORKFLOW_MODE=true +dapr run --app-id wfexample3 --dapr-grpc-port 50023 python3 cross-app3.py & +dapr run --app-id wfexample2 --dapr-grpc-port 50022 python3 cross-app2.py & +dapr run --app-id wfexample1 --dapr-grpc-port 50021 python3 cross-app1.py +``` + + +When you run the apps with the `ERROR_WORKFLOW_MODE` environment variable set, you will see output like this: +``` +... +app2 - received workflow call +app2 - raising error in workflow due to error mode being enabled +app1 - received workflow error from app2 +... +``` +among others. This shows that the workflow calls are failing as expected, and they are being handled as expected too. + diff --git a/examples/workflow/cross-app1.py b/examples/workflow/cross-app1.py index 775d60bf..f84de662 100644 --- a/examples/workflow/cross-app1.py +++ b/examples/workflow/cross-app1.py @@ -10,6 +10,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import timedelta + +from durabletask.task import TaskFailedError import dapr.ext.workflow as wf import time @@ -21,14 +24,23 @@ def app1_workflow(ctx: wf.DaprWorkflowContext): print(f'app1 - received workflow call', flush=True) print(f'app1 - triggering app2 workflow', flush=True) - yield ctx.call_child_workflow( - workflow='app2_workflow', - input=None, - app_id='wfexample2', - ) - print(f'app1 - received workflow result', flush=True) - print(f'app1 - returning workflow result', flush=True) + try: + retry_policy = wf.RetryPolicy( + max_number_of_attempts=2, + first_retry_interval=timedelta(milliseconds=100), + max_retry_interval=timedelta(seconds=3), + ) + yield ctx.call_child_workflow( + workflow='app2_workflow', + input=None, + app_id='wfexample2', + retry_policy=retry_policy, + ) + print(f'app1 - received workflow result', flush=True) + except TaskFailedError as e: + print(f'app1 - received workflow error from app2', flush=True) + print(f'app1 - returning workflow result', flush=True) return 1 @@ -41,6 +53,6 @@ def app1_workflow(ctx: wf.DaprWorkflowContext): instance_id = wf_client.schedule_new_workflow(workflow=app1_workflow) # Wait for the workflow to complete - time.sleep(5) + time.sleep(7) wfr.shutdown() diff --git a/examples/workflow/cross-app2.py b/examples/workflow/cross-app2.py index 4f54aefb..4cb30874 100644 --- a/examples/workflow/cross-app2.py +++ b/examples/workflow/cross-app2.py @@ -10,6 +10,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import timedelta +import os + +from durabletask.task import TaskFailedError import dapr.ext.workflow as wf import time @@ -19,11 +23,24 @@ @wfr.workflow def app2_workflow(ctx: wf.DaprWorkflowContext): print(f'app2 - received workflow call', flush=True) + if os.getenv('ERROR_WORKFLOW_MODE', 'false') == 'true': + print(f'app2 - raising error in workflow due to error mode being enabled', flush=True) + raise ValueError('Error in workflow due to error mode being enabled') print(f'app2 - triggering app3 activity', flush=True) - yield ctx.call_activity('app3_activity', input=None, app_id='wfexample3') - print(f'app2 - received activity result', flush=True) - print(f'app2 - returning workflow result', flush=True) + try: + retry_policy = wf.RetryPolicy( + max_number_of_attempts=2, + first_retry_interval=timedelta(milliseconds=100), + max_retry_interval=timedelta(seconds=3), + ) + result = yield ctx.call_activity( + 'app3_activity', input=None, app_id='wfexample3', retry_policy=retry_policy + ) + print(f'app2 - received activity result', flush=True) + except TaskFailedError as e: + print(f'app2 - received activity error from app3', flush=True) + print(f'app2 - returning workflow result', flush=True) return 2 diff --git a/examples/workflow/cross-app3.py b/examples/workflow/cross-app3.py index e138e2a5..ecc945ca 100644 --- a/examples/workflow/cross-app3.py +++ b/examples/workflow/cross-app3.py @@ -9,7 +9,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import os import dapr.ext.workflow as wf import time @@ -19,6 +19,9 @@ @wfr.activity def app3_activity(ctx: wf.DaprWorkflowContext) -> int: print(f'app3 - received activity call', flush=True) + if os.getenv('ERROR_ACTIVITY_MODE', 'false') == 'true': + print(f'app3 - raising error in activity due to error mode being enabled', flush=True) + raise ValueError('Error in activity due to error mode being enabled') print(f'app3 - returning activity result', flush=True) return 3 From 8a9da119523b4441b9ff2ab746d5ec54a3852866 Mon Sep 17 00:00:00 2001 From: Albert Callarisa Date: Thu, 2 Oct 2025 10:38:00 +0200 Subject: [PATCH 4/4] Remove unnecessary hardcoded ports Signed-off-by: Albert Callarisa --- examples/workflow/README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index d1bc306e..2e09eeef 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -57,7 +57,7 @@ timeout_seconds: 30 --> ```sh -dapr run --app-id wf-simple-example --dapr-grpc-port 50001 -- python3 simple.py +dapr run --app-id wf-simple-example -- python3 simple.py ``` @@ -99,7 +99,7 @@ timeout_seconds: 30 --> ```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -- python3 task_chaining.py +dapr run --app-id wfexample -- python3 task_chaining.py ``` @@ -146,7 +146,7 @@ timeout_seconds: 30 --> ```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -- python3 fan_out_fan_in.py +dapr run --app-id wfexample -- python3 fan_out_fan_in.py ``` @@ -186,7 +186,7 @@ This example demonstrates how to use a workflow to interact with a human user. T The Dapr CLI can be started using the following command: ```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 +dapr run --app-id wfexample ``` In a separate terminal window, run the following command to start the Python workflow app: @@ -222,7 +222,7 @@ This example demonstrates how to eternally running workflow that polls an endpoi The Dapr CLI can be started using the following command: ```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 +dapr run --app-id wfexample ``` In a separate terminal window, run the following command to start the Python workflow app: @@ -254,7 +254,7 @@ This workflow runs forever or until you press `ENTER` to stop it. Starting the a This example demonstrates how to call a child workflow. The Dapr CLI can be started using the following command: ```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 +dapr run --app-id wfexample ``` In a separate terminal window, run the following command to start the Python workflow app: @@ -295,9 +295,9 @@ sleep: 20 --> ```sh -dapr run --app-id wfexample3 --dapr-grpc-port 50003 python3 cross-app3.py & -dapr run --app-id wfexample2 --dapr-grpc-port 50002 python3 cross-app2.py & -dapr run --app-id wfexample1 --dapr-grpc-port 50001 python3 cross-app1.py +dapr run --app-id wfexample3 python3 cross-app3.py & +dapr run --app-id wfexample2 python3 cross-app2.py & +dapr run --app-id wfexample1 python3 cross-app1.py ``` @@ -338,9 +338,9 @@ sleep: 20 ```sh export ERROR_ACTIVITY_MODE=true -dapr run --app-id wfexample3 --dapr-grpc-port 50013 python3 cross-app3.py & -dapr run --app-id wfexample2 --dapr-grpc-port 50012 python3 cross-app2.py & -dapr run --app-id wfexample1 --dapr-grpc-port 50011 python3 cross-app1.py +dapr run --app-id wfexample3 python3 cross-app3.py & +dapr run --app-id wfexample2 python3 cross-app2.py & +dapr run --app-id wfexample1 python3 cross-app1.py ``` @@ -379,9 +379,9 @@ sleep: 20 ```sh export ERROR_WORKFLOW_MODE=true -dapr run --app-id wfexample3 --dapr-grpc-port 50023 python3 cross-app3.py & -dapr run --app-id wfexample2 --dapr-grpc-port 50022 python3 cross-app2.py & -dapr run --app-id wfexample1 --dapr-grpc-port 50021 python3 cross-app1.py +dapr run --app-id wfexample3 python3 cross-app3.py & +dapr run --app-id wfexample2 python3 cross-app2.py & +dapr run --app-id wfexample1 python3 cross-app1.py ```