|
2 | 2 |
|
3 | 3 | [![Build Status][travis-image]][travis-url]
|
4 | 4 | [![Coverage Status][coveralls-image]][coveralls-url]
|
5 |
| -[![MIT License][license-image]][license-urlcoveralls-image]:https://coveralls.io/repos/github/wework/we-call-gem/badge.svg?branch=main |
| 5 | +[![MIT License][license-image]][license-url] |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +Requires metadata and offers client/server middleware to help debug HTTP calls, raise warnings for deprecations, supporting trace IDs, etc. |
| 10 | + |
| 11 | +It aims to arm API developers and users with tools to make their calls more robust, and enforces Good Ideas™ with sane defaults whenever possible. |
| 12 | + |
| 13 | +## Goals |
| 14 | + |
| 15 | +- Work just like Faraday out of the box |
| 16 | +- Remove some of the guesswork that comes with HTTP service orientated architecures |
| 17 | +- Provide sane defaults whenever possible, but ask for more information if required |
| 18 | +- Facilitate [HTTP Evolution](https://www.mnot.net/blog/2012/12/04/api-evolution.html) |
| 19 | + |
| 20 | +## Usage |
| 21 | + |
| 22 | + |
| 23 | +```ruby |
| 24 | +gem 'we-call' |
| 25 | +``` |
| 26 | + |
| 27 | +```ruby |
| 28 | +# config/initializers/we-call.rb |
| 29 | + |
| 30 | +We::Call.configure do |config| |
| 31 | + config.app_name = 'service-a' # default nil (Connection class falls back to APP_NAME or Rails name) |
| 32 | + config.app_env = 'staging' # default nil (Connection class back to RACK_ENV || RAILS_ENV) |
| 33 | + config.detect_deprecations = false # default true |
| 34 | +end |
| 35 | +``` |
| 36 | + |
| 37 | +As this is a Faraday wrapper, the only thing that will change from normal Faraday usage is initialization. |
| 38 | + |
| 39 | +```ruby |
| 40 | +connection = We::Call::Connection.new(host: 'https://some-service.example.com/', timeout: 2) |
| 41 | + |
| 42 | +# or with a Faraday connection block |
| 43 | +connection = We::Call::Connection.new(host: 'https://some-service.example.com/', timeout: 2) do |conn| |
| 44 | + conn.token_auth('abc123token') |
| 45 | + conn.headers['Foo'] = 'bar' |
| 46 | +end |
| 47 | +``` |
| 48 | + |
| 49 | +See more connection block options in the [Faraday documentation](https://github.com/lostisland/faraday). |
| 50 | + |
| 51 | +### Provide an App |
| 52 | + |
| 53 | +An application should provide its own name in the user agent when calling other services. This is important in case this app busts a local cache, causing it to stampeding herd other service(s). |
| 54 | + |
| 55 | +Other services need to know which server is causing the problem, so no connections are allowed through `We::Call` without an app being set. |
| 56 | + |
| 57 | +```ruby |
| 58 | +# Provided at config |
| 59 | +connection = We::Call.configure do |config| |
| 60 | + config.app_name = 'Service A' |
| 61 | +end |
| 62 | + |
| 63 | +# Provided at initialization |
| 64 | +connection = We::Call::Connection.new(host: 'https://service-b.example.com/', app: 'Service A', timeout: 2) |
| 65 | +``` |
| 66 | + |
| 67 | +_Ofc services could lie about this, so do not use App Name for any sort of security. For that you need to use tokens assigned to applications. This is essentially just forcing a user agent._ |
| 68 | + |
| 69 | +### Provide an Env |
| 70 | + |
| 71 | +```ruby |
| 72 | +# Provided at config |
| 73 | +connection = We::Call.configure do |config| |
| 74 | + config.app_env = 'staging' |
| 75 | +end |
| 76 | + |
| 77 | +# Provided at initialization |
| 78 | +connection = We::Call::Connection.new(host: 'https://service-b.example.com/', env: 'staging', timeout: 2) |
| 79 | +``` |
| 80 | + |
| 81 | +Not only is knowing the app name important, but knowing the env is necessary too. Sometimes people configure stuff wrong, and Service A (staging) will hit Service B (production) 😨. |
| 82 | + |
| 83 | +If you are using Rack or Rails, you should not need to do this, as it'll use RACK_ENV or RAILS_ENV by default. |
| 84 | + |
| 85 | +### Timeouts |
| 86 | + |
| 87 | +By default Faraday will let HTTP calls go on forever. In reality this is often 30 seconds for e.g: a Heroku app. Asking developers to make a choice about how long they're willing to wait on this call gives them a chance to consider an acceptable timeout. |
| 88 | + |
| 89 | +The lower this number can be the better, as it reduces time web threads spend waiting for calls that are unlikely to respond anyway. |
| 90 | + |
| 91 | +```ruby |
| 92 | +# Provided at initialization |
| 93 | +connection = We::Call::Connection.new(host: 'https://service-b.example.com/', timeout: 2) |
| 94 | +``` |
| 95 | + |
| 96 | +Timeouts can only be provided at initialization of a connection, as they should be different for each service. This is down to the sad reality that some internal services are more performant than others, and various third-parties will have different SLAs. |
| 97 | + |
| 98 | +As well as `timeout: num_seconds` which can set the entire open/read (essentially the total response time of the server), another optional argument exists for `open_timeout: numseconds`. This is how long We::Call should spend waiting for a vague sign of life from the server, which by default is 1. |
| 99 | + |
| 100 | + |
| 101 | +## Middleware |
| 102 | + |
| 103 | +### Client |
| 104 | + |
| 105 | +**Retry** |
| 106 | + |
| 107 | +Automatically enabled, the retry middleware will retry the request in case of network errors. By default, the middleware will retry up to 3 times, waiting 1 second between the retries. |
| 108 | + |
| 109 | +Disable the middleware: |
| 110 | + |
| 111 | +```ruby |
| 112 | +We::Call.configure do |config| |
| 113 | + config.retry = false |
| 114 | +end |
| 115 | +``` |
| 116 | + |
| 117 | +Adjust the middleware: |
| 118 | + |
| 119 | +```ruby |
| 120 | +We::Call.configure do |config| |
| 121 | + config.retry_options = { interval: 0.5 } |
| 122 | +end |
| 123 | +``` |
| 124 | + |
| 125 | +The gem smartly merges the options passed, so you can specify your own list of exceptions without being afraid to override the default ones: |
| 126 | + |
| 127 | +```ruby |
| 128 | +We::Call.configure do |config| |
| 129 | + config.retry_options = { exceptions: [Faraday::ResourceNotFound] } |
| 130 | +end |
| 131 | +``` |
| 132 | + |
| 133 | +Check [Faraday's Retry Docs](https://github.com/lostisland/faraday/blob/master/docs/middleware/request/retry.md) for a list of available options. |
| 134 | + |
| 135 | +**DetectDeprecations** |
| 136 | + |
| 137 | +Automatically enabled, the faraday-sunset middleware will watch for the [Sunset header](https://tools.ietf.org/html/draft-wilde-sunset-header-03) and send warning to `ActiveSupport::Deprecation` if enabled, or to whatever is in `ENV['rake.logger']`. |
| 138 | + |
| 139 | +[faraday-sunset]: https://github.com/wework/faraday-sunset |
| 140 | + |
| 141 | +### Server |
| 142 | + |
| 143 | +**LogUserAgent** |
| 144 | + |
| 145 | +_(Optional)_ Log the User Agent, which might just be browser information (merely kinda handy), or could be an app name, like the one `We::Call::Connection` asks you for. |
| 146 | + |
| 147 | +```ruby |
| 148 | +config.middleware.insert_after Rails::Rack::Logger, We::Call::Middleware::Server::LogUserAgent |
| 149 | +``` |
| 150 | + |
| 151 | +Easy! Check your logs for `user_agent=service-name; app_name=service-name;` The `app_name` will only show up if this was called by `We::Call::Connection` (as this is the only thing setting the `X-App-Name` header.) |
| 152 | + |
| 153 | +## Requirements |
| 154 | + |
| 155 | +- **Ruby:** v2.2 - v2.5 |
| 156 | +- **Faraday:** v0.10 - v0.15 |
| 157 | + |
| 158 | +_**Note:** Other versions of Faraday may work, but we can't test against all of them forever._ |
| 159 | + |
| 160 | +## TODO |
| 161 | + |
| 162 | +- [x] Split DetectDeprecations into standalone [faraday-sunset] gem |
| 163 | +- [ ] Work on sane defaults for retries and error raising |
| 164 | + |
| 165 | + |
| 166 | +## Testing |
| 167 | + |
| 168 | +To run tests and modify locally, you'll want to `bundle install` in this directory. |
| 169 | + |
| 170 | +``` |
| 171 | +bundle exec appraisal rspec |
| 172 | +``` |
| 173 | + |
| 174 | +## Development |
| 175 | + |
| 176 | +If you want to test this gem within an application, update your Gemfile to have something like this: `gem 'we-call', github: 'wework/we-call-gem', branch: 'BRANCHNAME'` and set your local config: `bundle config --local local.we-call path/to/we-call-gem` |
| 177 | + |
| 178 | +Simply revert the Gemfile change (updating the version as necessary!) and remove the config with `bundle config --delete local.we-call`. |
| 179 | + |
| 180 | +References: [Blog Post](https://rossta.net/blog/how-to-specify-local-ruby-gems-in-your-gemfile.html) and [Bundle Documentation](https://bundler.io/v1.2/git.html#local) |
| 181 | + |
| 182 | +## Contributing |
| 183 | + |
| 184 | +Bug reports and pull requests are welcome on GitHub at [wework/we-call](https://github.com/wework/we-call). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. |
| 185 | + |
| 186 | + |
| 187 | +[coveralls-image]:https://coveralls.io/repos/github/wework/we-call-gem/badge.svg?branch=master |
6 | 188 | [coveralls-url]:https://coveralls.io/github/wework/we-call-gem?branch=main
|
7 | 189 |
|
8 | 190 | [travis-url]:https://travis-ci.org/wework/we-call-gem
|
9 |
| -[travis-image]: https://travis-ci.org/wework/we-call-gem.svg?branch=master |
| 191 | +[travis-image]: https://travis-ci.org/wework/we-call-gem.svg?branch=main |
10 | 192 |
|
11 | 193 | [license-url]: LICENSE
|
12 | 194 | [license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square
|
0 commit comments