-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
Call a global #on_failure hook when the flow fails #14
Conversation
@timriley, the |
2240c5d
to
f07b0cb
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.
This is looking really good! I've left a couple of notes that I think we would be good to address before we merge this (and a few thoughts that are for well beyond this PR, but they felt relevant to include along the way) — let me know what you think!
BTW, I'm fine with StepsMethodPrepender
to continue being the name for now :)
0a71b69
to
4845948
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.
Left a couple extra notes and one suggestion for a change — once you've considered that, then I think this is good to merge! Thanks @waiting-for-dev!
Most of the time, individual operations are responsible for handling their own failures. However, there are cases when it makes sense to handle failures globally, for example, when you want to log the error for a given flow. When using the raw behavior, i.e., when we don't prepend around methods, that can be easily handled manually: ```ruby class CreateUser < Dry::Operation skip_prepending def call(input) steps do attrs = step validate(input) step persist(attrs) user end.tap do |result| log_failure(result.failure) if result.failure? end end # ... end ``` However, by automatically wrapping around `#steps` we gain focus on the happy path, but we lose the ability to handle global failures. Because of that, we introduce an `#on_failure` hook that is only called when using the prepended behavior on a failing flow. The method accepts the unwrapped failure extracted from the result object. ```ruby class CreateUser < Dry::Operation def call(input) attrs = step validate(input) step persist(attrs) user end private def on_failure(failure) log_failure(failure) end # ... end ``` `#on_failure` can also take a second optional argument, which will be assigned to the prepended method's name. That's useful when we're defining more than one flow in a single class. ```ruby class UserFlows < Dry::Operation operate_on :create_user, :delete_user # ... private def on_failure(failure, flow_name) case flow_name when :create_user # ... when :delete_user # ... end end # ... end ``` The calling of the hook is done via an injected result handler lambda. At some point, we might want to make this behavior configurable.
4845948
to
b84bd9b
Compare
@waiting-for-dev Have answered the final outstanding question, this looks good to merge! |
Most of the time, individual operations are responsible for handling their own failures. However, there are cases when it makes sense to handle failures globally, for example, when you want to log the error for a given flow.
When using the raw behavior, i.e., when we don't prepend around methods, that can be easily handled manually:
However, by automatically wrapping around
#steps
we gain focus on the happy path, but we lose the ability to handle global failures.Because of that, we introduce an
#on_failure
hook that is only called when using the prepended behavior on a failing flow. The method accepts the unwrapped failure extracted from the result object.#on_failure
can also take a second optional argument, which will be assigned to the prepended method's name. That's useful when we're defining more than one flow in a single class.The calling of the hook is done via an injected result handler lambda. At some point, we might want to make this behavior configurable.