hutch 0.19.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +11 -0
  4. data/CHANGELOG.md +438 -0
  5. data/Gemfile +22 -0
  6. data/Guardfile +5 -0
  7. data/LICENSE +22 -0
  8. data/README.md +317 -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 +30 -0
  15. data/lib/hutch.rb +62 -0
  16. data/lib/hutch/adapter.rb +11 -0
  17. data/lib/hutch/adapters/bunny.rb +33 -0
  18. data/lib/hutch/adapters/march_hare.rb +37 -0
  19. data/lib/hutch/broker.rb +374 -0
  20. data/lib/hutch/cli.rb +205 -0
  21. data/lib/hutch/config.rb +125 -0
  22. data/lib/hutch/consumer.rb +75 -0
  23. data/lib/hutch/error_handlers.rb +8 -0
  24. data/lib/hutch/error_handlers/airbrake.rb +26 -0
  25. data/lib/hutch/error_handlers/honeybadger.rb +28 -0
  26. data/lib/hutch/error_handlers/logger.rb +16 -0
  27. data/lib/hutch/error_handlers/sentry.rb +23 -0
  28. data/lib/hutch/exceptions.rb +7 -0
  29. data/lib/hutch/logging.rb +32 -0
  30. data/lib/hutch/message.rb +31 -0
  31. data/lib/hutch/serializers/identity.rb +19 -0
  32. data/lib/hutch/serializers/json.rb +22 -0
  33. data/lib/hutch/tracers.rb +6 -0
  34. data/lib/hutch/tracers/newrelic.rb +19 -0
  35. data/lib/hutch/tracers/null_tracer.rb +15 -0
  36. data/lib/hutch/version.rb +4 -0
  37. data/lib/hutch/worker.rb +143 -0
  38. data/spec/hutch/broker_spec.rb +377 -0
  39. data/spec/hutch/cli_spec.rb +80 -0
  40. data/spec/hutch/config_spec.rb +126 -0
  41. data/spec/hutch/consumer_spec.rb +130 -0
  42. data/spec/hutch/error_handlers/airbrake_spec.rb +34 -0
  43. data/spec/hutch/error_handlers/honeybadger_spec.rb +36 -0
  44. data/spec/hutch/error_handlers/logger_spec.rb +15 -0
  45. data/spec/hutch/error_handlers/sentry_spec.rb +20 -0
  46. data/spec/hutch/logger_spec.rb +28 -0
  47. data/spec/hutch/message_spec.rb +38 -0
  48. data/spec/hutch/serializers/json_spec.rb +17 -0
  49. data/spec/hutch/worker_spec.rb +99 -0
  50. data/spec/hutch_spec.rb +87 -0
  51. data/spec/spec_helper.rb +40 -0
  52. metadata +194 -0
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "rake"
7
+ gem "guard", "~> 0.8.8"
8
+ gem "guard-rspec", "~> 0.5.4"
9
+ end
10
+
11
+ group :development, :test do
12
+ gem "sentry-raven"
13
+ gem "honeybadger"
14
+ gem "coveralls", require: false
15
+ gem "newrelic_rpm"
16
+ gem "airbrake"
17
+ end
18
+
19
+ group :development, :darwin do
20
+ gem "rb-fsevent", "~> 0.9"
21
+ gem "growl", "~> 1.0.3"
22
+ end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2, :cli => '--color --format doc' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-2014 GoCardless
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,317 @@
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
+ * `mq_tls_ca_certificates`: array of paths to CA keys (if not specified to Hutch, will default to Bunny defaults which are system-dependent)
293
+ * `mq_verify_peer`: should SSL certificate be verified? (default: `true`)
294
+ * `require_paths`: array of paths to require
295
+ * `autoload_rails`: should Hutch command line runner try to automatically load Rails environment files?
296
+ * `daemonise`: should Hutch runner process daemonise?
297
+ * `pidfile`: path to PID file the runner should use
298
+ * `channel_prefetch`: basic.qos prefetch value to use (default: `0`, no limit). See Bunny and RabbitMQ documentation.
299
+ * `publisher_confirms`: enables publisher confirms. Leaves it up to the app how they are
300
+ tracked (e.g. using `Hutch::Broker#confirm_select` callback or `Hutch::Broker#wait_for_confirms`)
301
+ * `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**.
302
+ * `log_level`: log level used by the standard Ruby logger (default: `Logger::INFO`)
303
+ * `mq_exchange`: exchange to use for publishing (default: `hutch`)
304
+ * `heartbeat`: [RabbitMQ heartbeat timeout](http://rabbitmq.com/heartbeats.html) (default: `30`)
305
+ * `connection_timeout`: Bunny's socket open timeout (default: `11`)
306
+ * `read_timeout`: Bunny's socket read timeout (default: `11`)
307
+ * `write_timemout`: Bunny's socket write timeout (default: `11`)
308
+ * `tracer`: tracer to use to track message processing
309
+
310
+
311
+ ## Supported RabbitMQ Versions
312
+
313
+ Hutch requires RabbitMQ 3.3 or later.
314
+
315
+ ---
316
+
317
+ 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,30 @@
1
+ require File.expand_path('../lib/hutch/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ if defined?(JRUBY_VERSION)
5
+ gem.platform = 'java'
6
+ gem.add_runtime_dependency 'march_hare', '>= 2.11.0'
7
+ else
8
+ gem.platform = Gem::Platform::RUBY
9
+ gem.add_runtime_dependency 'bunny', '>= 2.2.0'
10
+ end
11
+ gem.add_runtime_dependency 'carrot-top', '~> 0.0.7'
12
+ gem.add_runtime_dependency 'multi_json', '~> 1.11.2'
13
+ gem.add_runtime_dependency 'activesupport', '>= 3.0'
14
+ gem.add_development_dependency 'rspec', '~> 3.0'
15
+ gem.add_development_dependency 'simplecov', '~> 0.7.1'
16
+
17
+ gem.name = 'hutch'
18
+ gem.summary = 'Easy inter-service communication using RabbitMQ.'
19
+ gem.description = 'Hutch is a Ruby library for enabling asynchronous ' +
20
+ 'inter-service communication using RabbitMQ.'
21
+ gem.version = Hutch::VERSION.dup
22
+ gem.authors = ['Harry Marr']
23
+ gem.email = ['developers@gocardless.com']
24
+ gem.homepage = 'https://github.com/gocardless/hutch'
25
+ gem.require_paths = ['lib']
26
+ gem.license = 'MIT'
27
+ gem.executables = ['hutch']
28
+ gem.files = `git ls-files`.split("\n")
29
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
30
+ end