liebre 0.1.21 → 0.2.1

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/Gemfile.lock +9 -7
  4. data/{LICENSE → LICENSE.txt} +1 -1
  5. data/README.md +492 -195
  6. data/Rakefile +2 -0
  7. data/lib/liebre.rb +27 -16
  8. data/lib/liebre/actor.rb +11 -0
  9. data/lib/liebre/actor/consumer.rb +80 -0
  10. data/lib/liebre/actor/consumer/callback.rb +34 -0
  11. data/lib/liebre/actor/consumer/core.rb +80 -0
  12. data/lib/liebre/actor/consumer/reporter.rb +84 -0
  13. data/lib/liebre/actor/consumer/resources.rb +47 -0
  14. data/lib/liebre/actor/consumer/resources/config.rb +65 -0
  15. data/lib/liebre/actor/context.rb +40 -0
  16. data/lib/liebre/actor/context/declare.rb +44 -0
  17. data/lib/liebre/actor/context/handler.rb +44 -0
  18. data/lib/liebre/actor/publisher.rb +58 -0
  19. data/lib/liebre/actor/publisher/core.rb +42 -0
  20. data/lib/liebre/actor/publisher/reporter.rb +55 -0
  21. data/lib/liebre/actor/publisher/resources.rb +33 -0
  22. data/lib/liebre/actor/rpc/client.rb +88 -0
  23. data/lib/liebre/actor/rpc/client/core.rb +75 -0
  24. data/lib/liebre/actor/rpc/client/pending.rb +65 -0
  25. data/lib/liebre/actor/rpc/client/reporter.rb +71 -0
  26. data/lib/liebre/actor/rpc/client/resources.rb +62 -0
  27. data/lib/liebre/actor/rpc/client/task.rb +33 -0
  28. data/lib/liebre/actor/rpc/server.rb +74 -0
  29. data/lib/liebre/actor/rpc/server/callback.rb +28 -0
  30. data/lib/liebre/actor/rpc/server/core.rb +75 -0
  31. data/lib/liebre/actor/rpc/server/reporter.rb +72 -0
  32. data/lib/liebre/actor/rpc/server/resources.rb +53 -0
  33. data/lib/liebre/adapter.rb +8 -0
  34. data/lib/liebre/adapter/bunny.rb +23 -0
  35. data/lib/liebre/adapter/bunny/chan.rb +38 -0
  36. data/lib/liebre/adapter/bunny/conn.rb +32 -0
  37. data/lib/liebre/adapter/bunny/exchange.rb +20 -0
  38. data/lib/liebre/adapter/bunny/queue.rb +59 -0
  39. data/lib/liebre/adapter/interface.rb +26 -0
  40. data/lib/liebre/adapter/interface/chan.rb +29 -0
  41. data/lib/liebre/adapter/interface/conn.rb +21 -0
  42. data/lib/liebre/adapter/interface/exchange.rb +13 -0
  43. data/lib/liebre/adapter/interface/queue.rb +37 -0
  44. data/lib/liebre/bridge.rb +72 -0
  45. data/lib/liebre/bridge/channel_builder.rb +36 -0
  46. data/lib/liebre/config.rb +8 -38
  47. data/lib/liebre/engine.rb +61 -0
  48. data/lib/liebre/engine/builder.rb +48 -0
  49. data/lib/liebre/engine/repository.rb +56 -0
  50. data/lib/liebre/engine/state.rb +49 -0
  51. data/lib/liebre/runner.rb +15 -47
  52. data/lib/liebre/version.rb +1 -1
  53. data/liebre.gemspec +9 -7
  54. data/spec/integration/publish_and_consume_spec.rb +71 -0
  55. data/spec/integration/rpc_communication_spec.rb +81 -0
  56. data/spec/integration/start_twice_spec.rb +63 -0
  57. data/spec/liebre/actor/consumer_spec.rb +169 -0
  58. data/spec/liebre/actor/context/declare_spec.rb +69 -0
  59. data/spec/liebre/actor/context/handler_spec.rb +65 -0
  60. data/spec/liebre/actor/publisher_spec.rb +58 -0
  61. data/spec/liebre/actor/rpc/client_spec.rb +126 -0
  62. data/spec/liebre/actor/rpc/server_spec.rb +141 -0
  63. data/spec/liebre/adapter/bunny_spec.rb +66 -0
  64. data/spec/liebre/bridge_spec.rb +54 -0
  65. data/spec/liebre/engine/builder_spec.rb +42 -0
  66. data/spec/liebre/engine_spec.rb +90 -0
  67. data/spec/liebre/version_spec.rb +10 -0
  68. data/spec/spec_helper.rb +2 -9
  69. metadata +97 -58
  70. data/lib/liebre/common.rb +0 -7
  71. data/lib/liebre/common/utils.rb +0 -37
  72. data/lib/liebre/connection_manager.rb +0 -85
  73. data/lib/liebre/publisher.rb +0 -113
  74. data/lib/liebre/runner/consumers.rb +0 -46
  75. data/lib/liebre/runner/starter.rb +0 -44
  76. data/lib/liebre/runner/starter/consumer.rb +0 -129
  77. data/lib/liebre/runner/starter/consumer/handler.rb +0 -35
  78. data/lib/liebre/runner/starter/resources.rb +0 -45
  79. data/lib/liebre/runner/starter/resources/queue_builder.rb +0 -63
  80. data/lib/liebre/runner/starter/rpc.rb +0 -59
  81. data/lib/liebre/tasks.rb +0 -12
  82. data/spec/config/liebre.yml +0 -48
  83. data/spec/config/rabbitmq.yml +0 -35
  84. data/spec/integration_spec.rb +0 -76
  85. data/spec/liebre/config_spec.rb +0 -63
  86. data/spec/liebre/connection_manager_spec.rb +0 -44
  87. data/spec/liebre/publisher_spec.rb +0 -92
  88. data/spec/liebre/runner/consumers_spec.rb +0 -59
  89. data/spec/liebre/runner/starter/consumer_spec.rb +0 -145
  90. data/spec/liebre/runner/starter/resources/queue_builder_spec.rb +0 -69
  91. data/spec/liebre/runner/starter/resources_spec.rb +0 -38
  92. data/spec/liebre/runner/starter/rpc_spec.rb +0 -100
  93. data/spec/liebre/runner/starter_spec.rb +0 -70
  94. data/spec/liebre/runner_spec.rb +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e5a7ee5a915ac1eef958be46fcaa5dc4f84c6d5
