-
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
Support non-linear phase dependencies #122
base: main
Are you sure you want to change the base?
Conversation
Author: James Dabbs <[email protected]> Date: Thu Jan 2 13:45:38 2025 -0800 feat: support non-linear phase dependencies Signed-off-by: Yevhenii Ponomarenko <[email protected]>
@@ -96,6 +96,31 @@ | |||
expect { subject.invoke(:pre_restart) }.to raise_error(HandcuffsPhaseUndefinedError) | |||
end | |||
end | |||
|
|||
context 'explicitly dependency graph' do |
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.
nb:
context 'explicitly dependency graph' do | |
context 'with explicit phase prerequisites' do |
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 updated the rake task in #123
# Prevent running post_restart migrations if there are outstanding | ||
# pre_restart migrations | ||
post_restart: [:pre_restart], |
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.
nb: Even though this is the only additional configuration at the moment, it might worth an additional refactor here to make this more explicit:
Handcuffs.configure do |config|
phases << Phase.new(:pre_restart)
phases << Phase.new(:post_restart, prerequisites: [:pre_restart]
end
# Or obsfuscate the initialization.
Handcuffs.configure do |config|
config.phases = [
:pre_restart,
{ post_restart: { prerequisites: [:pre_restart]}
]
end
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 is sort of where I started, then pared it back a bit. But another thing we want at some point is phase aliases (s.t. we can gradually do simplifying renames), and at that point, you sort of need something like this. Happy to take a swing at adding that.
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 like the explicitness, but the notation gets more complicated and messy, and potentially more intricate parsing and validation as well.
In order to make the current simple config more usable by shortening the feedback loop, I added a rake task that both validates and prints out what Handcuffs perceives to the be ordering and dependencies, so if there is any question in the end users mind they can all ways do a quick check and modify their configuration as needed.
when Hash | ||
phases | ||
else | ||
# Assume each entry depends on all entries before 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.
Potentially a follow up, but in a similar vein as the explicit prerequisite configuration, I'm wondering if this should be explicit as well, instead of an assumption.
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 is actually the way it works now, based on my testing. I updated the README to spell it out here in a proposed update
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.
All for this addition. Some suggestions below on minor changes that could be made here or in a follow up.
attr_accessor :default_phase | ||
|
||
def initialize | ||
@phases = [] | ||
@default_phase = nil | ||
end | ||
|
||
def phases=(phases) | ||
@phases = Phases.new(phases) |
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.
Although there was no format checking prior to this change, a simple array is hard to do incorrectly. But I wonder if it would make sense to evaluate the input now that we are providing the ability to define a graph via a hash. That is much more complex, and maybe better to flag issues at at configuration time rather than flag a configuration error when migrations are actually run.
If nothing else, the configuration class seems like the right place to provide meaningful feedback for invalid configurations.
Two failure cases I could see happening are
(1) Not defining dependencies as an array
phases = { a: [:b, :c], b: [], c: :b }
ph = Phases.new(phases) # <== no errors here
# running migrations leads to:
ph.in_order
(irb):26:in `block in in_order': undefined method `each' for :b:Symbol (NoMethodError)
->(phase, &block) { @phases.fetch(phase).each(&block) }
(2) accidentally defining a circular dependency (not likely in simple setups, but possible in more complex configurations)
phases = { a: [:b, :c], b: [:c], c: [:a] }
ph = Phases.new(phases) # <== no errors here
# running migrations leads to:
ph.in_order
(irb):24:in `in_order': uninitialized constant Phases::TSort (NameError)
TSort.tsort(
^^^^^
end | ||
``` | ||
|
||
The phase order of `handcuffs:migrate[all]` will satisfy these dependencies, but is otherwise unspecified. |
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.
(unless there is an error in the config : )
@@ -72,7 +72,26 @@ You can run all migrations using | |||
rake 'handcuffs:migrate[all]' | |||
``` | |||
|
|||
This differs from running `rake db:migrate` in that migrations will be run in the _order that the phases are defined in the handcuffs config_. | |||
This differs from running `rake db:migrate` in that migrations will be run in the _order that the phases are defined in the handcuffs config_. By default, a phase is assumed to depend on all phases named before it. Phase dependencies can be customized by declaring a dependency graph explicitly: |
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.
We should have a changelog entry as well; either in this PR or when the release is pushed
@jamesdabbs-procore & @yevhenii-ponomarenko - I added a rake task to check/validate the phase configuration and updated the README to (hopefully) be a little more clear about all of the moving parts. If you'd like this PR would merge those changes into this branch |
feat: support non-linear phase dependencies
Checklist: