rabbitmq-actors 2.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +101 -0
- data/.rspec +5 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/README.md +602 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rabbitmq/actors.rb +13 -0
- data/lib/rabbitmq/actors/base/agent.rb +108 -0
- data/lib/rabbitmq/actors/base/consumer.rb +105 -0
- data/lib/rabbitmq/actors/base/producer.rb +83 -0
- data/lib/rabbitmq/actors/patterns.rb +10 -0
- data/lib/rabbitmq/actors/patterns/headers/headers_consumer.rb +106 -0
- data/lib/rabbitmq/actors/patterns/headers/headers_producer.rb +64 -0
- data/lib/rabbitmq/actors/patterns/master_workers/master_producer.rb +61 -0
- data/lib/rabbitmq/actors/patterns/master_workers/worker.rb +63 -0
- data/lib/rabbitmq/actors/patterns/publish_subscribe/publisher.rb +72 -0
- data/lib/rabbitmq/actors/patterns/publish_subscribe/subscriber.rb +70 -0
- data/lib/rabbitmq/actors/patterns/routing/routing_consumer.rb +99 -0
- data/lib/rabbitmq/actors/patterns/routing/routing_producer.rb +75 -0
- data/lib/rabbitmq/actors/patterns/topics/topic_consumer.rb +105 -0
- data/lib/rabbitmq/actors/patterns/topics/topic_producer.rb +64 -0
- data/lib/rabbitmq/actors/testing.rb +8 -0
- data/lib/rabbitmq/actors/testing/rspec.rb +8 -0
- data/lib/rabbitmq/actors/testing/rspec/stub.rb +52 -0
- data/lib/rabbitmq/actors/version.rb +5 -0
- data/lib/rabbitmq/server.rb +18 -0
- data/rabbitmq-actors.gemspec +32 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cffb9d414d6249d8d1da35687662d07d67764a8b
|
4
|
+
data.tar.gz: bcf47adfc06a9f41585c265dc3e1e99365ade4c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e206d7af8c02776499fb664234588fb0829afc9b1a1b08ce7c8319c8aff2a77f893978cc7ae97b30ac5a86eaf9bde87bed834df5d96598ee8f31a39593b94bf
|
7
|
+
data.tar.gz: 1b14cf154fd035b3c463af0bafa702b495b0b81c0f420d0153fd633ae8112e451e6a20bd424446fa11f2ee86ab8de4d4b45e6d73e03e0496845f4a8495c1eaee
|
data/.gitignore
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Gems
|
11
|
+
/Gemfile.lock
|
12
|
+
|
13
|
+
# Ignore the default SQLite database.
|
14
|
+
/db/*.sqlite3
|
15
|
+
/db/*.sqlite3-journal
|
16
|
+
|
17
|
+
# Ignore gem building folder: pkg
|
18
|
+
pkg
|
19
|
+
|
20
|
+
# Ignore log and tmp files
|
21
|
+
log/*
|
22
|
+
*.log
|
23
|
+
tmp
|
24
|
+
tmp/**/*
|
25
|
+
|
26
|
+
# Documentation
|
27
|
+
/doc/
|
28
|
+
doc/api
|
29
|
+
doc/app
|
30
|
+
.yardoc
|
31
|
+
/_yardoc/
|
32
|
+
.yardopts
|
33
|
+
|
34
|
+
# Public Uploads
|
35
|
+
public/system/*
|
36
|
+
public/themes/*
|
37
|
+
|
38
|
+
# Public Cache
|
39
|
+
public/javascripts/cache
|
40
|
+
public/stylesheets/cache
|
41
|
+
|
42
|
+
# Vendor Cache
|
43
|
+
vendor/cache
|
44
|
+
|
45
|
+
# Acts as Indexed
|
46
|
+
index/**/*
|
47
|
+
|
48
|
+
# Refinery Specific
|
49
|
+
*.tmproj
|
50
|
+
*.autobackupbyrefinery.*
|
51
|
+
refinerycms-*.gem
|
52
|
+
.autotest
|
53
|
+
|
54
|
+
# Mac
|
55
|
+
.DS_Store
|
56
|
+
|
57
|
+
# Windows
|
58
|
+
Thumbs.db
|
59
|
+
|
60
|
+
# NetBeans
|
61
|
+
nbproject
|
62
|
+
|
63
|
+
# Eclipse
|
64
|
+
.project
|
65
|
+
|
66
|
+
# Redcar
|
67
|
+
.redcar
|
68
|
+
|
69
|
+
# Rubinius
|
70
|
+
*.rbc
|
71
|
+
|
72
|
+
# RVM / rbenv
|
73
|
+
.ruby-version
|
74
|
+
.ruby-gemset
|
75
|
+
.rvmrc
|
76
|
+
|
77
|
+
# Vim
|
78
|
+
*.swp
|
79
|
+
*.swo
|
80
|
+
|
81
|
+
# RubyMine
|
82
|
+
.idea
|
83
|
+
|
84
|
+
# E-texteditor
|
85
|
+
.eprj
|
86
|
+
|
87
|
+
# Backup
|
88
|
+
*~
|
89
|
+
|
90
|
+
# Capybara Bug
|
91
|
+
capybara-*html
|
92
|
+
|
93
|
+
# sass
|
94
|
+
.sass-cache
|
95
|
+
.sass-cache/*
|
96
|
+
|
97
|
+
# SimpleCov (tests code converage)
|
98
|
+
/coverage
|
99
|
+
|
100
|
+
# RSpec
|
101
|
+
/spec/reports/
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.3.1
|
5
|
+
- 2.4.0
|
6
|
+
branches:
|
7
|
+
only:
|
8
|
+
- master
|
9
|
+
script: bundle exec rspec spec
|
10
|
+
notifications:
|
11
|
+
slack:
|
12
|
+
secure: HzwhTguhPlUK35NGFFrdMCDW0FFUOej4Ffl8MNKU6+lUH7dsac63wlGdxXh0MWC68QXpPgKys///bvmutDII6wbsqHoNijMQAPm+9ITg85m1/XQIbWu/O0bl31Mw4QH9c+sTUVSMW6HBg9L5mrRJi6I3I937iCbV7KRvgH8qrmYD43OM3xGircQv0LNbZl1D1mpfoAIhZmLh1G5eztxhbKT7EwDubxiJoz/AosQRY9YmwJ19X6huuq69xQB+74+Gs3EX1o7V0+3vkIqLzVOL2QoX4dCvMfOakim+FYKaytj+IIvC361ddLJQmHq+EBuF791bEJFk2FIQrPCYBRQiRJcAQMOnGBokCrWV/01NQyjMZCWuMpMTbMBMvA4by7cjok3fPw6kHmCl4vDEL+sqKa7/6BXEu140xfqRHcthvteqglimplXqflffiH9fT7IhyueJpfDVyo7BhCAGevMSazP9SJ1yeO30iPjQHC8TUf72uuRdzWid7JVV4QIYSiCs+dQSuW41I1eqmk6zoceOijPma77vPF91Ucr+rvlTCAG8ipjv1l2K96gS7nvFEus2GVc/GpukXkGS91MFNPNddCljK3YY0s4vke4TcV5l+/Y9hCbYOGjXxhxyimEBkoop3ar19gSvHjrbaPMR8evwl6nifCKu8IYU19iqPaSbYsI=
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,602 @@
|
|
1
|
+
# RabbitMQ::Actors
|
2
|
+
|
3
|
+
rabbitmq-actors is a simple and direct way to use RabbitMQ in your Ruby applications.
|
4
|
+
It uses the excellent [bunny](http://rubybunny.info/) Ruby client to RabbitMQ and basically, it provides a set of Ruby classes
|
5
|
+
implementing the most common producer-consumer patterns for multiple applications to exchange messages
|
6
|
+
using a RabbitMQ message broker:
|
7
|
+
- _master/worker_
|
8
|
+
- _publisher/subscriber_
|
9
|
+
- _routing producers/consumers_
|
10
|
+
- _topic producers/consumers_ and
|
11
|
+
- _headers producers/consumers_
|
12
|
+
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
If you don't have RabbitMQ installed, you can do it following the instructions in [RabbitMQ website](https://www.rabbitmq.com/download.html).
|
17
|
+
On Mac OSX, the fastest way is via [Homebrew](https://brew.sh/):
|
18
|
+
```
|
19
|
+
$ brew install rabbitmq
|
20
|
+
$ rabbitmq-server
|
21
|
+
```
|
22
|
+
Make sure, `/usr/local/sbin` is in your `$PATH`
|
23
|
+
|
24
|
+
Once rabbitmq is installed locally or remotely accessible, add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem 'rabbitmq-actors'
|
28
|
+
```
|
29
|
+
|
30
|
+
and execute:
|
31
|
+
|
32
|
+
$ bundle
|
33
|
+
|
34
|
+
or install it yourself as:
|
35
|
+
|
36
|
+
$ gem install rabbitmq-actors
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
|
40
|
+
To use rabbitmq-actors in your application, the first thing you need to do is set the url where the RabbitMQ messaging broker is running:
|
41
|
+
|
42
|
+
```
|
43
|
+
RabbitMQ::Server.url = 'amqp://localhost' # talk to local RabbitMQ server
|
44
|
+
```
|
45
|
+
|
46
|
+
### Patterns
|
47
|
+
There are different ways you can use RabbitMQ depending on the domain of your application.
|
48
|
+
RabbitMQ allows you to implement several message exchange patterns between producers and consumers.
|
49
|
+
Use the one/s that fit most the needs of your application:
|
50
|
+
|
51
|
+
### Master - Worker pattern
|
52
|
+
Under this strategy, one or several programs (masters) produce and publish messages to a known work queue.
|
53
|
+
On the other hand, consumer programs (workers) bind to that queue so that RabbitMQ distributes all the received
|
54
|
+
messages among the workers in a round robin manner. Therefore, every message is routed to only one of the workers.
|
55
|
+
|
56
|
+
**Creating a master producer**
|
57
|
+
|
58
|
+
This gem provides the `RabbitMQ::Actors::MasterProducer` class to help you implement master producers in your
|
59
|
+
application:
|
60
|
+
|
61
|
+
```
|
62
|
+
master = RabbitMQ::Actors::MasterProducer.new(
|
63
|
+
queue_name: 'transactions',
|
64
|
+
auto_delete: false,
|
65
|
+
reply_queue_name: 'confirmations',
|
66
|
+
logger: Rails.logger)
|
67
|
+
```
|
68
|
+
In the example above, `master` is a new MasterProducer instance routing messages to the `"purchases"`
|
69
|
+
queue (which won't be automatically deleted by RabbitMQ when there are no consumers listening to it: `auto_delete: false`).
|
70
|
+
|
71
|
+
The `reply_queue_name` param will add a `reply_to: "confirmations"` property to every message published by
|
72
|
+
this master instance so that the consumer receiving the message knows the queue where it has to publish
|
73
|
+
any response message.
|
74
|
+
|
75
|
+
The activity happening inside `master` (when publishing messages, closing connections...) will be logged to
|
76
|
+
`Rails.logger` (STDOUT by default) with `:info` severity level.
|
77
|
+
|
78
|
+
|
79
|
+
**Publishing messages**
|
80
|
+
|
81
|
+
To publish a message using our fresh new `master` instance, we just call `publish` instance method with some
|
82
|
+
mandatory arguments (message and :message_id):
|
83
|
+
|
84
|
+
```
|
85
|
+
message = { stock: 'Apple', number: 1000 }.to_json
|
86
|
+
master.publish(message, message_id: '1234837325', content_type: "application/json")
|
87
|
+
```
|
88
|
+
|
89
|
+
- `message` is a string containing the body of the message to be published to RabbitMQ
|
90
|
+
- `:message_id` is a keyword argument and contains any id our application uses to identify the message.
|
91
|
+
|
92
|
+
There are lots of optional params you can add to publish a message (see the
|
93
|
+
MasterProducer code documentation and Bunny::Exchange#publish code documentation):
|
94
|
+
|
95
|
+
- `persistent:` Should the message be persisted to disk?. Default true.
|
96
|
+
- `mandatory:` Should the message be returned if it cannot be routed to any queue?
|
97
|
+
- `timestamp:` A timestamp associated with this message
|
98
|
+
- `expiration:` Expiration time after which the message will be deleted
|
99
|
+
- `type:` Message type, e.g. what type of event or command this message represents. Can be any string
|
100
|
+
- `reply_to:` Queue name other apps should send the response to. Default to `:reply_queue_name if set at creation time.
|
101
|
+
- `content_type:` Message content type (e.g. application/json)
|
102
|
+
- `content_encoding:` Message content encoding (e.g. gzip)
|
103
|
+
- `correlation_id:` Message correlated to this one, e.g. what request this message is a reply for
|
104
|
+
- `priority:` Message priority, 0 to 9. Not used by RabbitMQ, only applications
|
105
|
+
- `user_id:` Optional user ID. Verified by RabbitMQ against the actual connection username
|
106
|
+
- `app_id:` Optional application ID
|
107
|
+
|
108
|
+
|
109
|
+
**Closing the channel**
|
110
|
+
|
111
|
+
Finally, close the channel when no more messages are going to be published by the master instance:
|
112
|
+
```
|
113
|
+
master.close
|
114
|
+
```
|
115
|
+
or using a chained method call after publishing the last message:
|
116
|
+
```
|
117
|
+
master.publish(message, message_id: '1234837325', content_type: "application/json").and_close
|
118
|
+
```
|
119
|
+
|
120
|
+
Actors (consumers and producers of all types) can share a connection to RabbitMQ broker
|
121
|
+
but each one connects via its own private channel. Although RabbitMQ can have thousands
|
122
|
+
of channels open simulataneously, it is a good practice to close it when an actor is not
|
123
|
+
being used anymore.
|
124
|
+
|
125
|
+
**Defining a worker consumer**
|
126
|
+
|
127
|
+
To define worker consumers of master produced messages, subclass `RabbitMQ::Actors::Worker` class and
|
128
|
+
define a private `perform` instance method to process a received message like this:
|
129
|
+
|
130
|
+
```
|
131
|
+
class MyListener < RabbitMQ::Actors::Worker
|
132
|
+
def initialize
|
133
|
+
super(queue_name: 'transactions',
|
134
|
+
manual_ack: true
|
135
|
+
logger: Rails.logger,
|
136
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close }
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def perform(**task)
|
142
|
+
message, message_id = JSON.parse(task[:body]), task[:properties][:message_id]
|
143
|
+
...
|
144
|
+
# your code to process the message
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
`RabbitMQ::Actors::Worker` class requires a mandatory keyword argument to initialize instances:
|
150
|
+
|
151
|
+
- `queue_name:` string containing the name of the durable queue where to receive messages from.
|
152
|
+
|
153
|
+
Other optional params you can add to initialize a worker (see the Worker code documentation):
|
154
|
+
|
155
|
+
- `manual_ack:` tells RabbitMQ to wait for a manual acknowledge from the worker before
|
156
|
+
marking a message as "processed" and remove it from the queue. If true, the acknowledgement will be
|
157
|
+
automatically sent by the worker after returning from `perform` method. Defaults to false.
|
158
|
+
- `logger:` where to log worker activity with :info severity. Defaults to STDOUT if none provided.
|
159
|
+
- `on_cancellation`: a Proc/Lambda object to be called right before the worker is terminated.
|
160
|
+
|
161
|
+
|
162
|
+
**Running a worker consumer**
|
163
|
+
|
164
|
+
Call #start! method on a worker instance to start listening and processing messages from the associated queue.
|
165
|
+
|
166
|
+
```
|
167
|
+
MyListener.new.start!
|
168
|
+
```
|
169
|
+
|
170
|
+
Press ^c on a console to stop a worker or send a process termination signal.
|
171
|
+
|
172
|
+
|
173
|
+
### Publish - Subscribe pattern
|
174
|
+
The idea behind this strategy is to broadcast messages to all the subscribed consumers (subscribers)
|
175
|
+
as opposed to only one consumer as it was the case in the Master - Worker pattern.
|
176
|
+
|
177
|
+
**Creating a publisher**
|
178
|
+
|
179
|
+
Instantiate the class `RabbitMQ::Actors::Publisher` to create publishers of messages under this scheme:
|
180
|
+
|
181
|
+
```
|
182
|
+
publisher = RabbitMQ::Actors::Publisher.new(exchange_name: 'sports', logger: Rails.logger)
|
183
|
+
```
|
184
|
+
|
185
|
+
`exchange_name:` param is mandatory and contains the name of the RabbitMQ exchange where to publis the
|
186
|
+
messages.
|
187
|
+
|
188
|
+
As we know from the master producer, the activity happening inside `publisher` can be sent to a logger
|
189
|
+
instance (`Rails.logger` in the example) with `:info` severity level.
|
190
|
+
|
191
|
+
Again, like master producers, `RabbitMQ::Actors::Publisher` you can pass a `reply_queue_name:` keyword
|
192
|
+
param to create a new instance.
|
193
|
+
|
194
|
+
|
195
|
+
**Publishing messages**
|
196
|
+
|
197
|
+
The way for a publisher to publish messages is identical to that of master producers. See documentation
|
198
|
+
above.
|
199
|
+
|
200
|
+
```
|
201
|
+
publisher.publish(message, message_id: '1232357390', content_type: "application/json")
|
202
|
+
```
|
203
|
+
|
204
|
+
**Closing the channel**
|
205
|
+
|
206
|
+
```
|
207
|
+
publisher.close
|
208
|
+
```
|
209
|
+
or using the chained way:
|
210
|
+
```
|
211
|
+
publisher.publish(message, message_id: '1234837390', content_type: "application/json").and_close
|
212
|
+
```
|
213
|
+
|
214
|
+
|
215
|
+
**Defining a subscriber**
|
216
|
+
|
217
|
+
To define your subscriber class, make it a subclass of `RabbitMQ::Actors::Subscriber` class and
|
218
|
+
define a private `perform` instance method to process a received message like this:
|
219
|
+
|
220
|
+
```
|
221
|
+
class ScoresListener < RabbitMQ::Actors::Subscriber
|
222
|
+
def initialize
|
223
|
+
super(exchange_name: 'scores',
|
224
|
+
logger: Rails.logger,
|
225
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def perform(**task)
|
231
|
+
match_data = JSON.parse(task[:body])
|
232
|
+
process_match(match_data)
|
233
|
+
end
|
234
|
+
...
|
235
|
+
end
|
236
|
+
```
|
237
|
+
|
238
|
+
`RabbitMQ::Actors::Publiser` class requires the following mandatory keyword argument:
|
239
|
+
|
240
|
+
- `exchange_name:` name of the exchange where to consume messages from.
|
241
|
+
|
242
|
+
You can also add `logger:` and `on_cancellation:` keyword params (see worker documentation above)
|
243
|
+
|
244
|
+
|
245
|
+
**Running a subscriber**
|
246
|
+
|
247
|
+
Like every consumer actor, call #start! method on an instance to start listening and processing messages.
|
248
|
+
|
249
|
+
```
|
250
|
+
ScoresListener.new.start!
|
251
|
+
```
|
252
|
+
|
253
|
+
Press ^c on a console or send a process termination signal to stop it.
|
254
|
+
|
255
|
+
|
256
|
+
### Routing pattern
|
257
|
+
The routing pattern is similar to _publish/subscribe_ strategy but messages are not routed to all consumers but
|
258
|
+
only to those bound to the value a property of the message named `routing_key`.
|
259
|
+
Every consumer bound to the exchange states those `routing_key` values it is interested in. When a message
|
260
|
+
arrives it comes with a certain `routing_key` value, so the exchange routes it to only those consumers
|
261
|
+
interested in that particular value.
|
262
|
+
|
263
|
+
**Creating a routing producer**
|
264
|
+
|
265
|
+
Instantiate the class `RabbitMQ::Actors::RoutingProducer` to create publishers of messages under this scheme:
|
266
|
+
|
267
|
+
```
|
268
|
+
routing_producer = RabbitMQ::Actors::RoutingProducer.new(
|
269
|
+
exchange_name: 'sports',
|
270
|
+
replay_queue_name: 'scores',
|
271
|
+
logger: Rails.logger)
|
272
|
+
```
|
273
|
+
The description of the 3 params is similar to that of the previous producer classes. See above.
|
274
|
+
|
275
|
+
**Publishing messages**
|
276
|
+
|
277
|
+
```
|
278
|
+
message = {
|
279
|
+
championship: 'Wimbledon',
|
280
|
+
match: {
|
281
|
+
player_1: 'Rafa Nadal',
|
282
|
+
player_2: 'Roger Federed',
|
283
|
+
date: '01-Jul-2016'
|
284
|
+
} }.to_json
|
285
|
+
|
286
|
+
routing_producer.publish(message, message_id: '1234837633', content_type: "application/json", routing_key: 'tennis')
|
287
|
+
```
|
288
|
+
The way to publish messages is similar to that of the rest of producers. Note the mandatory param `routing_key:`
|
289
|
+
|
290
|
+
- `routing_key:` send the message only to queues bound to this string value.
|
291
|
+
|
292
|
+
**Closing the channel**
|
293
|
+
|
294
|
+
```
|
295
|
+
routing_producer.close
|
296
|
+
```
|
297
|
+
or using the chained way:
|
298
|
+
```
|
299
|
+
routing_producer.publish(message, message_id: '1234837633', content_type: "application/json", routing_key: 'tennis').and_close
|
300
|
+
```
|
301
|
+
|
302
|
+
|
303
|
+
**Defining a routing consumer**
|
304
|
+
|
305
|
+
Use the class `RabbitMQ::Actors::RoutingConsumer` in a similar way as to the rest of consumer types:
|
306
|
+
|
307
|
+
```
|
308
|
+
class TennisListener < RabbitMQ::Actors::RoutingConsumer
|
309
|
+
def initialize
|
310
|
+
super(exchange_name: 'sports',
|
311
|
+
binding_keys: ['tennis'],
|
312
|
+
logger: Rails.logger,
|
313
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
def perform(**task)
|
319
|
+
match_data = JSON.parse(task[:body])
|
320
|
+
process_tennis_match(match_data)
|
321
|
+
end
|
322
|
+
...
|
323
|
+
end
|
324
|
+
|
325
|
+
class FootballListener < RabbitMQ::Actors::RoutingConsumer
|
326
|
+
def initialize
|
327
|
+
super(exchange_name: 'sports',
|
328
|
+
binding_keys: ['football', 'soccer'],
|
329
|
+
logger: Rails.logger,
|
330
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
def perform(**task)
|
336
|
+
match_data = JSON.parse(task[:body])
|
337
|
+
process_footbal_match(match_data)
|
338
|
+
end
|
339
|
+
...
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
`RabbitMQ::Actors::RoutingConsumer` class requires the following mandatory keyword arguments:
|
344
|
+
|
345
|
+
- `exchange_name:` name of the exchange where to consume messages from.
|
346
|
+
- `binding_keys:` a string or list of strings with the routing key values this consumer is interested in.
|
347
|
+
|
348
|
+
You can also add `logger:` and `on_cancellation:` keyword params (see worker documentation above)
|
349
|
+
|
350
|
+
|
351
|
+
**Running a routing consumer**
|
352
|
+
|
353
|
+
Like every consumer actor, call #start! method on an instance to start listening and processing messages.
|
354
|
+
|
355
|
+
```
|
356
|
+
TennisListener.new.start!
|
357
|
+
FootballListener.new.start!
|
358
|
+
```
|
359
|
+
|
360
|
+
Press ^c on a console or send a process termination signal to stop it.
|
361
|
+
|
362
|
+
|
363
|
+
### Topics pattern
|
364
|
+
The topics pattern is very similar to the routing one. However routing keys are not free string values. Instead,
|
365
|
+
every routing key is a dot separated list of words.
|
366
|
+
|
367
|
+
Binding keys can use special chars to match one or several words:
|
368
|
+
|
369
|
+
`* can substitute for exactly one word`
|
370
|
+
`# can substitute for zero or more words`
|
371
|
+
|
372
|
+
**Creating a topic producer**
|
373
|
+
|
374
|
+
Instantiate the class `RabbitMQ::Actors::TopicProducer` to create publishers of messages under this scheme:
|
375
|
+
|
376
|
+
```
|
377
|
+
topic_producer = RabbitMQ::Actors::TopicProducer.new(topic_name: 'weather', logger: Rails.logger)
|
378
|
+
```
|
379
|
+
|
380
|
+
where
|
381
|
+
|
382
|
+
- `topic_name:` (mandatory) is the name of the exchange to send messages to.
|
383
|
+
|
384
|
+
`reply_queue_name:` and `logger` are the 2 optional params that can be added.
|
385
|
+
|
386
|
+
|
387
|
+
**Publishing messages**
|
388
|
+
|
389
|
+
```
|
390
|
+
message = { temperature: 20, rain: 30%, wind: 'NorthEast' }.to_json
|
391
|
+
topic_producer.publish(message, message_id: '1234837633', content_type: "application/json", routing_key: 'Europe.Spain.Madrid')
|
392
|
+
```
|
393
|
+
|
394
|
+
Note the special format of the mandatory `routing_key:` param
|
395
|
+
|
396
|
+
|
397
|
+
**Closing the channel**
|
398
|
+
|
399
|
+
```
|
400
|
+
topic_producer.close
|
401
|
+
```
|
402
|
+
|
403
|
+
or using the chained way:
|
404
|
+
|
405
|
+
```
|
406
|
+
topic_producer.publish(message, message_id: '1234837633', content_type: "application/json", routing_key: 'Europe.Spain.Madrid').and_close
|
407
|
+
```
|
408
|
+
|
409
|
+
|
410
|
+
**Defining a topic consumer**
|
411
|
+
|
412
|
+
Use the class `RabbitMQ::Actors::TopicConsumer` in a similar way as to the rest of consumer types:
|
413
|
+
|
414
|
+
```
|
415
|
+
class SpainTennisListener < RabbitMQ::Actors::TopicConsumer
|
416
|
+
def initialize
|
417
|
+
super(topic_name: 'sports',
|
418
|
+
binding_keys: '#.tennis.#.spain.#',
|
419
|
+
logger: Rails.logger,
|
420
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
421
|
+
end
|
422
|
+
|
423
|
+
private
|
424
|
+
|
425
|
+
def perform(**task)
|
426
|
+
match_data = JSON.parse(task[:body])
|
427
|
+
process_tennis_match(match_data)
|
428
|
+
end
|
429
|
+
|
430
|
+
def process_tennis_match(data)
|
431
|
+
...
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
class AmericaSoccerListener < RabbitMQ::Actors::TopicConsumer
|
436
|
+
def initialize
|
437
|
+
super(exchange_name: 'sports',
|
438
|
+
binding_keys: '#.soccer.#.america.#',
|
439
|
+
logger: Rails.logger,
|
440
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
441
|
+
end
|
442
|
+
|
443
|
+
private
|
444
|
+
|
445
|
+
def perform(**task)
|
446
|
+
match_data = JSON.parse(task[:body])
|
447
|
+
process_soccer_match(match_data)
|
448
|
+
end
|
449
|
+
|
450
|
+
def process_soccer_match(data)
|
451
|
+
...
|
452
|
+
end
|
453
|
+
end
|
454
|
+
```
|
455
|
+
|
456
|
+
`RabbitMQ::Actors::TopicConsumer` class requires the following mandatory keyword arguments:
|
457
|
+
|
458
|
+
- `topic_name:` name of the exchange where to consume messages from.
|
459
|
+
- `binding_keys:` a string or list of strings with the routing key matching patterns this consumer
|
460
|
+
is interested in.
|
461
|
+
|
462
|
+
As always, you can also add `logger:` and `on_cancellation:` keyword params (see worker documentation above)
|
463
|
+
|
464
|
+
|
465
|
+
**Running a topic consumer**
|
466
|
+
|
467
|
+
Like every consumer actor, call #start! method on an instance to start listening and processing messages.
|
468
|
+
|
469
|
+
```
|
470
|
+
SpainTennisListener.new.start!
|
471
|
+
AmericaSoccerListener.new.start!
|
472
|
+
```
|
473
|
+
|
474
|
+
Press ^c on a console or send a process termination signal to stop it.
|
475
|
+
|
476
|
+
|
477
|
+
### Headers pattern
|
478
|
+
The headers pattern is a strategy based on headers instead of routing keys to deliver messages
|
479
|
+
to consumers. Messages add a `headers:` property including pairs of key-value entries.
|
480
|
+
Consumers show interest in certain headers to get messages sent.
|
481
|
+
|
482
|
+
**Creating a headers producer**
|
483
|
+
|
484
|
+
Instantiate the class `RabbitMQ::Actors::HeadersProducer` to create publishers of messages under this scheme:
|
485
|
+
|
486
|
+
```
|
487
|
+
headers_producer = RabbitMQ::Actors::HeadersProducer.new(headers_name: 'reports', logger: Rails.logger)
|
488
|
+
```
|
489
|
+
|
490
|
+
where
|
491
|
+
|
492
|
+
- `headers_name:` (mandatory) is the name of the exchange to send messages to.
|
493
|
+
|
494
|
+
`reply_queue_name:` and `logger` are the 2 optional params that can be added.
|
495
|
+
|
496
|
+
|
497
|
+
**Publishing messages**
|
498
|
+
|
499
|
+
```
|
500
|
+
message = 'A report about USA economy'
|
501
|
+
headers_producer.publish(
|
502
|
+
message,
|
503
|
+
message_id: '1234837633',
|
504
|
+
headers: { 'type' => :economy, 'area' => 'USA'})
|
505
|
+
```
|
506
|
+
|
507
|
+
where
|
508
|
+
|
509
|
+
- `headers:` send the message only to consumers bound to this exchange and matching any/all
|
510
|
+
of these header pairs.
|
511
|
+
|
512
|
+
As usual, `message` and `message_id:` are also mandatory params. See documentation above for
|
513
|
+
all the optional message params.
|
514
|
+
|
515
|
+
|
516
|
+
**Closing the channel**
|
517
|
+
|
518
|
+
```
|
519
|
+
headers_producer.close
|
520
|
+
```
|
521
|
+
|
522
|
+
or using the chained way:
|
523
|
+
|
524
|
+
```
|
525
|
+
headers_producer.publish(...).and_close
|
526
|
+
```
|
527
|
+
|
528
|
+
|
529
|
+
**Defining a headers consumer**
|
530
|
+
|
531
|
+
Use the class `RabbitMQ::Actors::HeadersConsumer` in a similar way as to the rest of consumer types:
|
532
|
+
|
533
|
+
````
|
534
|
+
class NewYorkBranchListener < RabbitMQ::Actors::HeadersConsumer
|
535
|
+
def initialize
|
536
|
+
super(headers_name: 'reports',
|
537
|
+
binding_headers: { 'type' => :econony, 'area' => 'USA', 'x-match' => 'any' },
|
538
|
+
logger: Rails.logger,
|
539
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
540
|
+
end
|
541
|
+
|
542
|
+
private
|
543
|
+
|
544
|
+
def perform(**task)
|
545
|
+
report_data = JSON.parse(task[:body])
|
546
|
+
process_report(report_data)
|
547
|
+
end
|
548
|
+
|
549
|
+
def process_report(data)
|
550
|
+
...
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
class LondonBranchListener < RabbitMQ::Actors::HeadersConsumer
|
555
|
+
def initialize
|
556
|
+
super(headers_name: 'reports',
|
557
|
+
binding_headers: { 'type' => :industry, 'area' => 'Europe', 'x-match' =>'any' },
|
558
|
+
logger: Rails.logger,
|
559
|
+
on_cancellation: ->{ ActiveRecord::Base.connection.close })
|
560
|
+
end
|
561
|
+
|
562
|
+
private
|
563
|
+
|
564
|
+
def perform(**task)
|
565
|
+
report_data = JSON.parse(task[:body])
|
566
|
+
process_report(report_data)
|
567
|
+
end
|
568
|
+
|
569
|
+
def process_report(data)
|
570
|
+
...
|
571
|
+
end
|
572
|
+
end
|
573
|
+
````
|
574
|
+
|
575
|
+
`RabbitMQ::Actors::HedersConsumer` class requires the following mandatory keyword arguments:
|
576
|
+
|
577
|
+
- `headers_name:` name of the exchange where to consume messages from.
|
578
|
+
- `binding_headers:` hash of headers this consumer is interested in.
|
579
|
+
|
580
|
+
Note the special mandatory binding header `'x-match'`. Its value can be one of these:
|
581
|
+
- `'any'` receive the message if any of the message headers matches any of the binding headers.
|
582
|
+
- `'all'` receive the message only if all of the binding headers are included in the message headers.
|
583
|
+
|
584
|
+
Optional params `logger:` and `on_cancellation:`
|
585
|
+
|
586
|
+
|
587
|
+
**Running a headers consumer**
|
588
|
+
|
589
|
+
Like every consumer actor, call #start! method on an instance to start listening and processing messages.
|
590
|
+
|
591
|
+
```
|
592
|
+
NewYorkBranchListener.new.start!
|
593
|
+
LondonBranchListener.new.start!
|
594
|
+
```
|
595
|
+
|
596
|
+
Press ^c on a console or send a process termination signal to stop it.
|
597
|
+
|
598
|
+
|
599
|
+
## Contributing
|
600
|
+
|
601
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[ltello]/rabbitmq-actors.
|
602
|
+
|