Skip to content

Error handlers #17

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

Merged
merged 6 commits into from
Feb 11, 2020

Conversation

bmorton
Copy link
Contributor

@bmorton bmorton commented Jan 31, 2020

Accidentally opened this before it was ready — I’ve updated the description below:

This PR adds error handlers similar to discussed in #16. I started implementing this at the RedisStoreAdapter level, but this seemed better because it can be used more broadly and doesn’t require implementation at each of the stores.

I tried following other patterns in the repo, but I’m happy to change things that seem overly complex. I’m undecided if I want to include something into the repo for handling failures more gracefully because there doesn’t seem to be a good way to hook it into the implementing application's instrumentation framework. Having an error handler that just silently swallows these things seemed like it would promote a bad practice. Instead, I think I will implement the error handler in my application and use this to integrate it. Thoughts?

I still need to add some documentation and update the change log if you’re happy with this approach.

@bmorton bmorton changed the title Error handler callbacks Error handlers Jan 31, 2020
@bmorton
Copy link
Contributor Author

bmorton commented Jan 31, 2020

Looks like I also have some tests to fix for other versions. I’ll take a look at that too.

@bmorton
Copy link
Contributor Author

bmorton commented Jan 31, 2020

Hmm, I'm going to have to dig deeper into that failure. I'm not sure I understand why it's happening yet.

Copy link
Owner

@DmitryTsepelev DmitryTsepelev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far so good! Added some comments, master is ready for rebasing

# Contains factory methods for error handlers
module ErrorHandlers
def self.build(handler, options = nil)
if handler.is_a?(ErrorHandlers::BaseErrorHandler)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably could allow a callable object (like lambda) here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, good idea! Added.

end
end

def self.build_by_name(name, options)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the time has come to make an util method and reuse it here and in the StoreAdapters module 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a swing at this. There's not too much to abstract away, but there's less duplication now. Let me know what you think or if I missed the point.

@bmorton bmorton force-pushed the error-handler-callbacks branch from a9d055e to 82a9a63 Compare February 6, 2020 21:42
Brian Morton added 3 commits February 6, 2020 15:15
In 1.10 and later, plugin initialization happens at schema definition time.  In
prior versions, the plugin initialization happens lazily.  Since we're swapping
storage adapters, we need this initialization to happen early.
@bmorton bmorton force-pushed the error-handler-callbacks branch from b00198b to 931307f Compare February 6, 2020 23:15
@bmorton
Copy link
Contributor Author

bmorton commented Feb 7, 2020

Tests are passing now! I'm gonna get this wired up in my app and prove it out. If there are no other concerns with the approach, I'll write up docs and update the changelog. Thanks!

@bmorton
Copy link
Contributor Author

bmorton commented Feb 9, 2020

Tested this in our app and it seems to work as intended. Mainly, I want to ensure that a network partition or latency between the app and Redis doesn't cause complete failure but instead degrades into a "QueryNotPersisted" failure followed by the client sending the query. It's definitely a degraded state, but better than complete failure I think.

Here's the error handler I'm using to accomplish this. It seems like this is a "bring your own" error handler, but it could go into this project if you think it'd be useful for others. I think an example would probably be good enough though.

class GracefulRedisErrorHandler < GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler
  ERROR_DETAILS = "Redis failed while trying to retrieve or save a persisted query"

  def initialize(logger)
    @logger = logger
  end

  def call(error)
    case error
    when Redis::BaseError
      # Treat Redis errors as a cache miss, but ensure we still log the error for visibility
      logger.graphql_operation_error(message: ERROR_DETAILS, error: error)
    else
      raise error
    end

    # Return nothing to ensure handled errors are treated as cache misses
    return # rubocop:disable Style/RedundantReturn
  end

  private

  def logger
    if @logger.is_a?(Proc)
      @logger.call
    else
      @logger
    end
  end
end

Copy link
Owner

@DmitryTsepelev DmitryTsepelev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome! Please take a look at my proposal on camelize, and I'll be happy to merge it in as soon as you're done with the changelog and docs ✨

module PersistedQueries
# Contains factory methods for error handlers
module BuilderHelpers
def self.camelize(name)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a perfect case for a String refinement. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm not a big fan of refinements. In this case, it would need to be on the Symbol class, so I'd expect camelize to return a symbol, but it needs to be a string. I'm happy to make the change if you think it'd improve the project, but I'm afraid it makes things less clear and adds too much magic here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable, it does not sound like an improvement

@DmitryTsepelev DmitryTsepelev merged commit 336affa into DmitryTsepelev:master Feb 11, 2020
@DmitryTsepelev
Copy link
Owner

Thanks for the amazing work, 0.2.0 is on its way! ✨

@bmorton bmorton mentioned this pull request Feb 12, 2020
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