Skip to content

Dynamic workflows and activities #1946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 16, 2025
Merged

Conversation

yuandrew
Copy link
Contributor

@yuandrew yuandrew commented May 2, 2025

What was changed

Added support for dynamic workflows and activities.

Wrote a sample-go sample temporalio/samples-go#404, will merge once this feature goes in

Why?

Feature parity with other SDKs

Checklist

  1. Closes Support dynamic activity and workflow implementations #543
    Queries, signals, updates are tracked in a different issue: Support dynamic query and update handler #997

  2. How was this tested:
    Added new unit tests and integration tests

  3. Any docs updates needed?

@yuandrew yuandrew force-pushed the dynamic-workflows branch from 0dba4f7 to ad35c64 Compare May 6, 2025 05:05
@yuandrew yuandrew changed the title [draft] Dynamic workflows Dynamic workflows and activities May 6, 2025
@yuandrew yuandrew marked this pull request as ready for review May 6, 2025 05:11
@yuandrew yuandrew requested a review from a team as a code owner May 6, 2025 05:11
@@ -80,6 +80,11 @@ func decodeArgsToRawValues(dc converter.DataConverter, fnType reflect.Type, data
}

// Unmarshal

// Dynamic workflows take in a single EncodedValues, encode all data into single EncodedValues
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it would be varargs of RawValue, but encoded values makes sense too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unfortunate we have both concepts, but EncodedValues is more appropriate for dynamic workflows

Copy link
Member

@cretz cretz May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrmm. I think they're similar from a user POV for params (I personally prefer codec to apply outside workflow boundary), but we should allow any return value IMO.

In some SDKs we do not require a certain signature for a dynamic workflow. So if they want a dynamic workflow that accepts a string parameter, ok, it'll just fail with others. We offer the raw value option to all workflows including dynamic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the dynamic workflow should be allowed to return anything I agree, I was referring to the input type

@@ -80,6 +80,11 @@ func decodeArgsToRawValues(dc converter.DataConverter, fnType reflect.Type, data
}

// Unmarshal

// Dynamic workflows take in a single EncodedValues, encode all data into single EncodedValues
if fnType.NumIn() == 2 && len(pointers) == 1 && fnType.In(1) == reflect.TypeOf((*converter.EncodedValues)(nil)).Elem() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrmm, wonder if it's worth a more specific check whether it's a dynamic workflow/activity instead of assuming so based on signature? Maybe not worth it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I would consider a more specific check or just not sue this function for dynamic workflows/activities

@@ -929,7 +1011,10 @@ func (we *workflowExecutor) Execute(ctx Context, input *commonpb.Payloads) (*com
// Execute and serialize result
result, err := envInterceptor.inboundInterceptor.ExecuteWorkflow(ctx, &ExecuteWorkflowInput{Args: args})
var serializedResult *commonpb.Payloads
if err == nil && result != nil {
// Dynamic workflows always return EncodedValues, skip encoding because result should already be encoded
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they always return encoded values? There is no validation of this return type at registration time, and they don't really need to. For many, it may make more sense to return an any or a RawValue. Also, even if we do support encoded value as a return type, one might expect a single encoded value instead of plural (or at least have the option).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it is a good question, In Java we require dynamic workflows to return an Object so that is like any in Go.

Copy link
Member

@cretz cretz May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of any, I think we should have no requirement on return type at all. Returning a RawValue should be just fine for example. Even returning a string or no return value besides error I think should be fine. Although we did in the past, we don't have to require strict signatures nowadays for dynamic things if we don't want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of matching the other non-Java SDKs and not requiring a return value. I don't think adding a return type requirement adds any guard rails for the users, so I wanna say matching behavior of the majority of the SDKs would be better.

// DynamicWorkflowOptions are options for a dynamic workflow.
//
// Exposed as: [go.temporal.io/sdk/workflow.DynamicWorkflowOptions]
DynamicWorkflowOptions struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need a different name here. The concept of "workflow options" is traditionally used by workflow callers to set options about the workflow they are calling (e.g. StartWorkflowOptions and ChildWorkflowOptions).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I see what you're saying, maybe config?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure, maybe DynamicWorkflowWorkerOptions or something like that. @Quinn-With-Two-Ns - any ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RegisterDynamicWorkflowOptions ? similar to RegisterWorkflowOptions

@Quinn-With-Two-Ns
Copy link
Contributor

I think this is a great case to try adding a sample and link to it in the PR, what do you think @yuandrew ?

@yuandrew
Copy link
Contributor Author

yuandrew commented May 6, 2025

Good idea, added a sample and linked in PR description

Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything LGTM but still hung up on the naming of the type returned from LoadDynamicOptions

// Exposed as: [go.temporal.io/sdk/workflow.DynamicRegisterOptions]
DynamicRegisterWorkflowOptions struct {
// Allows dynamic options to be loaded for a workflow.
LoadDynamicOptions func(details LoadDynamicOptionsDetails) (RegisterDynamicWorkflowOptions, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about this, the concept of DynamicRegisterWorkflowOptions (even if exposed as DynamicRegisterOptions) and RegisterDynamicWorkflowOptions is just too confusingly named.

I think we should use the word "runtime" in here maybe, e.g.:

Suggested change
LoadDynamicOptions func(details LoadDynamicOptionsDetails) (RegisterDynamicWorkflowOptions, error)
LoadDynamicRuntimeOptions func(details LoadDynamicRuntimeOptionsDetails) (DynamicRuntimeWorkflowOptions, error)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine with me, whatever we do here we should also change the dynamic activity options name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only specifically concerning runtime loading of workflow options by workflow type and has no activity anallog at this time

@@ -978,8 +1082,11 @@ func (ae *activityExecutor) ExecuteWithActualArgs(ctx context.Context, args []in
result, resultErr := interceptor.ExecuteActivity(ctx, &ExecuteActivityInput{Args: args})
var serializedResult *commonpb.Payloads
if result != nil {
// Dynamic activities always return EncodedValues, skip encoding because result should already be encoded
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we say we wanted dynamic workflows and activities to be able to return any value?

@yuandrew yuandrew enabled auto-merge (squash) May 16, 2025 00:31
@yuandrew yuandrew merged commit f55d382 into temporalio:master May 16, 2025
14 checks passed
@yuandrew yuandrew deleted the dynamic-workflows branch May 16, 2025 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support dynamic activity and workflow implementations
3 participants