dkastner-hutch 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +397 -0
  5. data/Gemfile +22 -0
  6. data/Guardfile +5 -0
  7. data/LICENSE +22 -0
  8. data/README.md +315 -0
  9. data/Rakefile +14 -0
  10. data/bin/hutch +8 -0
  11. data/circle.yml +3 -0
  12. data/examples/consumer.rb +13 -0
  13. data/examples/producer.rb +10 -0
  14. data/hutch.gemspec +24 -0
  15. data/lib/hutch/broker.rb +356 -0
  16. data/lib/hutch/cli.rb +205 -0
  17. data/lib/hutch/config.rb +121 -0
  18. data/lib/hutch/consumer.rb +66 -0
  19. data/lib/hutch/error_handlers/airbrake.rb +26 -0
  20. data/lib/hutch/error_handlers/honeybadger.rb +28 -0
  21. data/lib/hutch/error_handlers/logger.rb +16 -0
  22. data/lib/hutch/error_handlers/sentry.rb +23 -0
  23. data/lib/hutch/error_handlers.rb +8 -0
  24. data/lib/hutch/exceptions.rb +7 -0
  25. data/lib/hutch/logging.rb +32 -0
  26. data/lib/hutch/message.rb +33 -0
  27. data/lib/hutch/tracers/newrelic.rb +19 -0
  28. data/lib/hutch/tracers/null_tracer.rb +15 -0
  29. data/lib/hutch/tracers.rb +6 -0
  30. data/lib/hutch/version.rb +4 -0
  31. data/lib/hutch/worker.rb +110 -0
  32. data/lib/hutch.rb +60 -0
  33. data/spec/hutch/broker_spec.rb +325 -0
  34. data/spec/hutch/cli_spec.rb +80 -0
  35. data/spec/hutch/config_spec.rb +126 -0
  36. data/spec/hutch/consumer_spec.rb +130 -0
  37. data/spec/hutch/error_handlers/airbrake_spec.rb +34 -0
  38. data/spec/hutch/error_handlers/honeybadger_spec.rb +36 -0
  39. data/spec/hutch/error_handlers/logger_spec.rb +15 -0
  40. data/spec/hutch/error_handlers/sentry_spec.rb +20 -0
  41. data/spec/hutch/logger_spec.rb +28 -0
  42. data/spec/hutch/message_spec.rb +38 -0
  43. data/spec/hutch/worker_spec.rb +98 -0
  44. data/spec/hutch_spec.rb +87 -0
  45. data/spec/spec_helper.rb +35 -0
  46. metadata +187 -0
