-
Notifications
You must be signed in to change notification settings - Fork 47
[BREAKING CHANGE]: update WorkflowAction API to expose access to Workflow instance values #349
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
Conversation
6446a18
to
e05bd71
Compare
e05bd71
to
2d260c7
Compare
@@ -27,14 +27,22 @@ public protocol WorkflowAction<WorkflowType> { | |||
/// | |||
/// - Returns: An optional output event for the workflow. If an output event is returned, it will be passed up | |||
/// the workflow hierarchy to this workflow's parent. | |||
func apply(toState state: inout WorkflowType.State) -> WorkflowType.Output? | |||
func apply( |
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.
Any thoughts around offering both the old and the new functions to avoid changes to lots of files?
I.e., if you added the new one, along with a default impl that called the old one, could we get away with only changing existing action types that need access to the context
?
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.
this was explored a few times in the past and i believe the conclusion was that it would be better to 'rip the bandaid off' so to speak. IMO the primary problem with trying to be source compatible is that you end up having to expose sort of redundant protocol requirements. yes, you can default the one with the new param to forward to the one without it, but it's still sort of an odd API. i think ideally you'd want them to be mutually exclusive, but i'm not sure that's expressible.
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.
that being said – if you've got alternate ideas, happy to hear them!
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.
Jamie has already explored ways to automate a large part of this migration, so I am fine with making the breaking change. It's why we have semver!
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, that makes sense - thanks for sharing the previous exploration!
func apply( | ||
toState state: inout WorkflowType.State, | ||
context: ApplyContext<WorkflowType> | ||
) -> WorkflowType.Output? | ||
} |
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 was wondering if there's some clever way to introduce this in a non-breaking way with protocol extensions (to ease the initial integration/migration) but I don't think there is... Throwing it out there in case someone smarter than me sees a way to make it work.
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.
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.
Much better than having to store stuff in State that is not state. 👍
*/ | ||
|
||
/// Internal utility protocol so that `WorkflowTesting` can provide an alternate implementation | ||
protocol ApplyContextType<WorkflowType> { |
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 no longer certain this abstraction is needed. i mostly copied it from the analogous thing done for RenderTester, but unlike in that case i don't think the testing infra relies on 'real' internals to apply actions.
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've got several questions but no big blockers. Thanks for tackling this!
I also noticed you're adding several TODO comments, where it's not entirely clear if you were intending to address them with this PR or if you're just marking new tech debt.
@@ -44,7 +44,7 @@ public struct StateMutationSink<WorkflowType: Workflow> { | |||
/// - update: The `State` mutation to perform. | |||
public func send(_ update: @escaping (inout WorkflowType.State) -> Void) { |
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.
thoughts on exposing the ApplyContext to StateMutationSink?
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 that probably makes sense to do, but would prefer to tackle as a follow-up for scope-management reasons, unless you feel this will immediately be desired. i think it's a straightforward plumbing change, and i may have had it implemented at one point but removed 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 don't have any need for it immediately, was just curious.
} | ||
|
||
public subscript<Value>( | ||
workflowValue keyPath: KeyPath<WorkflowType, 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.
Decided not to use dynamic property lookup?
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.
yes, for now anyway. this will make callsites more verbose, but also easier to locate in the source code, and somewhat more explicit about what's going on.
func concreteApplyContextInvalidatedAfterUse() async throws { | ||
var escapedContext: ApplyContext<EscapingContextWorkflow>? | ||
let onApply = { (context: ApplyContext<EscapingContextWorkflow>) in | ||
#expect(context[workflowValue: \.property] == 42) |
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.
These are the first #expect
-based tests in Workflow, I believe. Just to confirm, did you check that failures are caught in CI?
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.
thanks for the callout. yes, i did a 'canary' run yesterday with a test that should have failed CI, and it seemed to behave as expected
86c2e57
to
9063748
Compare
this BREAKING change updates the WorkflowAction API to pass through a new parameter in the
apply()
method that allows reading values off the corresponding node's current Workflow instance. the new method signature is:which mirrors the
render()
API, but uses a newApplyContext
parameter to expose access to the runtime-managed data. the public API forApplyContext
exposes a read-only subscript for accessing Workflow values via keypath:for reviewing purposes, i'd suggest using the 'commit filter' function to exclude the commit labeled 'AUTOMATED' as that one is essentially just noise where a bunch of new parameters were added.
also, there's definitely some more documentation to be written here, but i've currently punted on that somewhat. do feel free to point out places you'd like to see anything specific though.