4
- data.tar.gz: 162226313a78c9c3373b56edc4a66844ff69568c
3
+ metadata.gz: d8d00979bc470fd55b7dce291b96658266ea74ac
4
+ data.tar.gz: 48dff8d83158aa0a84f74a5aaac36e1f86de64b1
5
5
  SHA512:
6
- metadata.gz: f75296a952c2a8f02de319343df92d8f9caf839ec819a57a909877fffff5dfd99046d7cf0d65ed38fbffe5c76c3bdc6b2739d8f8f556901ce13dd0799ebf9da5
7
- data.tar.gz: 18b09cb54e1430ef62e2c7a807c3561cef1b8a4650987ad3f3075196fc8fc7f4eb4431dd41a788627ed6e4bc08ac34eb33f914cfed50105b72675f1fbc88dbc3
6
+ metadata.gz: b4d5fe0344c0cecf9ff3313ff58521406d20b9f6df88aae242bcfc450860f8dabd1180a3f0ec11371297fa2a1792508d279fd7d1b4bbc03dfcafc303dc9631bf
7
+ data.tar.gz: 20f95bcd2593a5bf0ef664f242fb3d53a7d429d785b83106643eafec38afaee2e021a512f4deb4b442a3f03ecae3f3fae0c9e623e4df0bf6bd1306fdad3e3b2c
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -1,28 +1,29 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- liebre (0.1.15)
5
- bunny (~> 2.5, >= 2.5.1)
4
+ liebre (0.2.0)
5
+ concurrent-ruby
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  amq-protocol (2.0.1)
11
- bunny (2.5.1)
11
+ bunny (2.2.2)
12
12
  amq-protocol (>= 2.0.1)
13
13
  coderay (1.1.1)
14
- diff-lcs (1.2.5)
14
+ concurrent-ruby (1.0.5)
15
+ diff-lcs (1.3)
15
16
  method_source (0.8.2)
16
17
  pry (0.10.4)
17
18
  coderay (~> 1.1.0)
18
19
  method_source (~> 0.8.1)
19
20
  slop (~> 3.4)
20
- rake (11.2.2)
21
+ rake (12.0.0)
21
22
  rspec (3.5.0)
22
23
  rspec-core (~> 3.5.0)
23
24
  rspec-expectations (~> 3.5.0)
24
25
  rspec-mocks (~> 3.5.0)
25
- rspec-core (3.5.2)
26
+ rspec-core (3.5.4)
26
27
  rspec-support (~> 3.5.0)
