-
Notifications
You must be signed in to change notification settings - Fork 27
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
Workflow update #142
Workflow update #142
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't finish the entire review, I'll try to continue this tomorrow.
await handle.SignalWorkflowAsync(wf => wf.UpdateGreetingParamsAsync(signalArg)); | ||
// Change the params via update | ||
var updateArg = new GreetingParams(Salutation: "Aloha", Name: "John"); | ||
await handle.ExecuteUpdateAsync(wf => wf.UpdateGreetingParamsAsync(updateArg)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth showing that this can return a value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that's necessary in this README, I think that can happen when this is properly documented. Note I am not showing you can set an ID either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth it IMHO, otherwise it's unclear how this is different from a signal. Of course it'll be properly documented later but for a Temporal noob this would be a bit confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. No reason not to show it, and at least this reason to show it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's reading too much into tiny sample (aren't they gonna get confused w/ update vs query? what about showing that queries can accept parameters? what about showing validators?), but I will add it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrmm, I'm struggling to fit the update-with-return value use case into the above sample. Any ideas on what I can change the GreetingWorkflow
's UpdateGreetingParamsAsync
to for demonstrating a return value?
(@Sushisource - you'll find the same challenge when you update the Python README)
Updates made based on team discussions |
a1817d8
to
7c8154f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Just some minor stuff.
/// <param name="rpcOptions">Extra RPC options.</param> | ||
/// <returns>Completed update task.</returns> | ||
/// <remarks>WARNING: Workflow update is experimental and APIs may change.</remarks> | ||
public Task GetResultAsync(TimeSpan? timeout = null, RpcOptions? rpcOptions = null) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit inclined to say this should actually have a different name if it disregards the result. Like WaitForCompletedAsync
.
But, ignoring that - why even bother with this separately. Can't someone just use the result-returning version and just not use the result?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit inclined to say this should actually have a different name if it disregards the result. Like WaitForCompletedAsync.
This matches the workflow get result (and we tend to kinda call it that for things that can optionally return results like workflows and activities). Users may get confused that the presence of a return value changes the function name since other than return value they are the same.
But, ignoring that - why even bother with this separately. Can't someone just use the result-returning version and just not use the result?
In .NET Task
and Task<Result>
are distinctly defined and there is no "unit" or "void" type for these purposes in .NET. So every .NET call (e.g. Task.Run) has to differentiate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aaah, ok, makes sense.
/// Intercept update validation. This is invoked every time validation is needed regardless | ||
/// of whether a validator was defined. | ||
/// </summary> | ||
/// <param name="input">Input details of the call.</param> | ||
public virtual void ValidateUpdate(HandleUpdateInput input) => Next.ValidateUpdate(input); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my implementation I only invoke if the validator is defined. I can see doing it always, if you want some blanket way to reject based on... whatever... without defining a validator for every update. I'll change that in my impl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Yeah I went back and forth here too but I figured "why not"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, after team discussion, here is what we want the SDK logic to be due to possible mutation of input and performance:
- if core determines validation should occur
- If validator present
- decode args for validator (or reject on fail)
- call validator (interceptor) w/ those args
- decode args for update (or reject on fail)
- accept
- If validator present
- else
- decode args for update (or task-fail on fail)
- accept
- call updator (interceptor) w/ those args
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic has been updated to support this approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to beat a dead horse, but I thought if we have to decode the args we would go through the interceptor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because it is a non trivial change in go and java to even know if a validator is present or not without going through the interceptor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to beat a dead horse
No problem, we need to get this right/consistent. Happy to discuss as much as is necessary.
So double-decoding args has already been settled to prevent validator mutability. We will discuss as a team whether to prevent interceptor call if validator not present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, after discussion, the logic in the bullets above is what we want. Don't run validate interceptor (and therefore don't decode args for it) if a validator is not present
// Capture command count so we can ensure it is not altered during validation | ||
var origCmdCount = completion?.Successful?.Commands?.Count ?? 0; | ||
inbound.Value.ValidateUpdate(updateInput); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems this would be useful to put behind some kind of "read only mode" protector like exists in Python / Java?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this was only the second time I needed such a thing and the error message is different enough and impl simpl;e enough that I didn't make reuseable (if .NET had simple yield-based contextmanager type thing like Python, definitely).
dynamic: updateDefn.Dynamic, | ||
dynamicArgPrepend: update.Name); | ||
|
||
// Do validation. Whether or not this runs a validator, this should accept/reject. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Do validation. Whether or not this runs a validator, this should accept/reject. | |
// Do validation. Whether or not this runs a validator, this must accept/reject. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're overthinking the terms used in simple internal English comments as if they're an RFC heh
|
||
// We want to try to decode args here _inside_ the validator rejection | ||
// try/catch so we can prevent acceptance on invalid args | ||
argsForUpdate = DecodeUpdateArgs(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you move this down a couple of lines to outside the if (runValidator)
, then it will be inside the validator rejection try/catch, but it will always run. Then you can eliminate the other call below in the handler section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't want to catch argument decoding failure validator-style w/ rejection if run validator is false. If run validator is false, I don't want to decode until later, where the failure is a task failure not a rejection.
{ | ||
// If the args were not already decoded because we didn't run the validation | ||
// step, decode them here | ||
argsForUpdate ??= DecodeUpdateArgs(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, you can eliminate this line by always decoding inside the validation try
above. (Just a clean up -- I know the way you have it it won't be called more than necessary.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to decode in the try above, I want this non-failure-exception to throw out of this method and therefore fail the task.
}, | ||
}); | ||
return Task.CompletedTask; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To check my understanding, exceptions other than Temporal FailureException
here bubble up and fail the workflow task, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. So we only reject/fail update if there is an exception when run-validator is true or there is a failure exception. Otherwise we fail the task.
// Do validation. Whether or not this runs a validator, this should accept/reject. | ||
try | ||
{ | ||
if (update.RunValidator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go and Java do not run validation when replaying. Do we need to put the same check in place here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Core is doing that check for you and setting this boolean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah-ha. I had not understood that, thanks.
What was changed
[WorkflowUpdate]
and[WorkflowUpdateValidator]
attributesWorkflowUpdateHandle
andExecuteWorkflowUpdateAsync
extension that is justStartWorkflowUpdateAsync
+GetResultAsync
Checklist