-
Notifications
You must be signed in to change notification settings - Fork 21
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
Error handlers #17
Conversation
Looks like I also have some tests to fix for other versions. I’ll take a look at that too. |
Hmm, I'm going to have to dig deeper into that failure. I'm not sure I understand why it's happening yet. |
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.
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) |
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 probably could allow a callable object (like lambda) here
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.
Yep, good idea! Added.
end | ||
end | ||
|
||
def self.build_by_name(name, options) |
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.
Probably the time has come to make an util method and reuse it here and in the StoreAdapters
module 🙂
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 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.
a9d055e
to
82a9a63
Compare
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.
b00198b
to
931307f
Compare
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! |
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 |
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.
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) |
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 looks like a perfect case for a String refinement. What do you think?
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.
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.
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.
Sounds reasonable, it does not sound like an improvement
Thanks for the amazing work, |
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.