A Faraday Middleware to handle spotty web services.
Add this line to your application's Gemfile:
gem 'faraday_middleware-circuit_breaker'And then execute:
$ bundle
Or install it yourself as:
$ gem install faraday_middleware-circuit_breaker
Simply add the middleware:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker
endMiddleware will automatically attempt to recover after a certain amount of time. This timeout is customizable:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, timeout: 10
endThe default is 60 seconds. To disable automatic recovery, set the timeout to Float::INFINITY. To make automatic recovery
instantaneous, set the timeout to 0 seconds though it's not recommended.
Some services might be allowed to fail more or less frequently than others. You can configure this by setting a custom threshold:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, threshold: 5
endThe default is 3 times.
On a failure, middleware will render an empty 503 http response by default. You can customize the fallback response:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, fallback: ->(env, exception) { # do something }
endMiddleware will try to call the call method on fallback passing 2 arguments:
env-- the connection environement from faradayexception-- the exception raised that triggered the circuit breaker
You can pass a method to be eager called like this:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, fallback: method(:foo)
end
def foo(env, exception)
# do something
endWhatever you chose, your method should return a valid faraday response. For example, here is the default fallback implementation:
proc { Faraday::Response.new(status: 503, response_headers: {}) }In some situations, it might required to allow for particular error types to be exempt from tripping the circuit breaker
(like regular 403 or 401 HTTP responses, which aren't really out-of-the-ordinary conditions that should trip the circuit breaker).
The underlying stoplight gem supports custom error handling,
The error_handler option allows you to add your own customer error handler behavior:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, error_handler: ->(exception, handler) { # do something }
endMiddleware will try to call the call method on error_handler passing 2 arguments:
exception-- the exception raised that triggered the circuit breakerhandler-- the current error handlerProcthat would be in charge of handling theexceptionif noerror_handleroption was passed
You can pass a method to be eager called like this (with a handler that exempts ArgumentError from tripping the circuit):
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, error_handler: method(:foo)
end
def foo(exception, handler)
raise exception if exception.is_a?(ArgumentError)
handler.call(exception)
endNOTE: It is most always a good idea to call the original handler with the exception that was passed in at the end of your
handler. (By default, the error_handler will just be Stoplight::Default::ERROR_HANDLER)
By default, the circuit breaker will count failures by domain, but this logic can be tweak by passing a lambda to the cache_key_generator option.
The lambda will receive the URI that Faraday is trying to call, and whatever string it returns will be used as the key to count the errors,
and all URI with the same key will trip together.
The default behaviour is:
Faraday.new(url: 'http://foo.com/bar') do |c|
c.use :circuit_breaker, cache_key_generator: ->(url) { URI.join(url, '/').to_s }
endBut for instance if when http://foo.com/bar?id=1 trips you also want http://foo.com/bar?id=2 to be tripped but http://foo.com/foo to go through, then you could pass the following:
Faraday.new(url: 'http://foo.com/bar') do |c|
c.use :circuit_breaker, cache_key_generator: lambda do |url|
base_url = url.clone
base_url.fragment = base_url.query = nil
base_url.to_s
end
endBecause the key is a simple string, it doesn't have to be constructed from the URI directly, so the following is also valid:
Faraday.new(url: 'http://foo.com/bar') do |c|
c.use :circuit_breaker, cache_key_generator: lambda do |url|
if url.hostname == 'api.mydomain.com'
if url.path.start_with? "/users"
return "user_service"
elsif url.path.start_with? "/orders"
return "orders_service"
else
return "other_service"
end
end
URI.join(url, '/').to_s
end
endMiddleware send notifications to standard error by default. You can customize the receivers.
To send notifications to a logger:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { logger: Rails.logger }
endTo send notifications to honeybadger:
require 'honeybadger'
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { honeybadger: "api_key" }
endYou'll need to have Honeybadger gem installed.
To send notifications to slack:
require 'slack-notifier'
slack = Slack::Notifier.new('http://www.example.com/webhook-url')
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { slack: slack }
endYou'll need to have Slack gem installed.
To send notifications to hipchat:
require 'hipchat'
hip_chat = HipChat::Client.new('token')
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { hipchat: { client: hipchat, room: 'room' } }
endYou'll need to have HipChat gem installed.
To send notifications to bugsnag:
require 'bugsnag'
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { bugsnag: Bugsnag }
endYou'll need to have Bugsnag gem installed.
To send notifications to sentry:
require 'sentry-raven'
sentry_raven = Raven::Configuration.new
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { sentry: sentry_raven } # or { raven: sentry_raven }
endYou'll need to have Sentry gem installed.
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/faraday_middleware-circuit_breaker.
The gem is available as open source under the terms of the MIT License.