diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..848df63 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing + +## Cloning this repository + +This repository contains submodules. Be sure to clone it with the option to include submodules. Otherwise, you will not be able to generate the protobuf code. + +```bash +git clone --recurse-submodules https://github.com/dapr/durabletask-go +``` + +If you already cloned the repository without `--recurse-submodules`, you can initialize and update the submodules with: + +```bash +git submodule update --init --recursive +``` + +To grab latest, do some variation of the following nuke your existing submodule folder then run: +```bash +rm -rf submodules/durabletask-protobuf +git submodule add --force https://github.com/dapr/durabletask-protobuf.git submodules/durabletask-protobuf +git submodule update --remote submodules/durabletask-protobuf +``` + +This will initialize and update the submodules. + +## Building the project + +This project requires go v1.19.x or greater. You can build a standalone executable by simply running `go build` at the project root. + +### Generating protobuf + +Use the following command to regenerate the protobuf from the submodule. Use this whenever updating the submodule reference. + +```bash +# Run from the repo root and specify the output directory +# This will place the generated files directly in api/protos/, matching the go_package and your repo structure. +protoc --go_out=. --go-grpc_out=. \ + -I submodules/durabletask-protobuf/protos \ + submodules/durabletask-protobuf/protos/orchestrator_service.proto \ + submodules/durabletask-protobuf/protos/backend_service.proto \ + submodules/durabletask-protobuf/protos/runtime_state.proto +``` + +For local development with protobuf changes: + +1. If you have local changes to the proto files in a neighboring durabletask-protobuf directory: +```bash +# Point go.mod to your local durabletask-protobuf repo +replace github.com/dapr/durabletask-protobuf => ../durabletask-protobuf + +# Regenerate protobuf files using your local proto definitions +protoc --go_out=. --go-grpc_out=. \ + -I ../durabletask-protobuf/protos \ + ../durabletask-protobuf/protos/orchestrator_service.proto \ + ../durabletask-protobuf/protos/backend_service.proto \ + ../durabletask-protobuf/protos/runtime_state.proto +``` + +This will use your local proto files instead of the ones in the submodule, which is useful when testing protobuf changes before submitting them upstream. + +### Generating mocks for testing + +Test mocks were generated using [mockery](https://github.com/vektra/mockery). Use the following command at the project root to regenerate the mocks. + +```bash +mockery --dir ./backend --name="^Backend|^Executor|^TaskWorker" --output ./tests/mocks --with-expecter +``` + +## Running tests + +All automated tests are under `./tests`. A separate test package hierarchy was chosen intentionally to prioritize [black box testing](https://en.wikipedia.org/wiki/Black-box_testing). This strategy also makes it easier to catch accidental breaking API changes. + +Run tests with the following command. + +```bash +go test ./tests/... -coverpkg ./api,./task,./client,./backend/...,./api/helpers +``` diff --git a/README.md b/README.md index 2f9cc7c..d0eb215 100644 --- a/README.md +++ b/README.md @@ -243,46 +243,6 @@ You can find this code in the [distributedtracing](./samples/distributedtracing) Note that each orchestration is represented as a single span with activities, timers, and sub-orchestrations as child spans. The generated spans contain a variety of attributes that include information such as orchestration instance IDs, task names, task IDs, etc. -## Cloning this repository - -This repository contains submodules. Be sure to clone it with the option to include submodules. Otherwise you will not be able to generate the protobuf code. - -```bash -git clone --recurse-submodules https://github.com/dapr/durabletask-go -``` - -## Building the project - -This project requires go v1.19.x or greater. You can build a standalone executable by simply running `go build` at the project root. - -### Generating protobuf - -Use the following command to regenerate the protobuf from the submodule. Use this whenever updating the submodule reference. - -```bash -# NOTE: assumes the .proto file defines: option go_package = "/api/protos" -# NOTE: currently the .proto file actually defines: option go_package = "/internal/protos"; , we are manually changing that to be /api/protos -protoc --go_out=. --go-grpc_out=. -I submodules/durabletask-protobuf/protos orchestrator_service.proto -``` - -### Generating mocks for testing - -Test mocks were generated using [mockery](https://github.com/vektra/mockery). Use the following command at the project root to regenerate the mocks. - -```bash -mockery --dir ./backend --name="^Backend|^Executor|^TaskWorker" --output ./tests/mocks --with-expecter -``` - -## Running tests - -All automated tests are under `./tests`. A separate test package hierarchy was chosen intentionally to prioritize [black box testing](https://en.wikipedia.org/wiki/Black-box_testing). This strategy also makes it easier to catch accidental breaking API changes. - -Run tests with the following command. - -```bash -go test ./tests/... -coverpkg ./api,./task,./client,./backend/...,./api/helpers -``` - ## Running integration tests You can run pre-built container images to run full integration tests against the durable task host over gRPC. diff --git a/api/protos/backend_service.pb.go b/api/protos/backend_service.pb.go index 48b1328..e478c96 100644 --- a/api/protos/backend_service.pb.go +++ b/api/protos/backend_service.pb.go @@ -2168,14 +2168,13 @@ var file_backend_service_proto_rawDesc = []byte{ 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x61, 0x0a, 0x31, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x74, - 0x61, 0x73, 0x6b, 0x2e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x5a, 0x0b, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0xaa, 0x02, 0x1e, 0x4d, 0x69, 0x63, 0x72, 0x6f, - 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x56, 0x0a, 0x2b, 0x69, 0x6f, 0x2e, 0x64, 0x61, 0x70, + 0x72, 0x2e, 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x69, 0x6d, + 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x5a, 0x0b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x73, 0xaa, 0x02, 0x19, 0x44, 0x61, 0x70, 0x72, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, + 0x65, 0x54, 0x61, 0x73, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/protos/orchestrator_service.pb.go b/api/protos/orchestrator_service.pb.go index b5e1ffc..5b148de 100644 --- a/api/protos/orchestrator_service.pb.go +++ b/api/protos/orchestrator_service.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.4 +// protoc v5.29.3 // source: orchestrator_service.proto package protos @@ -195,8 +195,8 @@ type TaskRouter struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` // orchestrationAppID - Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"` // appID + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` // orchestrationAppID + Target *string `protobuf:"bytes,2,opt,name=target,proto3,oneof" json:"target,omitempty"` // appID } func (x *TaskRouter) Reset() { @@ -239,8 +239,8 @@ func (x *TaskRouter) GetSource() string { } func (x *TaskRouter) GetTarget() string { - if x != nil { - return x.Target + if x != nil && x.Target != nil { + return *x.Target } return "" } @@ -7412,11 +7412,12 @@ var file_orchestrator_service_proto_rawDesc = []byte{ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, - 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3c, 0x0a, 0x0a, 0x54, 0x61, + 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4c, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x77, 0x0a, 0x15, 0x4f, 0x72, 0x63, 0x68, + 0x12, 0x1b, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, + 0x07, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x77, 0x0a, 0x15, 0x4f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, @@ -10550,6 +10551,7 @@ func file_orchestrator_service_proto_init() { } } } + file_orchestrator_service_proto_msgTypes[0].OneofWrappers = []interface{}{} file_orchestrator_service_proto_msgTypes[16].OneofWrappers = []interface{}{} file_orchestrator_service_proto_msgTypes[34].OneofWrappers = []interface{}{ (*HistoryEvent_ExecutionStarted)(nil), diff --git a/api/protos/orchestrator_service_grpc.pb.go b/api/protos/orchestrator_service_grpc.pb.go index 83d8f09..8898e91 100644 --- a/api/protos/orchestrator_service_grpc.pb.go +++ b/api/protos/orchestrator_service_grpc.pb.go @@ -4,7 +4,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.4 +// - protoc v5.29.3 // source: orchestrator_service.proto package protos diff --git a/api/protos/runtime_state.pb.go b/api/protos/runtime_state.pb.go index c97f205..dffc0d3 100644 --- a/api/protos/runtime_state.pb.go +++ b/api/protos/runtime_state.pb.go @@ -313,14 +313,13 @@ var file_runtime_state_proto_rawDesc = []byte{ 0x6e, 0x74, 0x52, 0x0c, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x42, 0x61, 0x0a, 0x31, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x64, 0x75, - 0x72, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x5a, 0x0b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0xaa, 0x02, - 0x1e, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x62, - 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x42, 0x56, 0x0a, 0x2b, + 0x69, 0x6f, 0x2e, 0x64, 0x61, 0x70, 0x72, 0x2e, 0x64, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x74, + 0x61, 0x73, 0x6b, 0x2e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x5a, 0x0b, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0xaa, 0x02, 0x19, 0x44, 0x61, 0x70, 0x72, 0x2e, + 0x44, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/backend/executor.go b/backend/executor.go index 02aeacb..94508d5 100644 --- a/backend/executor.go +++ b/backend/executor.go @@ -217,6 +217,7 @@ func (executor *grpcExecutor) ExecuteActivity(ctx context.Context, iid api.Insta FailureDetails: failureDetails, }, }, + Router: e.Router, } } else { responseEvent = &protos.HistoryEvent{ @@ -229,6 +230,7 @@ func (executor *grpcExecutor) ExecuteActivity(ctx context.Context, iid api.Insta TaskExecutionId: task.TaskExecutionId, }, }, + Router: e.Router, } } diff --git a/backend/runtimestate/runtimestate.go b/backend/runtimestate/runtimestate.go index c992add..2490177 100644 --- a/backend/runtimestate/runtimestate.go +++ b/backend/runtimestate/runtimestate.go @@ -92,6 +92,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. EventType: &protos.HistoryEvent_OrchestratorStarted{ OrchestratorStarted: &protos.OrchestratorStartedEvent{}, }, + Router: action.Router, }) // Duplicate the start event info, updating just the input @@ -111,6 +112,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. ParentTraceContext: s.StartEvent.ParentTraceContext, }, }, + Router: action.Router, }, ) @@ -135,6 +137,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. FailureDetails: completedAction.FailureDetails, }, }, + Router: action.Router, }) if s.StartEvent.GetParentInstance() != nil { msg := &protos.OrchestrationRuntimeStateMessage{ @@ -170,6 +173,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. Name: createtimer.Name, }, }, + Router: action.Router, }) // TODO cant pass trace context s.PendingTimers = append(s.PendingTimers, &protos.HistoryEvent{ @@ -195,6 +199,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. ParentTraceContext: currentTraceContext, }, }, + Router: action.Router, } AddEvent(s, scheduledEvent) s.PendingTasks = append(s.PendingTasks, scheduledEvent) @@ -216,6 +221,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. ParentTraceContext: currentTraceContext, }, }, + Router: action.Router, }) startEvent := &protos.HistoryEvent{ EventId: -1, @@ -236,6 +242,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. ParentTraceContext: currentTraceContext, }, }, + Router: action.Router, } s.PendingMessages = append(s.PendingMessages, &protos.OrchestrationRuntimeStateMessage{HistoryEvent: startEvent, TargetInstanceID: createSO.InstanceId}) @@ -250,6 +257,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. Input: sendEvent.Data, }, }, + Router: action.Router, } AddEvent(s, e) s.PendingMessages = append(s.PendingMessages, &protos.OrchestrationRuntimeStateMessage{HistoryEvent: e, TargetInstanceID: sendEvent.Instance.InstanceId}) @@ -266,6 +274,7 @@ func ApplyActions(s *protos.OrchestrationRuntimeState, customStatus *wrapperspb. Recurse: terminate.Recurse, }, }, + Router: action.Router, }, } s.PendingMessages = append(s.PendingMessages, msg) diff --git a/submodules/durabletask-protobuf b/submodules/durabletask-protobuf index 9862ba6..545663a 160000 --- a/submodules/durabletask-protobuf +++ b/submodules/durabletask-protobuf @@ -1 +1 @@ -Subproject commit 9862ba6ab0b7295eaa9a73d468d29814c1f5e349 +Subproject commit 545663a4db0040bb02c642c27010e337b9be8d31 diff --git a/task/activity.go b/task/activity.go index c93e5e8..f351925 100644 --- a/task/activity.go +++ b/task/activity.go @@ -16,6 +16,7 @@ type callActivityOption func(*callActivityOptions) error type callActivityOptions struct { rawInput *wrapperspb.StringValue retryPolicy *RetryPolicy + targetAppID *string } type RetryPolicy struct { @@ -58,6 +59,13 @@ func (policy *RetryPolicy) Validate() error { return nil } +func WithAppID(targetAppID string) callActivityOption { + return func(opt *callActivityOptions) error { + opt.targetAppID = &targetAppID + return nil + } +} + // WithActivityInput configures an input for an activity invocation. // The specified input must be JSON serializable. func WithActivityInput(input any) callActivityOption { diff --git a/task/orchestrator.go b/task/orchestrator.go index 4835599..ae91aa2 100644 --- a/task/orchestrator.go +++ b/task/orchestrator.go @@ -17,6 +17,7 @@ import ( "github.com/dapr/durabletask-go/api/helpers" "github.com/dapr/durabletask-go/api/protos" "github.com/dapr/durabletask-go/backend" + "github.com/dapr/kit/ptr" ) // Orchestrator is the functional interface for orchestrator functions. @@ -28,6 +29,7 @@ type OrchestrationContext struct { Name string IsReplaying bool CurrentTimeUtc time.Time + appID *string registry *TaskRegistry rawInput []byte @@ -201,6 +203,10 @@ func (ctx *OrchestrationContext) processEvent(e *backend.HistoryEvent) error { // OrchestratorStarted is only used to update the current orchestration time ctx.CurrentTimeUtc = e.Timestamp.AsTime() } else if es := e.GetExecutionStarted(); es != nil { + // Extract source AppID from HistoryEvent Router if this is ExecutionStartedEvent + if e.GetRouter() != nil { + ctx.appID = ptr.Of(e.GetRouter().GetSource()) + } err = ctx.onExecutionStarted(es) } else if ts := e.GetTaskScheduled(); ts != nil { err = ctx.onTaskScheduled(e.EventId, ts) @@ -278,6 +284,17 @@ func (ctx *OrchestrationContext) internalScheduleActivity(activityName, taskExec }, } + // Add TaskRouter support for cross-app activities + if ctx.appID != nil { + scheduleTaskAction.Router = &protos.TaskRouter{ + Source: *ctx.appID, // Current orchestrator app ID + } + + if options.targetAppID != nil { + scheduleTaskAction.Router.Target = options.targetAppID // Target activity app ID + } + } + ctx.pendingActions[scheduleTaskAction.Id] = scheduleTaskAction task := newTask(ctx) @@ -285,6 +302,7 @@ func (ctx *OrchestrationContext) internalScheduleActivity(activityName, taskExec return task } +// TODO: cassie wire appID into suborchestration options too for cross app wf func (ctx *OrchestrationContext) CallSubOrchestrator(orchestrator interface{}, opts ...subOrchestratorOption) Task { options := new(callSubOrchestratorOptions) for _, configure := range opts {