FaradayMiddleware::CircuitBreaker
A Faraday Middleware to handle spotty web services.
Installation
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
Usage
Simply add the middleware:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker
endConfiguration
Timeout
Middleware 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.
Threshold
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.
Custom fallback
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 faraday -
exception-- 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: {}) }Custom error handling
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 breaker -
handler-- 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)
Custom Stoplight key
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
endNotifiers
Middleware send notifications to standard error by default. You can customize the receivers.
Logger
To send notifications to a logger:
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { logger: Rails.logger }
endHoneybadger
To 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.
Slack
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.
HipChat
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.
Bugsnag
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.
Sentry
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.
Development
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.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/faraday_middleware-circuit_breaker.
License
The gem is available as open source under the terms of the MIT License.