UNPKG

9.28 kBMarkdownView Raw
1# Messaging API supporting acknowledgements and request-response
2
3[![Build Status](http://ci.salemove.com/buildStatus/icon?job=freddy)](http://ci.salemove.com/job/freddy/)
4[![Code Climate](https://codeclimate.com/repos/52a1f75613d6374c030432d2/badges/f8f96e50aa9f57dfae00/gpa.png)](https://codeclimate.com/repos/52a1f75613d6374c030432d2/feed)
5
6## Usage
7
8### Ruby
9
10#### Setup
11
12* Inject the appropriate default logger and set up connection parameters:
13
14```ruby
15Freddy.setup(Logger.new(STDOUT), host: 'localhost', port: 5672, user: 'guest', pass: 'guest')
16```
17
18* Use Freddy to deliver and respond to messages:
19
20```ruby
21freddy = Freddy.new(logger = Freddy.logger)
22```
23
24 * by default the Freddy instance will reuse connections and queues for messaging, if you want to use a distinct tcp connection, response queue and timeout checking thread, then use
25
26```ruby
27freddy.use_distinct_connection
28```
29
30#### Destinations
31Freddy encourages but doesn't enforce the following protocol for destinations:
32
33* For sending messages to services:
34
35```
36<service_name>.<method_name>.<anything_else_you_need>.<...>
37```
38
39* For reporting errors:
40
41```
42<service_name>.<method_name>.'responder'|'producer'.'errors'
43```
44
45#### Delivering messages
46
47* Simply deliver a message:
48```ruby
49freddy.deliver(destination, message)
50```
51 * destination is the recipient of the message
52 * message is the contents of the message
53
54* Deliver a message expecting explicit acknowledgement
55```ruby
56freddy.deliver_with_ack(destination, message, timeout_seconds = 3) do |error|
57```
58
59 * If timeout_seconds pass without a response from the responder, then the callback is called with a timeout error.
60
61 * callback is called with one argument: a string that contains an error message if
62 * the message couldn't be sent to any responders or
63 * the responder negatively acknowledged(nacked) the message or
64 * the responder finished working but didn't positively acknowledge the message
65
66 * callback is called with one argument that is nil if the responder positively acknowledged the message
67 * note that the callback will not be called in the case that there is a responder who receives the message, but the responder doesn't finish processing the message or dies in the process.
68
69* Deliver expecting a response
70```ruby
71freddy.deliver_with_response(destination, message, timeout_seconds = 3) do |response, msg_handler|
72```
73
74 * If `timeout_seconds pass` without a response from the responder then the callback is called with the hash
75```ruby
76{ error: 'Timed out waiting for response' }
77```
78
79 * Callback is called with 2 arguments
80
81 * The parsed response
82
83 * The `MessageHandler`(described further down)
84
85* Synchronous deliver expecting response
86```ruby
87 response = freddy.deliver_with_response(destination, message, timeout_seconds = 3)
88```
89
90#### Responding to messages
91
92* Respond to messages while not blocking the current thread:
93```ruby
94freddy.respond_to destination do |message, msg_handler|
95```
96* Respond to message and block the thread
97```ruby
98freddy.respond_to_and_block destination do |message, msg_handler|
99```
100
101* The callback is called with 2 arguments
102
103 * the parsed message (note that in the message all keys are symbolized)
104 * the `MessageHandler` (described further down)
105
106#### The MessageHandler
107
108When responding to messages the MessageHandler is given as the second argument.
109```ruby
110freddy.respond_to destination do |message, msg_handler|
111```
112
113The following operations are supported:
114
115 * acknowledging the message
116```ruby
117msg_handler.ack(response = nil)
118```
119
120 * when the message was produced with `produce_with_response`, then the response is sent to the original producer
121
122 * when the message was produced with `produce_with_ack`, then only a positive acknowledgement is sent, the provided response is dicarded
123
124 * negatively acknowledging the message
125```ruby
126msg_handler.nack(error = "Couldn't process message")
127```
128
129 * when the message was produced with `produce_with_response`, then the following hash is sent to the original producer
130```ruby
131{ error: error }
132```
133
134 * when the message was produced with `produce_with_ack`, then the error (e.g negative acknowledgement) is sent to the original producer
135
136 * Getting additional properties of the message (shouldn't be necessary under normal circumstances)
137```ruby
138msg_handler.properties
139```
140
141#### Tapping into messages
142When it's necessary to receive messages but not consume them, consider tapping.
143
144```ruby
145freddy.tap_into pattern do |message, destination|
146```
147
148* `destination` refers to the destination that the message was sent to
149* Note that it is not possible to acknowledge or respond to message while tapping.
150* When tapping the following wildcards are supported in the `pattern` :
151 * `#` matching 0 or more words
152 * `*` matching exactly one word
153
154Examples:
155
156```ruby
157freddy.tap_into "i.#.free"
158```
159
160receives messages that are delivered to `"i.want.to.break.free"`
161
162```ruby
163freddy.tap_into "somebody.*.love"
164```
165
166receives messages that are delivered to `somebody.to.love` but doesn't receive messages delivered to `someboy.not.to.love`
167
168It is also possible to use the blocking version of `tap_into`:
169
170```ruby
171freddy.tap_into_and_block pattern, &callback do |message, destination|
172```
173
174#### The ResponderHandler
175
176When responding to a message or tapping the ResponderHandler is returned.
177```ruby
178responder_handler = freddy.respond_to ....
179```
180
181The following operations are supported:
182
183 * stop responding
184```ruby
185responder_handler.cancel
186```
187
188 * join the current thread to the responder thread
189```ruby
190responder_handler.join
191```
192
193 * delete the destination
194```ruby
195responder_handler.destroy_destination
196```
197
198 * Primary use case is in tests to not leave dangling destinations. It deletes the destination even if there are responders for the same destination in other parts of the system. Use with caution in production code.
199
200
201#### Notes about concurrency
202
203The underlying bunny implementation uses 1 responder thread by default. This means that if there is a time-consuming process or a sleep call in a responder then other responders will not receive messages concurrently.
204
205This is especially devious when using `deliver_with_response` in a responder because `deliver_with_response` creates a new anonymous responder which will not receive the response if the parent responder uses a sleep call.
206
207To resolve this problem *freddy* uses 4 responder threads by default (configurable by `responder_thread_count`). Note that this means that ordered message processing is not guaranteed by default. Read more from <http://rubybunny.info/articles/concurrency.html>.
208
209***
210
211### Node.js
212
213Since 0.2.1 freddy for node.js uses promises, more info at
214
215https://github.com/kriskowal/q (Pay close attention the the section *The End*)
216
217
218#### Setup
219```coffee
220Freddy = require 'freddy'
221Freddy.addErrorListener(listener)
222Freddy.connect('amqp://guest:guest@localhost:5672', logger).done (freddy) ->
223 continueWith(freddy)
224, (error) ->
225 doSthWithError(error)
226```
227
228#### Delivering messages
229```coffee
230freddy.deliver(destination, message, options = {})
231
232freddy.deliverWithAck(destination, message, callback)
233
234freddy.deliverWithResponse(destination, message, callback)
235```
236
237* The previous 2 can be used with additional options also:
238```coffee
239freddy.deliverWithAckAndOptions(destination, message, options, callback)
240
241freddy.deliverWithResponseAndOptions(destination, message, options, callback)
242```
243
244 The options include:
245
246 * `timeout`: In seconds, defaults to 3.
247 * `suppressLog`: Avoid logging the message contents
248
249
250#### Responding to messages
251```coffee
252freddy.respondTo(destination, callback)
253```
254
255* `respondTo` returns a promise which resolved with the ResponderHandler
256
257```coffee
258freddy.respondTo(destination, callback)
259.done (responderHandler) ->
260 doSthWith(responderHandler.cancel())
261```
262
263#### The MessageHandler
264No differences to ruby spec
265
266#### Tapping into messages
267
268```coffee
269responderHandler = freddy.tapInto(pattern, callback)
270```
271
272No other differences to ruby spec, blocking variant is not provided for obvious reasons.
273
274#### The ResponderHandler
275
276* When cancelling the responder returns a promise, no messages will be received after the promise resolves.
277
278```coffee
279freddy.respondTo(destination, (->))
280.then (responderHandler) ->
281 responderHandler.cancel()
282.done ->
283 freddy.deliver(destination, easy: 'go') #will not be received
284```
285* The join method is not provided for obvious reasons.
286
287## Development
288
289* Use RSpec and mocha, make sure the tests pass.
290* Don't leak underlying messaging protocol internals.
291
292## Credits
293
294**freddy** was originally written by [Urmas Talimaa] as part of SaleMove development team.
295
296![SaleMove Inc. 2012][SaleMove Logo]
297
298**freddy** is maintained and funded by [SaleMove, Inc].
299
300The names and logos for **SaleMove** are trademarks of SaleMove, Inc.
301
302## License
303
304**freddy** is Copyright © 2013 SaleMove Inc. It is free software, and may be redistributed under the terms specified in the [Apache License].
305
306[Urmas Talimaa]: https://github.com/urmastalimaa?source=c "Urmas"
307[SaleMove, Inc]: http://salemove.com/ "SaleMove Website"
308[SaleMove Logo]: http://app.salemove.com/assets/logo.png "SaleMove Inc. 2012"
309[Apache License]: http://choosealicense.com/licenses/apache/ "Apache License"