data/README.md ADDED
@@ -0,0 +1,315 @@
1
+ ![](http://cl.ly/image/3h0q3F3G142K/hutch.png)
2
+
3
+ Hutch is a Ruby library for enabling asynchronous inter-service communication
4
+ in a service-oriented architecture, using RabbitMQ.
5
+
6
+ [![Gem Version](https://badge.fury.io/rb/hutch.png)](http://badge.fury.io/rb/hutch)
7
+ [![Build Status](https://travis-ci.org/gocardless/hutch.png?branch=master)](https://travis-ci.org/gocardless/hutch)
8
+ [![Dependency Status](https://gemnasium.com/gocardless/hutch.png)](https://gemnasium.com/gocardless/hutch)
9
+ [![Code Climate](https://codeclimate.com/github/gocardless/hutch.png)](https://codeclimate.com/github/gocardless/hutch)
10
+
11
+ To install with RubyGems:
12
+
13
+ ```
14
+ $ gem install hutch
15
+ ```
16
+
17
+ ## Project Maturity
18
+
19
+ Hutch is a moderately mature project (started in early 2013)
20
+ that was extracted from production systems.
21
+
22
+
23
+ ## Overview
24
+
25
+ Hutch is a conventions-based framework for writing services that communicate
26
+ over RabbitMQ. Hutch is opinionated: it uses topic exchanges for message
27
+ distribution and makes some assumptions about how consumers and publishers
28
+ should work.
29
+
30
+ With Hutch, consumers are stored in separate files and include the `Hutch::Consumer` module.
31
+ They are then loaded by a command line runner which connects to RabbitMQ, sets up queues and bindings,
32
+ and so on. Publishers connect to RabbitMQ via `Hutch.connect` and publish using `Hutch.publish`.
33
+
34
+ Hutch uses [Bunny](http://rubybunny.info) under the hood.
35
+
36
+
37
+ ## Defining Consumers
38
+
39
+ Consumers receive messages from a RabbitMQ queue. That queue may be bound to
40
+ one or more topics (represented by routing keys).
41
+
42
+ To create a consumer, include the `Hutch::Consumer` module in a class that
43
+ defines a `#process` method. `#process` should take a single argument, which
44
+ will be a `Message` object. The `Message` object encapsulates the message data,
45
+ along with any associated metadata. To access properties of the message, use
46
+ Hash-style indexing syntax:
47
+
48
+ ```ruby
49
+ message[:id] # => "02ABCXYZ"
50
+ ```
51
+
52
+ To subscribe to a topic, pass a routing key to `consume` in the class
53
+ definition. To bind to multiple routing keys, simply pass extra routing keys
54
+ in as additional arguments. Refer to the [RabbitMQ docs on topic exchanges
55
+ ][topic-docs] for more information about how to use routing keys. Here's an
56
+ example consumer:
57
+
58
+ ```ruby
59
+ class FailedPaymentConsumer
60
+ include Hutch::Consumer
61
+ consume 'gc.ps.payment.failed'
62
+
63
+ def process(message)
64
+ mark_payment_as_failed(message[:id])
65
+ end
66
+ end
67
+ ```
68
+
69
+ By default, the queue name will be named using the consumer class. You can set
70
+ the queue name explicitly by using the `queue_name` method:
71
+
72
+ ```ruby
73
+ class FailedPaymentConsumer
74
+ include Hutch::Consumer
75
+ consume 'gc.ps.payment.failed'
76
+ queue_name 'failed_payments'
77
+
78
+ def process(message)
79
+ mark_payment_as_failed(message[:id])
80
+ end
81
+ end
82
+ ```
83
+
84
+ You can also set custom arguments per consumer. This example declares a consumer with
85
+ a maximum length of 10 messages:
86
+
87
+ ```ruby
88
+ class FailedPaymentConsumer
89
+ include Hutch::Consumer
90
+ consume 'gc.ps.payment.failed'
91
+ arguments 'x-max-length' => 10
92
+ end
93
+ ```
94
+
95
+ Custom queue arguments can be found on [this page](https://www.rabbitmq.com/extensions.html).
96
+
97
+ Consumers can write to Hutch's log by calling the logger method. The logger method returns
98
+ a [Logger object](http://ruby-doc.org/stdlib-2.1.2/libdoc/logger/rdoc/Logger.html).
99
+
100
+ ```ruby
101
+ class FailedPaymentConsumer
102
+ include Hutch::Consumer
103
+ consume 'gc.ps.payment.failed'
104
+
105
+ def process(message)
106
+ logger.info "Marking payment #{message[:id]} as failed"
107
+ mark_payment_as_failed(message[:id])
108
+ end
109
+ end
110
+ ```
111
+
112
+ If you are using Hutch with Rails and want to make Hutch log to the Rails
113
+ logger rather than `stdout`, add this to `config/initializers/hutch.rb`
114
+
115
+ ```ruby
116
+ Hutch::Logging.logger = Rails.logger
117
+ ```
118
+
119
+ A logger can be set for the client by adding this config before calling `Hutch.connect`
120
+
121
+ ```ruby
122
+ client_logger = Logger.new("/path/to/bunny.log")
123
+ Hutch::Config.set(:client_logger, client_logger)
124
+ ```
125
+
126
+ See this [RabbitMQ tutorial on topic exchanges](http://www.rabbitmq.com/tutorials/tutorial-five-ruby.html)
127
+ to learn more.
128
+
129
+ ### Message Processing Tracers
130
+
131
+ Tracers allow you to track message processing.
132
+
133
+ #### NewRelic
134
+ ```ruby
135
+ Hutch::Config.set(:tracer, Hutch::Tracers::NewRelic)
136
+ ```
137
+ This will enable NewRelic custom instrumentation. Batteries included! Screenshoots available [here](https://monosnap.com/list/557020a000779174f23467e3).
138
+
139
+ ## Running Hutch
140
+
141
+ After installing the Hutch gem, you should be able to start it by simply
142
+ running `hutch` on the command line. `hutch` takes a number of options:
143
+
144
+ ```console
145
+ $ hutch -h
146
+ usage: hutch [options]
147
+ --mq-host HOST Set the RabbitMQ host
148
+ --mq-port PORT Set the RabbitMQ port
149
+ -t, --[no-]mq-tls Use TLS for the AMQP connection
150
+ --mq-tls-cert FILE Certificate for TLS client verification
151
+ --mq-tls-key FILE Private key for TLS client verification
152
+ --mq-exchange EXCHANGE Set the RabbitMQ exchange
153
+ --mq-vhost VHOST Set the RabbitMQ vhost
154
+ --mq-username USERNAME Set the RabbitMQ username
155
+ --mq-password PASSWORD Set the RabbitMQ password
156
+ --mq-api-host HOST Set the RabbitMQ API host
157
+ --mq-api-port PORT Set the RabbitMQ API port
158
+ -s, --[no-]mq-api-ssl Use SSL for the RabbitMQ API
159
+ --config FILE Load Hutch configuration from a file
160
+ --require PATH Require a Rails app or path
161
+ --[no-]autoload-rails Require the current rails app directory
162
+ -q, --quiet Quiet logging
163
+ -v, --verbose Verbose logging
164
+ --version Print the version and exit
165
+ -h, --help Show this message and exit
166
+ ```
167
+
168
+ The first three are for configuring which RabbitMQ instance to connect to.
169
+ `--require` is covered in the next section. Configurations can also be
170
+ specified in a YAML file for convenience by passing the file location
171
+ to the --config option. The file should look like:
172
+
173
+ ```yaml
174
+ mq_username: peter
175
+ mq_password: rabbit
176
+ mq_host: broker.yourhost.com
177
+ ```
178
+
179
+ Passing a setting as a command-line option will overwrite what's specified
180
+ in the config file, allowing for easy customization.
181
+
182
+ ### Loading Consumers
183
+
184
+ Using Hutch with a Rails app is simple. Either start Hutch in the working
185
+ directory of a Rails app, or pass the path to a Rails app in with the
186
+ `--require` option. Consumers defined in Rails apps should be placed with in
187
+ the `app/consumers/` directory, to allow them to be auto-loaded when Rails
188
+ boots.
189
+
190
+ To require files that define consumers manually, simply pass each file as an
191
+ option to `--require`. Hutch will automatically detect whether you've provided
192
+ a Rails app or a standard file, and take the appropriate behaviour:
193
+
194
+ ```bash
195
+ $ hutch --require path/to/rails-app # loads a rails app
196
+ $ hutch --require path/to/file.rb # loads a ruby file
197
+ ```
198
+
199
+ ### Stopping Hutch
200
+
201
+ Hutch supports graceful stops. That means that if done correctly, Hutch will wait for your consumer to finish processing before exiting.
202
+
203
+ To gracefully stop your workers, you may send the following signals to your Hutch processes: `INT`, `TERM`, or `QUIT`.
204
+
205
+ ```bash
206
+ kill -SIGINT 123 # or kill -2 123
207
+ kill -SIGTERM 456 # or kill -15 456
208
+ kill -SIGQUIT 789 # or kill -3 789
209
+ ```
210
+
211
+ ![](http://g.recordit.co/wyCdzG9Kh3.gif)
212
+
213
+ ## Producers
214
+
215
+ Hutch includes a `publish` method for sending messages to Hutch consumers. When
216
+ possible, this should be used, rather than directly interfacing with RabbitMQ
217
+ libraries.
218
+
219
+ ```ruby
220
+ Hutch.connect
221
+ Hutch.publish('routing.key', subject: 'payment', action: 'received')
222
+ ```
223
+
224
+ ### Producer Configuration
225
+
226
+ Producers are not run with the 'hutch' command. You can specify configuration
227
+ options as follows:
228
+
229
+ ```ruby
230
+ Hutch::Config.set(:mq_exchange, 'name')
231
+ ```
232
+
233
+ ### Publisher Confirms
234
+
235
+ For maximum message reliability when producing messages, you can force Hutch to use
236
+ [Publisher Confirms](https://www.rabbitmq.com/confirms.html) and wait for a confirmation
237
+ after every message published. This is the safest possible option for publishers
238
+ but also results in a **significant throughput drop**.
239
+
240
+ ```ruby
241
+ Hutch::Config.set(:force_publisher_confirms, true)
242
+ ```
243
+
244
+ ### Writing Well-Behaved Publishers
245
+
246
+ You may need to send messages to Hutch from languages other than Ruby. This
247
+ prevents the use of `Hutch.publish`, requiring custom publication code to be
248
+ written. There are a few things to keep in mind when writing producers that
249
+ send messages to Hutch.
250
+
251
+ - Make sure that the producer exchange name matches the exchange name that
252
+ Hutch is using.
253
+ - Hutch works with topic exchanges, check the producer is also using topic
254
+ exchanges.
255
+ - Use message routing keys that match those used in your Hutch consumers.
256
+ - Be sure your exchanges are marked as durable. In the Ruby AMQP gem, this is
257
+ done by passing `durable: true` to the exchange creation method.
258
+ - Publish messages as persistent.
259
+ - Using publisher confirms is highly recommended.
260
+
261
+ Here's an example of a well-behaved publisher, minus publisher confirms:
262
+
263
+ ```ruby
264
+ AMQP.connect(host: config[:host]) do |connection|
265
+ channel = AMQP::Channel.new(connection)
266
+ exchange = channel.topic(config[:exchange], durable: true)
267
+
268
+ message = JSON.dump({ subject: 'Test', id: 'abc' })
269
+ exchange.publish(message, routing_key: 'test', persistent: true)
270
+ end
271
+ ```
272
+
273
+ If using publisher confirms with amqp gem, see [this issue][pc-issue]
274
+ and [this gist][pc-gist] for more info.
275
+
276
+ ## Configuration Reference
277
+
278
+ ### Config File
279
+
280
+ It is recommended to use a separate config file, unless you use URIs for connection (see below).
281
+
282
+ Known configuration parameters are:
283
+
284
+ * `mq_host`: RabbitMQ hostname (default: `localhost`)
285
+ * `mq_port`: RabbitMQ port (default: `5672`)
286
+ * `mq_vhost`: vhost to use (default: `/`)
287
+ * `mq_username`: username to use (default: `guest`, only can connect from localhost as of RabbitMQ 3.3.0)
288
+ * `mq_password`: password to use (default: `guest`)
289
+ * `mq_tls`: should TLS be used? (default: `false`)
290
+ * `mq_tls_cert`: path to client TLS certificate (public key)
291
+ * `mq_tls_key`: path to client TLS private key
292
+ * `require_paths`: array of paths to require
293
+ * `autoload_rails`: should Hutch command line runner try to automatically load Rails environment files?
294
+ * `daemonise`: should Hutch runner process daemonise?
295
+ * `pidfile`: path to PID file the runner should use
296
+ * `channel_prefetch`: basic.qos prefetch value to use (default: `0`, no limit). See Bunny and RabbitMQ documentation.
297
+ * `publisher_confirms`: enables publisher confirms. Leaves it up to the app how they are
298
+ tracked (e.g. using `Hutch::Broker#confirm_select` callback or `Hutch::Broker#wait_for_confirms`)
299
+ * `force_publisher_confirms`: enables publisher confirms, forces `Hutch::Broker#wait_for_confirms` for every publish. **This is the safest option which also offers the lowest throughput**.
300
+ * `log_level`: log level used by the standard Ruby logger (default: `Logger::INFO`)
301
+ * `mq_exchange`: exchange to use for publishing (default: `hutch`)
302
+ * `heartbeat`: [RabbitMQ heartbeat timeout](http://rabbitmq.com/heartbeats.html) (default: `30`)
303
+ * `connection_timeout`: Bunny's socket open timeout (default: `11`)
304
+ * `read_timeout`: Bunny's socket read timeout (default: `11`)
305
+ * `write_timemout`: Bunny's socket write timeout (default: `11`)
306
+ * `tracer`: tracer to use to track message processing
307
+
308
+
309
+ ## Supported RabbitMQ Versions
310
+
311
+ Hutch requires RabbitMQ 3.3 or later.
312
+
313
+ ---
314
+
315
+ GoCardless ♥ open source. If you do too, come [join us](https://gocardless.com/jobs/backend_developer).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc "Run an IRB session with Hutch pre-loaded"
4
+ task :console do
5
+ exec "irb -I lib -r hutch"
6
+ end
7
+
8
+ desc "Run the test suite"
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.pattern = FileList['spec/**/*_spec.rb']
11
+ t.rspec_opts = %w(--color --format doc)
12
+ end
13
+
14
+ task :default => :spec
data/bin/hutch ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/hutch'
4
+ require_relative '../lib/hutch/cli'
5
+
6
+ cli = Hutch::CLI.new
7
+ cli.run(ARGV)
8
+
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ machine:
2
+ services:
3
+ - rabbitmq-server
@@ -0,0 +1,13 @@
1
+ require 'hutch'
2
+
3
+ class TestConsumer
4
+ include Hutch::Consumer
5
+ consume 'hutch.test'
6
+
7
+ def process(message)
8
+ puts "TestConsumer got a message: #{message}"
9
+ puts "Processing..."
10
+ sleep(1)
11
+ puts "Done"
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ require 'hutch'
2
+
3
+ Hutch.connect
4
+ loop do
5
+ print "Press return to send test message..."
6
+ gets
7
+ Hutch.publish('hutch.test', subject: 'test message')
8
+ puts "Send message with routing key 'hutch.test'"
9
+ end
10
+
data/hutch.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../lib/hutch/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.add_runtime_dependency 'bunny', '>= 1.7.0'
5
+ gem.add_runtime_dependency 'carrot-top', '~> 0.0.7'
6
+ gem.add_runtime_dependency 'multi_json', '~> 1.5'
7
+ gem.add_runtime_dependency 'activesupport', '>= 3.0'
8
+ gem.add_development_dependency 'rspec', '~> 3.0'
9
+ gem.add_development_dependency 'simplecov', '~> 0.7.1'
10
+
11
+ gem.name = 'hutch'
12
+ gem.summary = 'Easy inter-service communication using RabbitMQ.'
13
+ gem.description = 'Hutch is a Ruby library for enabling asynchronous ' +
14
+ 'inter-service communication using RabbitMQ.'
15
+ gem.version = Hutch::VERSION.dup
16
+ gem.authors = ['Harry Marr']
17
+ gem.email = ['developers@gocardless.com']
18
+ gem.homepage = 'https://github.com/gocardless/hutch'
19
+ gem.require_paths = ['lib']
20
+ gem.license = 'MIT'
21
+ gem.executables = ['hutch']
22
+ gem.files = `git ls-files`.split("\n")
23
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
24
+ end