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
end
Configuration
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
end
The 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
end
The 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 }
end
Middleware 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
end
Whatever 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 }
end
Middleware 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 handlerProc
that would be in charge of handling theexception
if noerror_handler
option 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)
end
NOTE: 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 }
end
But 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
end
Because 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
end
Notifiers
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 }
end
Honeybadger
To send notifications to honeybadger:
require 'honeybadger'
Faraday.new(url: 'http://foo.com') do |c|
c.use :circuit_breaker, notifiers: { honeybadger: "api_key" }
end
You'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 }
end
You'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' } }
end
You'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 }
end
You'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 }
end
You'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.