27
28
  rspec-expectations (3.5.0)
28
29
  diff-lcs (>= 1.2.0, < 2.0)
@@ -38,10 +39,11 @@ PLATFORMS
38
39
 
39
40
  DEPENDENCIES
40
41
  bundler (~> 1.6)
42
+ bunny
41
43
  liebre!
42
44
  pry
43
45
  rake
44
46
  rspec
45
47
 
46
48
  BUNDLED WITH
47
- 1.13.6
49
+ 1.14.6
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016 jcabotc
1
+ Copyright (c) 2017 jcabotc
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,268 +1,565 @@
1
1
  # Liebre
2
2
 
3
- ## Intro
3
+ A library to interact with AMQP servers.
4
4
 
5
- **Liebre stands for hare in spanish.**
5
+ Liebre stands for hare in spanish.
6
6
 
7
- This is a gem that handles RabbitMQ consumers, publishers and RPCs, based on [bunny](https://github.com/ruby-amqp/bunny).
7
+ ## Installation
8
8
 
9
- * It allows to create classes that will be invoked everytime a message it's received in its subscribed queue.
10
- * It handles RPCs as a special Consumer where a callback message is returned to the exchange.
11
- * You can use its Publisher to send message to an exchange.
12
- * There is also a Publisher RPC method to send a message and wait for its response.
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'liebre'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install liebre
20
+
21
+ ## Introduction
22
+
23
+ The Liebre library provides 4 abstractions, or **actors**, to interact with the server:
24
+
25
+ * **Publisher**: Publishes messages to an exchange
26
+ * **Consumer**: Binds a queue to an exchange, and consumes messages
27
+ * **RPC Client**: Publishes messages to an exchange and blocks until a response is received through an exclusive queue
28
+ * **RPC Server**: Binds a queue to an exchange, consumes messages, and replies the caller by putting a message at the specified queue
29
+
30
+ Each actor has its own thread and its own channel. Some actors (Consumer and RPC Server)
31
+ also have their own thread pool in order to be able to handle messages concurrently.
32
+
33
+ It leverages [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) `Concurrent::Async` mixin to
34
+ implement the actors.
13
35
 
14
36
  ## Configuration
15
- It is based on 2 config files:
16
37
 
17
- * rabbitmq.yml: it contains RabbitMQ connection configurations and can be enviroment dependant (default path `config/rabbitmq.yml`), it must contain, at least the `default` connection
18
- * without environment set:
38
+ Liebre accepts the following configuration options:
39
+
40
+ ```ruby
41
+ Liebre.configure do |config|
42
+ config.logger = $logger
43
+ config.adapter = Liebre::Adapter::Bunny
44
+ config.connections = connections_config
45
+ config.actors = actors_config
46
+ end
19
47
  ```
20
- default:
21
- :host: localhost
22
- :port: 5672
23
- :user: guest
24
- :pass: guest
25
- :vhost: /
26
- :threaded: true
27
- :heartbeat: 2
48
+
49
+ ### Logger
50
+
51
+ The logger configuration option is optional and defaults to `Logger.new(nil)`.
52
+ Liebre will log in the following events:
53
+
54
+ * An actor is started
55
+ * An actor is stopped
56
+ * Some error happened on the actor's thread
57
+
58
+ Any other logging should be done from the application.
59
+
60
+ ### Adapter
61
+
62
+ It specifies the adapter to use to interact with the server. The only adapter that ships with
63
+ the library is the `Liebre::Adapter::Bunny` adapter that uses the `bunny` gem.
64
+
65
+ **IMPORTANT**: Note that you should have the `bunny` gem available to use the
66
+ `Liebre::Adapter::Bunny` adapter.
67
+
68
+ ### Connections
69
+
70
+ On startup Liebre will establish a set of connections with one or more AMQP servers. Actors can be
71
+ started on any of this connections.
72
+
73
+ ```ruby
74
+ {one: {host: "foo.com", port: 123},
75
+ other: {}}
28
76
  ```
29
- * with environment set:
77
+
78
+ The example above will establish two connections, the configuration for each connection
79
+ will be given with no modification to the adapter.
80
+
81
+ ### Actors
82
+
83
+ The configuration of all actors:
84
+
85
+ ```ruby
86
+ {
87
+ publishers: {
88
+ my_publisher: {connection: :one,
89
+ resources: {
90
+ exchange: {name: "foo", type: "fanout"}}},
91
+ my_other_pub: {connection: :other,
92
+ resources: {
93
+ exchange: {name: "bar", type: "direct", opts: {durable: true}}}}
94
+ },
95
+ consumers: {
96
+ my_consumer: {connection: :one,
97
+ handler: MyApp::Consumer,
98
+ prefetch_count: 10,
99
+ pool_size: 10,
100
+ resources: {
101
+ exchange: {name: "baz", type: "fanout"},
102
+ queue: {name: "baz_queue"},
103
+ bind: [{routing_key: "a_key"}, {routing_key: "another"}]}},
104
+ },
105
+ rpc_clients: {
106
+ my_rpc_client: {connection: :other,
107
+ resources: {
108
+ exchange: {name: "qux", type: "fanout"}}},
109
+ },
110
+ rpc_servers: {
111
+ my_rpc_server: {connection: :one,
112
+ handler: MyApp::RPCServer,
113
+ prefetch_count: 5,
114
+ pool_size: 5,
115
+ resources: {
116
+ exchange: {name: "quux", type: "fanout"},
117
+ queue: {name: "quux_queue"}}}
118
+ }
119
+ }
30
120
  ```
31
- development:
32
- default:
33
- :host: localhost
34
- :port: 5672
35
- :user: guest
36
- :pass: guest
37
- :vhost: /
38
- :threaded: true
39
- :heartbeat: 2
40
- rpc:
41
- :host: localhost
42
- :port: 5672
43
- :user: guest
44
- :pass: guest
45
- :vhost: rpc
46
- :threaded: true
47
- :heartbeat: 2
48
- test:
49
- default:
50
- :host: localhost
51
- :port: 5672
52
- :user: guest
53
- :pass: guest
54
- :vhost: /
55
- :threaded: true
56
- :heartbeat: 2
57
- rpc:
58
- :host: localhost
59
- :port: 5672
60
- :user: guest
61
- :pass: guest
62
- :vhost: rpc
63
- :threaded: true
64
- :heartbeat: 2
65
-
66
- production:
67
- ...
121
+
122
+ The example above will start 5 actors:
123
+
124
+ * A publisher to the `"foo"` exchange over the connection `:one`
125
+ * A publisher to the `"bar"` exchange over the connection `:other`
126
+ * A consumer of the "baz" queue that will consume messages over the connection `:one` and process them by running `MyApp::Consumer` handler on a pool of 10 dedicated threads
127
+ * A rpc client to the `"qux"` exchange over the connection `:other`
128
+ * A rpc server of the "quux" queue that will consume messages over the connection `:one` and process them by running `MyApp::RPCServer` handler on a pool of 5 dedicated threads
129
+
130
+ Common options:
131
+
132
+ * `:connection` - The name of the connection to open the channel over
133
+ * `:resources` - Configuration about queues and exchanges. Depends on the actor
134
+
135
+ Options for message handlers (Consumer and RPC Server):
136
+
137
+ * `:handler` - The class to use to handle messages. Its interface depends on the actor
138
+ * `:prefetch_count` - The prefetch count of the actor's channel
139
+ * `:pool_size` - The number of dedicated threads that will be used to handle messages concurrently
140
+
141
+ ## Start the engine
142
+
143
+ ### On an existing service
144
+
145
+ A `Liebre::Engine` is an object that is able to start and stop a given configuration.
146
+
147
+ ```ruby
148
+ require 'yaml'
149
+ require 'liebre'
150
+
151
+ config = Liebre::Config.new
152
+
153
+ config.adapter = Liebre::Adapter::Bunny
154
+ config.connections = YAML.load_file("config/rabbitmq.yml")
155
+ config.actors = YAML.load_file("config/liebre.yml")
156
+
157
+ engine = Liebre::Engine.new(config)
158
+ engine.start
68
159
  ```
69
160
 
70
- * liebre.yml (default path `config/liebre.yml`)
161
+ The example above will establish the specified connections and start all actors with
162
+ their own threads and thread pools.
163
+
164
+ Usually only one engine is required per application. To simplify the process, a default
165
+ engine that uses the default config is provided.
166
+
167
+ ```ruby
168
+ require 'liebre'
169
+
170
+ Liebre.configure do |config|
171
+ config.adapter = Liebre::Adapter::Bunny
172
+ config.connections = YAML.load_file("config/rabbitmq.yml")
173
+ config.actors = YAML.load_file("config/liebre.yml")
174
+ end
175
+
176
+ Liebre.engine.start
177
+ ```
178
+
179
+ This kind of startup is commonly used on rack applications. The previous code
180
+ can be called from a Rails initializer.
181
+
182
+ ### As a standalone application
183
+
184
+ If you wish to start Liebre as a standalone application `Liebre::Runner` is provided.
185
+
186
+ ```ruby
187
+ require 'liebre'
188
+
189
+ Liebre.configure do |config|
190
+ # some config
191
+ end
192
+
193
+ runner = Liebre::Runner.new(engine: Liebre.engine)
194
+ runner.run
71
195
  ```
72
- rpc_request_timeout: 5
73
-
74
- consumers:
75
- some_consumer:
76
- class_name: MyConsumer
77
- rpc: false
78
- pool_size: 1
79
- num_threads: 4
80
- exchange:
81
- name: "consumer_exchange"
82
- type: "fanout"
83
- opts:
84
- durable: false
85
- queue:
86
- name: "consumer_queue"
87
- opts:
88
- durable: false
89
- bind:
90
- routing_key: #key a string or an array of strings
91
- - key_1
92
- - key_2
93
-
94
- some_rpc:
95
- class_name: MyRPC
96
- rpc: true
97
- connection_name: rpc
98
- pool_size: 1
99
- exchange:
100
- name: "rpc_exchange"
101
- type: "fanout"
102
- opts:
103
- durable: false
104
- queue:
105
- name: "rpc_queue"
106
- opts:
107
- durable: false
108
-
109
- publishers:
110
- some_publisher:
111
- exchange:
112
- name: "consumer_exchange"
113
- type: "fanout"
114
- opts:
115
- durable: false
116
- rpc_publisher:
117
- connection_name: rpc
118
- exchange:
119
- name: "rpc_exchange"
120
- type: "fanout"
121
- opts:
122
- durable: false
196
+
197
+ The example above starts the runner with the default engine
198
+
199
+ It sets up some system signals in order to respond gracefully to unix `kill`
200
+ and sleeps the main thread forever.
201
+
202
+ The previous pattern is so common that a shortcut is provided:
203
+
204
+ ```ruby
205
+ require 'liebre'
206
+
207
+ Liebre.configure do |config|
208
+ # some config
209
+ end
210
+
211
+ Liebre.start
123
212
  ```
124
213
 
125
- ### Consumers
214
+ ### Start partially
126
215
 
127
- An entry for each consumer in your app, consumer options:
128
- * `class_name` (mandatory): The class that will be invoked everytime a message is received
129
- * `rpc` is a flag to specify the consumer behaviour (default false)
130
- * `connection_name`: the name of the connection to use (default `default`)
131
- * `pool_size` of the connection channel (default 1)
132
- * `num_threads`: number of consumers of the queue (default 1)
133
- * `exchange` a hash of options:
134
- * `name`: the exchange name
135
- * `type`: the exchange type (fanout, direct or topic)
136
- * `opts`: options hash to pass to bunny exchange function
137
- * `queue` a hash of options:
138
- * `name`: the queue name
139
- * `opts`: options hash to pass to bunny queue function
140
- * `bind` a hash of options (optional):
141
- * `routing_key`: the binding routing key, it can be a single string or an array of strings
216
+ Imagine the following setup: You have an application that may be started as a rack
217
+ application and also as a worker with no rack interface to consume from rabbitmq as
218
+ a background tasks system.
142
219
 
143
- ### Publishers
220
+ When you start the application with rack you may use publishers or rpc clients, but
221
+ you don't want to start the consumers and rpc servers.
144
222
 
145
- An entry for each exchange you want to publish to, options:
146
- * `connection_name`: the name of the connection to use (default `default`)
147
- * `exchange` a hash of options:
148
- * `name`: the exchange name
149
- * `type`: the exchange type (fanout, direct or topic)
150
- * `opts`: options hash to pass to bunny exchange function
223
+ When you start the application with `Liebre::Runner` you want all actors to start.
151
224
 
152
- ### Other configurations
225
+ To handle this kind of scenarios `Liebre` keeps track of the already started actors
226
+ and prevents them to start twice on a given engine.
153
227
 
154
- `rpc_request_timeout`: set the timeout for an RPC request
228
+ A solution to this case may be the following:
155
229
 
156
- ### Change default paths and set env and Logger
230
+ Start all publishers and consumers on an initialized that will be shared between
231
+ both ways to start the application:
157
232
 
158
- You can change these defaults in an initializer with something like:
233
+ ```ruby
234
+ require 'liebre'
159
235
 
236
+ Liebre.configure do |config|
237
+ # some config
238
+ end
239
+
240
+ Liebre.engine.start(only: [:publishers, :rpc_clients])
160
241
  ```
161
- Liebre::Config.config_path = "your/custom/path"
162
- Liebre::Config.connection_path = "your/custom/path"
163
242
 
164
- Liebre::Config.env = "production"
165
- Liebre::Config.logger = Logger.new(...)
243
+ This code will configure `Liebre` and starts all publishers and rpc clients.
244
+
245
+ When you start your application with `Liebre::Runner` you can do the following:
246
+
247
+ ```ruby
248
+ # run all your initializers including the one above
166
249
 
250
+ require 'liebre'
251
+ Liebre.start
167
252
  ```
168
253
 
169
- ## Usage
254
+ This code will start all actors, and publishers and rpc clients will only be started
255
+ once.
170
256
 
171
- There are 2 different consumer usages: `Consumer` and `RPC`.
257
+ ## Actors
172
258
 
173
- ### Consumer
259
+ Once a liebre engine has been started a `Liebre::Repository` has been populated with all actors
260
+ that can be fetched by name.
174
261
 
175
- You only need to create a class with this simple interface:
262
+ ```ruby
263
+ # ... start the engine
264
+ repo = engine.repo
176
265
 
266
+ publisher_1 = repo.publisher(:my_publisher)
267
+ publisher_2 = repo.publisher(:my_other_pub)
268
+ consumer = repo.consumer(:my_consumer)
269
+ rpc_client = repo.rpc_server(:my_rpc_server)
270
+ rpc_server = repo.rpc_server(:my_rpc_server)
177
271
  ```
178
- class MyConsumer
179
-
180
- def initialize payload, meta
181
- @payload = payload #the content of the message
182
- @meta = meta #the meta information (also called properties)
183
- end
184
272
 
185
- def call
186
- #do your stuff here
187
- #return :ack to anknowledge the message,
188
- #return :reject to requeue it
189
- #or return :error to send it to the dead-letter-exchange
190
- :ack
191
- end
273
+ ### Publisher
274
+
275
+ A publisher is an actor that declares an exchange on startup and provides a method to publish
276
+ messages to that exchange.
277
+
278
+ The `:resources` section of the actor's configuration requires the exchange specification:
279
+
280
+ ```ruby
281
+ require 'liebre'
192
282
 
283
+ Liebre.configure do |config|
284
+ config.adapter = Liebre::Adapter::Bunny
285
+ config.connections = {default: {}}
286
+
287
+ config.actors = {
288
+ publishers: {
289
+ my_pub: {
290
+ connection: :default,
291
+ resources: {
292
+ exchange: {name: "foo",
293
+ type: "direct",
294
+ opts: {durable: true}}
295
+ }
296
+ }
297
+ }
298
+ }
193
299
  end
300
+
301
+ Liebre.engine.start
302
+
303
+ publisher = Liebre.repo.publisher(:my_pub)
304
+ publisher.publish("some_data")
305
+ publisher.publish("more_data", :routing_key => "my_key")
194
306
  ```
195
307
 
196
- Every time a message is received, a new instance of this class will be created.
308
+ The exchange specification:
197
309
 
198
- ### RPC
310
+ * `:name` - The name of the exchange
311
+ * `:type` - The exchange type (`"direct"`, `"fanout"`, etc)
312
+ * `:opts` - (defaults to `{}`) The exchange options
199
313
 
200
- You only need to create a class with this simple interface:
314
+ Once started the `#publish` method can be used to send messages to the configured
315
+ exchange.
316
+
317
+ ### Consumer
318
+
319
+ A consumer is an actor that declares an exchange, a queue and binds them on startup.
320
+ It owns a thread pool to handle consumed messages.
321
+
322
+ After declaration it subscribes to the queue and starts consuming messages. Once a message
323
+ is consumed a handler for that message is started in a thread of the pool.
324
+
325
+ The handler class must implement the following interface:
326
+
327
+ * `#initialize` receives 3 arguments: `payload`, `meta`, and `callback`.
328
+ * `#call`
329
+
330
+ The handler class is initialized with that 3 arguments:
331
+
332
+ * `payload` - The actual content of the message
333
+ * `meta` - Message metadata that includes the headers and other information (depends on the adapter)
334
+ * `callback` - An object that responds to `#ack`, `#nack`, and `#reject`
335
+
336
+ ```ruby
337
+ class MyHandler
201
338
 
202
- ```
203
- class MyRPC
204
-
205
339
  def initialize payload, meta, callback
206
- @payload = payload #the content of the message
207
- @meta = meta #the meta information (also called properties)
208
- @callback = callback #a Proc that will publish an answer
340
+ @payload = payload
341
+ @callback = callback
209
342
  end
210
343
 
211
344
  def call
212
- #do your stuff here
213
- @callback.call("your response")
345
+ case payload
346
+ when "0" then zero()
347
+ when "1" then one()
348
+ else raise "unknown!"
349
+ end
214
350
  end
215
351
 
352
+ private
353
+
354
+ def zero
355
+ puts "yay!"
356
+ callback.ack()
357
+ end
358
+
359
+ def one
360
+ puts "wtf!"
361
+ callback.reject(requeue: false)
362
+ end
363
+
364
+ attr_reader :payload, :callback
365
+
216
366
  end
217
367
  ```
218
368
 
219
- Every time a message is received, a new instance of this class will be created.
369
+ The handler above will print `"yay!"` and ack the message when the payload is `"0"`,
370
+ print `"wtf!"` and reject the message when the payload is `"1"`, and will raise (and therefore
371
+ will be rejected by liebre) when the handler raises an error.
220
372
 
221
- There are 2 ways to publish a message: `enqueue` and `enqueue_and_wait`.
373
+ The `:resources` section of the actor's configuration requires the exchange and the queue,
374
+ and may include binding options.
222
375
 
223
- ### `enqueue`
376
+ ```ruby
377
+ require 'liebre'
378
+
379
+ Liebre.configure do |config|
380
+ config.adapter = Liebre::Adapter::Bunny
381
+ config.connections = {default: {}}
382
+
383
+ config.actors = {
384
+ consumers: {
385
+ my_con: {
386
+ connection: :default,
387
+ handler: MyHandler,
388
+ prefetch_count: 5,
389
+ pool_size: 5,
390
+ resources: {
391
+ exchange: {name: "foo",
392
+ type: "direct",
393
+ opts: {durable: true}},
394
+ queue: {name: "bar",
395
+ opts: {durable: true}},
396
+ bind: [{routing_key: "one"},
397
+ {routing_key: "other"}]
398
+ }
399
+ }
400
+ }
401
+ }
402
+ end
224
403
 
404
+ Liebre.engine.start
225
405
  ```
226
- publisher = Liebre::Publisher.new("some_publisher_name")
227
406
 
228
- publisher.enqueue "hello", :routing_key => "consumer_queue"
407
+ The exchange specification is the same as for the publisher.
229
408
 
230
- publisher.enqueue "bye", :routing_key => "consumer_queue"
409
+ The queue specification:
231
410
 
232
- ```
411
+ * `:name` - The name of the queue
412
+ * `:opts` - (defaults to `{}`) The queue options
233
413
 
234
- ### `enqueue_and_wait` (alias `rpc`)
414
+ The bind specification is optional and defaults to `{}`. `:bind` value may be:
235
415
 
236
- ```
237
- rpc_publisher = Liebre::Publisher.new("rpc_publisher")
416
+ * not present - the queue is bound to the exchange once with `{}` as options
417
+ * a hash - the queues is bound once to the exchange with that options
418
+ * a list of hashes - the queues is bound to the exchange once per hash of options
238
419
 
239
- response = publisher.enqueue_and_wait "hello", :routing_key => "consumer_queue"
420
+ The example above declares the exchange `"foo"`, declares the queue `"bar"`,
421
+ binds the queue to the exchange twice: one with the `"one"` routing key and
422
+ another with the `"other"` routing key).
240
423
 
241
- another_response = publisher.rpc "bye", :routing_key => "consumer_queue"
424
+ It starts a thread pool of 5 threads and starts consuming messages.
242
425
 
243
- ```
426
+ For each message the consumer receives a handler is instantiated and `#call` is called on
427
+ it in one of the threads of its pool.
244
428
 
245
- ### Installation and Execution
429
+ ### RPC Client
246
430
 
247
- Add the following to your Gemfile:
248
- ```
249
- gem "liebre", ">~ 0.1"
250
- ```
431
+ A RPC client is an actor that declares an exchange, and declares a temporary queue with
432
+ the options `exclusive`, and `auto_delete`.
251
433
 
252
- In your Raketask add:
434
+ After declaration it subscribes to the queue.
253
435
 
254
- ```
255
- require "liebre/tasks"
256
- ```
436
+ The `:resources` section of the rpc client configuration must specify the exchange.
257
437
 
258
- Then you just need to run:
259
- ```
260
- rake liebre:run
438
+ ```ruby
439
+ require 'liebre'
440
+
441
+ Liebre.configure do |config|
442
+ config.adapter = Liebre::Adapter::Bunny
443
+ config.connections = {default: {}}
444
+
445
+ config.actors = {
446
+ rpc_client: {
447
+ my_client: {
448
+ connection: :default,
449
+ resources: {
450
+ exchange: {name: "foo",
451
+ type: "direct",
452
+ opts: {durable: true}},
453
+ queue: {prefix: "client_responses"}
454
+ }
455
+ }
456
+ }
457
+ }
458
+ end
459
+
460
+ Liebre.engine.start
461
+
462
+ client = Liebre.repo.rpc_client(:my_pub)
463
+ client.request("data") # => rpc server response (or nil on timeout, 5 seconds by default)
464
+ client.request("data", routing_key: "bar") # => rpc server response (or nil on timeout, 5 seconds by default)
465
+ client.request("data", {}, 15) # => rpc server response (or nil on timeout after 15 seconds)
261
466
  ```
262
467
 
263
- Alternative way:
468
+ The exchange specification is the same as for the publisher.
469
+
470
+ The queue specification is optional and includes a prefix for the queue's name. The name of the
471
+ queue will be that prefix followed by a random token, for example: `"client_responses_q23jrefdzXw"`.
472
+
473
+ When a request is performed the client will block until the response is received or the timeout is reached.
474
+
475
+ ### RPC Server
476
+
477
+ A rpc server is an actor that declares an exchange, a queue and binds them on startup.
478
+ It owns a thread pool to handle requests.
479
+
480
+ After declaration it subscribes to the queue and starts consuming messages. Once a message
481
+ is consumed a handler for that message is started in a thread of the pool.
482
+
483
+ The handler class must implement the following interface:
484
+
485
+ * `#initialize` receives 3 arguments: `payload`, `meta`, and `callback`.
486
+ * `#call`
487
+
488
+ The handler class is initialized with that 3 arguments:
489
+
490
+ * `payload` - The actual content of the message
491
+ * `meta` - Message metadata that includes the headers and other information (depends on the adapter)
492
+ * `callback` - An object that responds to `#reply`
493
+
494
+ ```ruby
495
+ class MyHandler
496
+
497
+ def initialize payload, meta, callback
498
+ @payload = payload
499
+ @callback = callback
500
+ end
501
+
502
+ def call
503
+ case payload
504
+ when "0" then callback.reply("zero")
505
+ when "1" then callback.reply("one")
506
+ else raise "unknown!"
507
+ end
508
+ end
509
+
510
+ private
511
+
512
+ attr_reader :payload, :callback
513
+
514
+ end
264
515
  ```
516
+
517
+ The handler above will reply to the client with "zero" when payload is "0",
518
+ reply with "one" when the payload is "1", and will raise (and therefore not reply)
519
+ when the handler raises error.
520
+
521
+ The `:resources` section of the actor's configuration requires the exchange and the queue,
522
+ and may include binding options.
523
+
524
+ ```ruby
265
525
  require 'liebre'
266
526
 
267
- Liebre::Runner.new.start
268
- ```
527
+ Liebre.configure do |config|
528
+ config.adapter = Liebre::Adapter::Bunny
529
+ config.connections = {default: {}}
530
+
531
+ config.actors = {
532
+ rpc_servers: {
533
+ my_rpc_server: {
534
+ connection: :default,
535
+ handler: MyHandler,
536
+ prefetch_count: 5,
537
+ pool_size: 5,
538
+ resources: {
539
+ exchange: {name: "foo",
540
+ type: "direct",
541
+ opts: {durable: true}},
542
+ queue: {name: "bar",
543
+ opts: {durable: true}},
544
+ bind: [{routing_key: "one"},
545
+ {routing_key: "other"}]
546
+ }
547
+ }
548
+ }
549
+ }
550
+ end
551
+
552
+ Liebre.engine.start
553
+ ```
554
+
555
+ The exchange specification is the same as for the publisher.
556
+ The queue and bind specifications are the same as for the consumer.
557
+
558
+ The example above declares the exchange `"foo"`, declares the queue `"bar"`,
559
+ binds the queue to the exchange twice: one with the `"one"` routing key and
560
+ another with the `"other"` routing key).
561
+
562
+ It starts a thread pool of 5 threads and starts consuming requests.
563
+
564
+ For each message the consumer receives it instantiates and runs `#call` on
565
+ the new handler in one of the threads of its pool.