-
Notifications
You must be signed in to change notification settings - Fork 7
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
question/improve: add helper functions for changing options #58
Comments
I like this proposal! That said, I think they should be eventually consistent. Consider the following example: service Example {
rpc Foo(FooInput) returns (FooOutput) {
option (temporal.v1.activity) = {
start_to_close_timeout: { seconds: 60 }
}
}
} baseActivityOptions := workflow.ActivityOptions{StartToCloseTimeout: 30 * time.Second}
optsA := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(baseActivityOptions).WithStartToCloseTimeout(10 * time.Second)
optsB := examplev1.NewExampleV1ActivityOptions().WithStartToCloseTimeout(10 * time.Second).WithActivityOptions(baseActivityOptions) calling either of the following should result in the same value applied to the activity's examplev1.Foo(ctx, &examplev1.FooInput{}, optsA)
examplev1.Foo(ctx, &examplev1.FooInput{}, optsB) This would require defining (and documenting) a preference order, e.g.
I think this is doable, just a little bit trickier than the sample after code above. I also think that the other options flavors (e.g. WorkflowOptions, ChildWorkflowOptions, LocalActivityOptions, etc) should probably do the same for consistency. If we were to go in this direction, it's probably worth refactoring how the generated options code works. Currently the various resource method/functions build the necessary sdk options from the generated option structs (if provided) and handle applying defaults. I'd probably refactor the generated options to handle this part by adopting the builder pattern, i.e.: type FooActivityOptions struct {
opts *workflow.ActivityOptions
startToCloseTimeout time.Duration
// ...
}
func (opts *FooActivityOptions) WithActivityOptions(ao workflow.ActivityOptions) {
opts.opts = &ao
}
func (opts *FooActivityOptions) WithStartToCloseTimeout(d time.Duration) {
opts.startToCloseTimeout = d
}
func (opts *FooActivityOptions) buildActivityOptions() (ao workflow.ActivityOptions, err error) {
if opts.opts != nil {
ao = *opts.opts
}
if d := opts.startToCloseTimeout; d > 0 {
ao.StartToCloseTimeout = d
}
// if schema defines a default
if ao.StartToCloseTimeout == 0 {
ao.StartToCloseTimeout = 60000000000 // 1m
}
// ...
return ao, nil
} Thoughts? |
|
re:
The main difference between this scenario and SDK's behavior, which is not documented well at the moment, is that the generated code starts with an empty options value. It then checks if the invocation provided an options value, and if so, replaces its initial options with those that were passed in by the I think my preference would be to follow a similar approach with field-specific options, where these would take precedence over the user-provided options value and also the schema defaults.
Using my previous example: service Example {
rpc Foo(FooInput) returns (FooOutput) {
option (temporal.v1.activity) = {
start_to_close_timeout: { seconds: 60 }
}
}
} optsA := examplev1.NewExampleV1ActivityOptions()
// workflow.GetActivityOptions(ctx).StartToCloseTimeout -> 60s
ao := workflow.ActivityOptions{HeartbeatTimeout: 30 * time.Second}
optsB := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao)
// workflow.GetActivityOptions(ctx).StartToCloseTimeout -> 60s
ao.StartTocloseTimeout: 30 * time.Second}
optsC := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao)
// workflow.GetActivityOptions(ctx).StartToCloseTimeout -> 30s
optsD := examplev1.NewExampleV1ActivityOptions().WithStartToCloseTimeout(10 * time.Second)
// workflow.GetActivityOptions(ctx).StartToCloseTimeout -> 10s
optsE := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao).WithStartToCloseTimeout(10 * time.Second)
// workflow.GetActivityOptions(ctx).StartToCloseTimeout -> 10s
optsF := examplev1.NewExampleV1ActivityOptions().WithStartToCloseTimeout(10 * time.Second).WithActivityOptions(ao)
// workflow.GetActivityOptions(ctx).StartToCloseTimeout -> 10s re:
While I like this idea, and can appreciate the desire to minimize the size of the generated code, I'm not sure this would work for every field. Consider the following example: service Example {
rpc Foo(FooInput) returns (FooOutput) {
option (temporal.v1.activity) = {
wait_for_cancellation: true
}
}
} optsA := examplev1.NewExampleV1ActivityOptions()
// workflow.GetActivityOptions(ctx).WaitForCancellation -> true
ao := workflow.ActivityOptions{WaitForCancellation: false}
optsB := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao)
// workflow.GetActivityOptions(ctx).WaitForCancellation -> true
optsB := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao).WithWaitForCancellation(false)
// workflow.GetActivityOptions(ctx).WaitForCancellation -> false It's not possible to differentiate between an unspecified options value and an explicit import (
"go.temporal.io/sdk/workflow"
"google.golang.org/protobuf/types/known/wrapperspb"
)
type FooActivityOptions struct {
opts *workflow.ActivityOptions
waitForCancellation *bool
waitForCancellation *wrapperspb.BoolValue
} I think a better opportunity for reduce boilerplate would be to refactor the generated code to use a single implementation of each option type instead of separate one for each rpc. When this was originally create prior to Go generics, per-rpc implementations were necessary to handle the |
I agree for this solution (added filds for structs) because nullable value without ptr for boolean not possible checking setted it or not (maybe other types same). However, problem for boolean types (for example) in service Example {
rpc Foo(FooInput) returns (FooOutput) {
option (temporal.v1.activity) = {
wait_for_cancellation: true
}
}
} ao := workflow.ActivityOptions{WaitForCancellation: false, HeartbeatTimeout: 10 * time.Second}
opts := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao)
// execute activity options: `WaitForCancellation` -> `true`
// only this pattern working
ao := workflow.ActivityOptions{HeartbeatTimeout: 10 * time.Second}
opts := examplev1.NewExampleV1ActivityOptions().WithActivityOptions(ao).WithWaitForCancellation(false) // or with changing order `With` funcs calls
// execute activity options: `WaitForCancellation` -> `false` |
Absolutely, currently it's not possible to override a schema default of It would be possible if we added these proposed field-specific options. As part of this effort, updating the documentation to highlight the order of option precedence and behavior with empty vs. specific "falsey" values will need to be added. |
I see u create PR, however i continue mind and detect case:
For ctx have func Question: can we assume that example: ctx = ctx.WithDataConverter(ctx, converter)
opts = examplev1.NewExampleV1WorkflowOptions().WithChildOptions(options)
examplev1.NewExampleV1(ctx, &examplev1.ExampleV1Request{}, opts) -> if we assume that opts = examplev1.NewExampleV1WorkflowOptions().WithChildOptions(options).WithDataConverter(conveter)
examplev1.NewExampleV1(ctx, &examplev1.ExampleV1Request{}, opts)
// in generated code:
type ExampleV1ChildOptions struct {
opts *workflow.ChildWorkflowOptions
// ...
dataConverter *converter.DataConverter
}
func ExampleV1Child(ctx workflow.Context, req *ExampleV1Request, options ...*ExampleV1ChildOptions) (*ExampleV1ChildRun, error) {
opts := &workflow.ChildWorkflowOptions{}
if len(options) > 0 && options[0].opts != nil {
opts = options[0].opts
}
if opts.TaskQueue == "" {
opts.TaskQueue = XnsTaskQueue
}
// ...
ctx = workflow.WithChildOptions(ctx, *opts)
if c := options[0].dataConverter; c != nil {
ctx = workflow.WithDataConverter(ctx, *c)
}
// ...
} |
I like this proposal 👍 let's split it out as a separate issue/request. |
Override default options (ex. activity options) need using
.WithActivityOptions
method because options included in context will be rewritten with define in proto (context options ignoring if have default - https://github.com/cludden/protoc-gen-go-temporal/blob/main/gen/example/v1/example_temporal.pb.go#L857) ..WithActivityOptions
- method overide all options, not have varinat chnaging one.Before this codegen we using options in ctx for execute activities/child workflow as:
for now if based from generated code and need change one default options already defined in proto:
My idea/concept for adding all methods for options activity & workflow (https://github.com/temporalio/sdk-go/blob/master/workflow/activity_options.go#L59 - for activity example):
generated code after:
I need your opinion for this concept.
The text was updated successfully, but these errors were encountered: