@@ -535,6 +535,10 @@ type registry struct {
535535 workflowVersioningBehaviorMap map [string ]VersioningBehavior
536536 activityFuncMap map [string ]activity
537537 activityAliasMap map [string ]string
538+ dynamicWorkflow interface {}
539+ dynamicWorkflowOptions DynamicRegisterWorkflowOptions
540+ dynamicActivity activity
541+ _ DynamicRegisterActivityOptions
538542 interceptors []WorkerInterceptor
539543}
540544
@@ -567,7 +571,7 @@ func (r *registry) RegisterWorkflowWithOptions(
567571 }
568572 // Validate that it is a function
569573 fnType := reflect .TypeOf (wf )
570- if err := validateFnFormat (fnType , true ); err != nil {
574+ if err := validateFnFormat (fnType , true , false ); err != nil {
571575 panic (err )
572576 }
573577 fnName , _ := getFunctionName (wf )
@@ -597,6 +601,29 @@ func (r *registry) RegisterWorkflowWithOptions(
597601 }
598602}
599603
604+ func (r * registry ) RegisterDynamicWorkflow (wf interface {}, options DynamicRegisterWorkflowOptions ) {
605+ r .Lock ()
606+ defer r .Unlock ()
607+ // Support direct registration of WorkflowDefinition
608+ factory , ok := wf .(WorkflowDefinitionFactory )
609+ if ok {
610+ r .dynamicWorkflow = factory
611+ r .dynamicWorkflowOptions = options
612+ return
613+ }
614+
615+ // Validate that it is a function
616+ fnType := reflect .TypeOf (wf )
617+ if err := validateFnFormat (fnType , true , true ); err != nil {
618+ panic (err )
619+ }
620+ if r .dynamicWorkflow != nil {
621+ panic ("dynamic workflow already registered" )
622+ }
623+ r .dynamicWorkflow = wf
624+ r .dynamicWorkflowOptions = options
625+ }
626+
600627func (r * registry ) RegisterActivity (af interface {}) {
601628 r .RegisterActivityWithOptions (af , RegisterActivityOptions {})
602629}
@@ -626,7 +653,7 @@ func (r *registry) RegisterActivityWithOptions(
626653 }
627654 return
628655 }
629- if err := validateFnFormat (fnType , false ); err != nil {
656+ if err := validateFnFormat (fnType , false , false ); err != nil {
630657 panic (err )
631658 }
632659 fnName , _ := getFunctionName (af )
@@ -669,7 +696,7 @@ func (r *registry) registerActivityStructWithOptions(aStruct interface{}, option
669696 continue
670697 }
671698 name := method .Name
672- if err := validateFnFormat (method .Type , false ); err != nil {
699+ if err := validateFnFormat (method .Type , false , false ); err != nil {
673700 if options .SkipInvalidStructFunctions {
674701 continue
675702 }
@@ -691,6 +718,26 @@ func (r *registry) registerActivityStructWithOptions(aStruct interface{}, option
691718 return nil
692719}
693720
721+ func (r * registry ) RegisterDynamicActivity (af interface {}, options DynamicRegisterActivityOptions ) {
722+ r .Lock ()
723+ defer r .Unlock ()
724+ // Support direct registration of activity
725+ a , ok := af .(activity )
726+ if ok {
727+ r .dynamicActivity = a
728+ return
729+ }
730+ // Validate that it is a function
731+ fnType := reflect .TypeOf (af )
732+ if err := validateFnFormat (fnType , false , true ); err != nil {
733+ panic (err )
734+ }
735+ if r .dynamicActivity != nil {
736+ panic ("dynamic activity already registered" )
737+ }
738+ r .dynamicActivity = & activityExecutor {name : "" , fn : af , dynamic : true }
739+ }
740+
694741func (r * registry ) RegisterNexusService (service * nexus.Service ) {
695742 if service .Name == "" {
696743 panic (fmt .Errorf ("tried to register a service with no name" ))
@@ -715,8 +762,14 @@ func (r *registry) getWorkflowAlias(fnName string) (string, bool) {
715762func (r * registry ) getWorkflowFn (fnName string ) (interface {}, bool ) {
716763 r .Lock ()
717764 defer r .Unlock ()
718- fn , ok := r .workflowFuncMap [fnName ]
719- return fn , ok
765+ if fn , ok := r .workflowFuncMap [fnName ]; ok {
766+ return fn , ok
767+ }
768+
769+ if r .dynamicWorkflow != nil {
770+ return "dynamic" , true
771+ }
772+ return nil , false
720773}
721774
722775func (r * registry ) getRegisteredWorkflowTypes () []string {
@@ -745,8 +798,13 @@ func (r *registry) addActivityWithLock(fnName string, a activity) {
745798func (r * registry ) GetActivity (fnName string ) (activity , bool ) {
746799 r .Lock ()
747800 defer r .Unlock ()
748- a , ok := r .activityFuncMap [fnName ]
749- return a , ok
801+ if a , ok := r .activityFuncMap [fnName ]; ok {
802+ return a , ok
803+ }
804+ if r .dynamicActivity != nil {
805+ return r .dynamicActivity , true
806+ }
807+ return nil , false
750808}
751809
752810func (r * registry ) getActivityNoLock (fnName string ) (activity , bool ) {
@@ -757,10 +815,17 @@ func (r *registry) getActivityNoLock(fnName string) (activity, bool) {
757815func (r * registry ) getRegisteredActivities () []activity {
758816 r .Lock ()
759817 defer r .Unlock ()
760- activities := make ([]activity , 0 , len (r .activityFuncMap ))
818+ numActivities := len (r .activityFuncMap )
819+ if r .dynamicActivity != nil {
820+ numActivities ++
821+ }
822+ activities := make ([]activity , 0 , numActivities )
761823 for _ , a := range r .activityFuncMap {
762824 activities = append (activities , a )
763825 }
826+ if r .dynamicActivity != nil {
827+ activities = append (activities , r .dynamicActivity )
828+ }
764829 return activities
765830}
766831
@@ -788,7 +853,12 @@ func (r *registry) getWorkflowDefinition(wt WorkflowType) (WorkflowDefinition, e
788853 if ok {
789854 return wdf .NewWorkflowDefinition (), nil
790855 }
791- executor := & workflowExecutor {workflowType : lookup , fn : wf , interceptors : r .interceptors }
856+ var dynamic bool
857+ if d , ok := wf .(string ); ok && d == "dynamic" {
858+ wf = r .dynamicWorkflow
859+ dynamic = true
860+ }
861+ executor := & workflowExecutor {workflowType : lookup , fn : wf , interceptors : r .interceptors , dynamic : dynamic }
792862 return newSyncWorkflowDefinition (executor ), nil
793863}
794864
@@ -799,8 +869,16 @@ func (r *registry) getWorkflowVersioningBehavior(wt WorkflowType) (VersioningBeh
799869 }
800870 r .Lock ()
801871 defer r .Unlock ()
802- behavior := r .workflowVersioningBehaviorMap [lookup ]
803- return behavior , behavior != VersioningBehaviorUnspecified
872+ if behavior , ok := r .workflowVersioningBehaviorMap [lookup ]; ok {
873+ return behavior , behavior != VersioningBehaviorUnspecified
874+ }
875+ if r .dynamicWorkflowOptions .LoadDynamicRuntimeOptions != nil {
876+ config := LoadDynamicRuntimeOptionsDetails {WorkflowType : wt }
877+ if behavior , err := r .dynamicWorkflowOptions .LoadDynamicRuntimeOptions (config ); err == nil {
878+ return behavior .VersioningBehavior , true
879+ }
880+ }
881+ return VersioningBehaviorUnspecified , false
804882}
805883
806884func (r * registry ) getNexusService (service string ) * nexus.Service {
@@ -820,7 +898,7 @@ func (r *registry) getRegisteredNexusServices() []*nexus.Service {
820898}
821899
822900// Validate function parameters.
823- func validateFnFormat (fnType reflect.Type , isWorkflow bool ) error {
901+ func validateFnFormat (fnType reflect.Type , isWorkflow , isDynamic bool ) error {
824902 if fnType .Kind () != reflect .Func {
825903 return fmt .Errorf ("expected a func as input but was %s" , fnType .Kind ())
826904 }
@@ -845,6 +923,17 @@ func validateFnFormat(fnType reflect.Type, isWorkflow bool) error {
845923 }
846924 }
847925
926+ if isDynamic {
927+ if fnType .NumIn () != 2 {
928+ return fmt .Errorf (
929+ "expected function to have two arguments, first being workflow.Context and second being an EncodedValues type, found %d arguments" , fnType .NumIn (),
930+ )
931+ }
932+ if fnType .In (1 ) != reflect .TypeOf ((* converter .EncodedValues )(nil )).Elem () {
933+ return fmt .Errorf ("expected function to EncodedValues as second argument, got %s" , fnType .In (1 ).Elem ())
934+ }
935+ }
936+
848937 // Return values
849938 // We expect either
850939 // <result>, error
@@ -888,17 +977,25 @@ type workflowExecutor struct {
888977 workflowType string
889978 fn interface {}
890979 interceptors []WorkerInterceptor
980+ dynamic bool
891981}
892982
893983func (we * workflowExecutor ) Execute (ctx Context , input * commonpb.Payloads ) (* commonpb.Payloads , error ) {
894984 dataConverter := WithWorkflowContext (ctx , getWorkflowEnvOptions (ctx ).DataConverter )
895985 fnType := reflect .TypeOf (we .fn )
896986
897- args , err := decodeArgsToRawValues (dataConverter , fnType , input )
898- if err != nil {
899- return nil , fmt .Errorf (
900- "unable to decode the workflow function input payload with error: %w, function name: %v" ,
901- err , we .workflowType )
987+ var args []interface {}
988+ var err error
989+ if we .dynamic {
990+ // Dynamic workflows take in a single EncodedValues, encode all data into single EncodedValues
991+ args = []interface {}{newEncodedValues (input , dataConverter )}
992+ } else {
993+ args , err = decodeArgsToRawValues (dataConverter , fnType , input )
994+ if err != nil {
995+ return nil , fmt .Errorf (
996+ "unable to decode the workflow function input payload with error: %w, function name: %v" ,
997+ err , we .workflowType )
998+ }
902999 }
9031000
9041001 envInterceptor := getWorkflowEnvironmentInterceptor (ctx )
@@ -918,6 +1015,7 @@ type activityExecutor struct {
9181015 name string
9191016 fn interface {}
9201017 skipInterceptors bool
1018+ dynamic bool
9211019}
9221020
9231021func (ae * activityExecutor ) ActivityType () ActivityType {
@@ -932,11 +1030,18 @@ func (ae *activityExecutor) Execute(ctx context.Context, input *commonpb.Payload
9321030 fnType := reflect .TypeOf (ae .fn )
9331031 dataConverter := getDataConverterFromActivityCtx (ctx )
9341032
935- args , err := decodeArgsToRawValues (dataConverter , fnType , input )
936- if err != nil {
937- return nil , fmt .Errorf (
938- "unable to decode the activity function input payload with error: %w for function name: %v" ,
939- err , ae .name )
1033+ var args []interface {}
1034+ var err error
1035+ if ae .dynamic {
1036+ // Dynamic activities take in a single EncodedValues, encode all data into single EncodedValues
1037+ args = []interface {}{newEncodedValues (input , dataConverter )}
1038+ } else {
1039+ args , err = decodeArgsToRawValues (dataConverter , fnType , input )
1040+ if err != nil {
1041+ return nil , fmt .Errorf (
1042+ "unable to decode the activity function input payload with error: %w for function name: %v" ,
1043+ err , ae .name )
1044+ }
9401045 }
9411046
9421047 return ae .ExecuteWithActualArgs (ctx , args )
@@ -1044,6 +1149,19 @@ func (aw *AggregatedWorker) RegisterWorkflowWithOptions(w interface{}, options R
10441149 aw .registry .RegisterWorkflowWithOptions (w , options )
10451150}
10461151
1152+ // RegisterDynamicWorkflow registers dynamic workflow implementation with the AggregatedWorker
1153+ func (aw * AggregatedWorker ) RegisterDynamicWorkflow (w interface {}, options DynamicRegisterWorkflowOptions ) {
1154+ if aw .workflowWorker == nil {
1155+ panic ("workflow worker disabled, cannot register workflow" )
1156+ }
1157+ if options .LoadDynamicRuntimeOptions == nil && aw .executionParams .UseBuildIDForVersioning &&
1158+ (aw .executionParams .DeploymentSeriesName != "" || aw .executionParams .WorkerDeploymentVersion != "" ) &&
1159+ aw .executionParams .DefaultVersioningBehavior == VersioningBehaviorUnspecified {
1160+ panic ("dynamic workflow does not have a versioning behavior" )
1161+ }
1162+ aw .registry .RegisterDynamicWorkflow (w , options )
1163+ }
1164+
10471165// RegisterActivity registers activity implementation with the AggregatedWorker
10481166func (aw * AggregatedWorker ) RegisterActivity (a interface {}) {
10491167 aw .registry .RegisterActivity (a )
@@ -1054,6 +1172,12 @@ func (aw *AggregatedWorker) RegisterActivityWithOptions(a interface{}, options R
10541172 aw .registry .RegisterActivityWithOptions (a , options )
10551173}
10561174
1175+ // RegisterDynamicActivity registers the dynamic activity function with options.
1176+ // Registering activities via a structure is not supported for dynamic activities.
1177+ func (aw * AggregatedWorker ) RegisterDynamicActivity (a interface {}, options DynamicRegisterActivityOptions ) {
1178+ aw .registry .RegisterDynamicActivity (a , options )
1179+ }
1180+
10571181func (aw * AggregatedWorker ) RegisterNexusService (service * nexus.Service ) {
10581182 if aw .started .Load () {
10591183 panic (errors .New ("cannot register Nexus services after worker start" ))
@@ -1334,6 +1458,11 @@ func (aw *WorkflowReplayer) RegisterWorkflowWithOptions(w interface{}, options R
13341458 aw .registry .RegisterWorkflowWithOptions (w , options )
13351459}
13361460
1461+ // RegisterDynamicWorkflow registers a dynamic workflow function to replay
1462+ func (aw * WorkflowReplayer ) RegisterDynamicWorkflow (w interface {}, options DynamicRegisterWorkflowOptions ) {
1463+ aw .registry .RegisterDynamicWorkflow (w , options )
1464+ }
1465+
13371466// ReplayWorkflowHistoryWithOptions executes a single workflow task for the given history.
13381467// Use for testing the backwards compatibility of code changes and troubleshooting workflows in a debugger.
13391468// The logger is an optional parameter. Defaults to the noop logger.
0 commit comments