Skip to content
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

Merged
merged 1 commit into from
Jun 22, 2024

Conversation

waiting-for-dev
Copy link
Member

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:

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.

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.

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.

@waiting-for-dev
Copy link
Member Author

@timriley, the StepsMethodPrepender module is now doing more than just prepending with #steps, as it's also invoking the result handler. Do we want to rename it? What do you think about something along the lines of Streamliner, Enhancer or Facilitator?

Copy link
Member

@timriley timriley left a 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 :)

lib/dry/operation.rb Outdated Show resolved Hide resolved
lib/dry/operation/class_context/steps_method_prepender.rb Outdated Show resolved Hide resolved
lib/dry/operation/errors.rb Outdated Show resolved Hide resolved
lib/dry/operation/class_context/steps_method_prepender.rb Outdated Show resolved Hide resolved
lib/dry/operation/class_context/steps_method_prepender.rb Outdated Show resolved Hide resolved
lib/dry/operation/class_context/steps_method_prepender.rb Outdated Show resolved Hide resolved
@waiting-for-dev waiting-for-dev force-pushed the waiting-for-dev/on_failure_hook branch 2 times, most recently from 0a71b69 to 4845948 Compare June 20, 2024 14:00
Copy link
Member

@timriley timriley left a 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.
@waiting-for-dev waiting-for-dev force-pushed the waiting-for-dev/on_failure_hook branch from 4845948 to b84bd9b Compare June 21, 2024 14:48
@timriley
Copy link
Member

@waiting-for-dev Have answered the final outstanding question, this looks good to merge!

@waiting-for-dev waiting-for-dev merged commit f1aacf4 into main Jun 22, 2024
6 checks passed
@waiting-for-dev waiting-for-dev deleted the waiting-for-dev/on_failure_hook branch June 22, 2024 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants