bmabey-rosetta_queue 0.2.0 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/History.txt +6 -3
  2. data/README.rdoc +1 -193
  3. data/Rakefile +7 -1
  4. data/cucumber.yml +1 -1
  5. data/examples/sample_amqp_consumer.rb +13 -3
  6. data/examples/sample_amqp_fanout_consumer.rb +6 -3
  7. data/examples/sample_amqp_fanout_producer.rb +3 -2
  8. data/examples/sample_amqp_producer.rb +2 -1
  9. data/features/filtering.feature +13 -13
  10. data/features/messaging.feature +28 -20
  11. data/features/step_definitions/common_messaging_steps.rb +54 -17
  12. data/features/step_definitions/filtering_steps.rb +2 -2
  13. data/features/step_definitions/point_to_point_steps.rb +19 -9
  14. data/features/step_definitions/publish_subscribe_steps.rb +22 -8
  15. data/features/support/env.rb +2 -0
  16. data/features/support/sample_consumers.rb +6 -6
  17. data/lib/rosetta_queue.rb +1 -0
  18. data/lib/rosetta_queue/adapter.rb +6 -6
  19. data/lib/rosetta_queue/adapters/amqp.rb +6 -3
  20. data/lib/rosetta_queue/adapters/amqp_evented.rb +22 -22
  21. data/lib/rosetta_queue/adapters/amqp_synch.rb +42 -36
  22. data/lib/rosetta_queue/adapters/base.rb +3 -3
  23. data/lib/rosetta_queue/adapters/beanstalk.rb +5 -5
  24. data/lib/rosetta_queue/adapters/fake.rb +5 -5
  25. data/lib/rosetta_queue/adapters/null.rb +9 -9
  26. data/lib/rosetta_queue/adapters/stomp.rb +25 -12
  27. data/lib/rosetta_queue/base.rb +2 -2
  28. data/lib/rosetta_queue/consumer.rb +4 -4
  29. data/lib/rosetta_queue/consumer_managers/base.rb +8 -6
  30. data/lib/rosetta_queue/consumer_managers/evented.rb +5 -5
  31. data/lib/rosetta_queue/consumer_managers/threaded.rb +4 -4
  32. data/lib/rosetta_queue/core_ext/string.rb +3 -3
  33. data/lib/rosetta_queue/core_ext/time.rb +20 -0
  34. data/lib/rosetta_queue/destinations.rb +6 -6
  35. data/lib/rosetta_queue/filters.rb +8 -8
  36. data/lib/rosetta_queue/logger.rb +2 -2
  37. data/lib/rosetta_queue/message_handler.rb +10 -4
  38. data/lib/rosetta_queue/producer.rb +2 -2
  39. data/lib/rosetta_queue/spec_helpers/hash.rb +3 -3
  40. data/lib/rosetta_queue/spec_helpers/helpers.rb +8 -8
  41. data/lib/rosetta_queue/spec_helpers/publishing_matchers.rb +26 -26
  42. data/spec/rosetta_queue/adapter_spec.rb +27 -27
  43. data/spec/rosetta_queue/adapters/amqp_synchronous_spec.rb +21 -1
  44. data/spec/rosetta_queue/adapters/beanstalk_spec.rb +3 -3
  45. data/spec/rosetta_queue/adapters/fake_spec.rb +6 -6
  46. data/spec/rosetta_queue/adapters/null_spec.rb +5 -5
  47. data/spec/rosetta_queue/adapters/shared_adapter_behavior.rb +4 -4
  48. data/spec/rosetta_queue/adapters/shared_fanout_behavior.rb +1 -1
  49. data/spec/rosetta_queue/adapters/stomp_spec.rb +39 -18
  50. data/spec/rosetta_queue/consumer_managers/evented_spec.rb +6 -6
  51. data/spec/rosetta_queue/consumer_managers/shared_manager_behavior.rb +3 -3
  52. data/spec/rosetta_queue/consumer_managers/threaded_spec.rb +5 -5
  53. data/spec/rosetta_queue/consumer_spec.rb +13 -13
  54. data/spec/rosetta_queue/core_ext/string_spec.rb +3 -3
  55. data/spec/rosetta_queue/destinations_spec.rb +8 -8
  56. data/spec/rosetta_queue/filters_spec.rb +16 -16
  57. data/spec/rosetta_queue/producer_spec.rb +15 -15
  58. data/spec/rosetta_queue/shared_messaging_behavior.rb +6 -6
  59. metadata +3 -2
@@ -1,17 +1,20 @@
1
1
  == 0.2.x (git)
2
2
 
3
3
  === New features
4
+ * Added core time extension when ActiveSupport not present. (Chris Wyckoff)
4
5
  === Bufixes
6
+ * Closing the connection after publishing message now instead of unsubscribing. (Chris Wyckoff)
7
+
5
8
 
6
9
  == 0.2.0
7
10
  === New features
8
- * Synchronous AMQP adapter
11
+ * Synchronous AMQP adapter (Chris Wyckoff - Lead Media Partners)
9
12
  * Uses celldee's synchronous AMQP client, 'Bunny'.
10
- * Added .delete method to Consumer class
13
+ * Added .delete method to Consumer class (Chris Wyckoff - Lead Media Partners)
11
14
  * Allows user to purge a queue.
12
15
  * Works for AMQP adapters only.
13
16
  === Bufixes
14
- * client :ack now working.
17
+ * client :ack now working. (Chris Wyckoff - Lead Media Partners)
15
18
 
16
19
  == 0.1.4
17
20
 
@@ -2,202 +2,10 @@
2
2
 
3
3
  Rosetta Queue is a messaging gateway API with adapters for many messaging systems available in Ruby. Messaging systems can be easily switched out with a small configuration change. Code for testing on the object and application level is also provided.
4
4
 
5
- The adapters provided currently are for stomp, amqp, and beanstalk. We would like to add adapters for other messaging gateways. The stomp adapter has been used in production along side with Apache's ActiveMQ. The amqp adapter currently works along side RabbitMQ and passes the acceptance tests but as of yet has not been used in production.
6
-
5
+ Documentation can be found on the wiki.[http://wiki.github.com/bmabey/rosetta_queue]
7
6
 
8
7
  == Authors
9
8
 
10
9
  Ben Mabey [http://github.com/bmabey]
11
10
 
12
11
  Chris Wyckoff [http://github.com/cwyckoff]
13
-
14
-
15
- == Quick Tutorial
16
-
17
- Note: The API will most likely change until we reach 1.0. We will be moving to a more concise API (i.e. queue(:test_queue) << message, etc...) We will also be changing how exceptions are handled.
18
-
19
- When using Rosetta Queue in an application you will need to configure the queues, adapters, and filters (optional). These configurations should be placed in a file that gets loaded once when your program starts. If you are using Rails then a good place for this is config/initializers/rosetta_queue.rb.
20
-
21
-
22
- To set up destinations for producing and consuming messages:
23
-
24
- RosettaQueue::Destinations.define do |queue|
25
- queue.map :test_queue, '/queue/my_test_queue'
26
- end
27
-
28
-
29
- Defining your adapter:
30
-
31
- RosettaQueue::Adapter.define do |a|
32
- a.user = ""
33
- a.password = ""
34
- a.host = "localhost"
35
- a.port = 61613
36
- a.type = "stomp"
37
- end
38
-
39
-
40
- Define a logger for Rosetta Queue to use. The logger should be a standard ruby logger:
41
-
42
- RosettaQueue.logger = Logger.new('/my_project/rosetta_queue.log')
43
-
44
-
45
- You can optionally set up filters that are applied to all messages that are sent and received. For example, if you want to use hashes as messages and serialize them as JSON the following filters (along with ActiveSupport) would accomplish this:
46
-
47
- RosettaQueue::Filters.define do |filter_for|
48
- filter_for.receiving { |message| ActiveSupport::JSON.decode(message) }
49
- filter_for.sending { |hash| hash.to_json }
50
- end
51
-
52
-
53
- To publish a message:
54
-
55
- message = {"hello" => "world!"} # Assuming you have a filter setup
56
- RosettaQueue::Producer.publish(:test_queue, message)
57
-
58
- When consuming messages from a queue you will generally want to create a consumer to handle the messages:
59
-
60
- class TestConsumer
61
- include RosettaQueue::MessageHandler
62
-
63
- subscribes_to :vendor_status
64
- options :persistent => true
65
-
66
- def on_message(message)
67
- puts "We consumed a message: #{message.inspect}"
68
- end
69
-
70
- end
71
-
72
- To fire the consumers up you will want to run a separate process create a manager with all of your consumers.
73
-
74
- require 'rosetta_queue'
75
- require 'rosetta_queue/consumer_managers/threaded'
76
- require 'test_consumer'
77
-
78
- RosettaQueue::ThreadedManager.create do |m|
79
- m.add(TestConsumer.new)
80
- m.start
81
- end
82
-
83
-
84
- It is recommended that you set your adapter to the 'null' adapter for your specs and then include RosettaQueue::Matchers in any example group you are needing to specify behaviour with RosettaQueue. The matchers currently switch out the null adapter for the fake adapter to verify the behaviour. All the matchers for the unit tests are lambda based, like so:
85
-
86
- lambda { model.save }.should publish("foo", :to => :test_queue).
87
-
88
- Please look at the publishing matchers for more information. For examples on how to write acceptance tests for your Rosetta Queue's code please see RosettaQueue's own Cucumber features and read this {blog post}[http://www.benmabey.com/2009/02/17/using-cucumber-to-integrate-distributed-systems-and-test-messaging/].
89
-
90
-
91
- == Adapters
92
-
93
- === AMQP
94
-
95
- RosettaQueue comes with two AMQP adapters: a synchronous adapter and an evented one. The former uses the synchronous AMQP client "Bunny" by celldee [http://github.com/celldee/bunny] and the latter, the evented AMQP client by Aman Gupta [http://github.com/tmm1/amqp].
96
-
97
- ==== Set Up
98
-
99
- Install Erlang, unless already installed, and the RabbitMQ messaging broker. Mac OSX users can install from ports. Linux users should use their package manager or choice. One installed, you can launch rabbitmq by running
100
-
101
- rabbitmq-server
102
-
103
- or
104
-
105
- rabbitmqctl start_app
106
-
107
-
108
- ==== Configuration
109
-
110
- If you are using an AMQP adapter, there are some important rules when defining the adapter and mapping your destinations. Two of the basic building blocks of AMQP are queues and exchanges. Queues bind to exchanges in several different ways. A can queue bind to an exchange and requests messages that match a specific routing key, which is called 'direct exchange' and is analogous to the 'point-to-point' messaging pattern. Alternately, queues can bind to an exchange to receive all messages sent to that exchange; this is called 'fanout exchange' and is analogous to the 'publish-subscribe' pattern. So, when you want to define a simple 'direct-exchange' queue, your queue name must begin with the term "queue".
111
-
112
- RosettaQueue::Destinations.define do |queue|
113
- queue.map :test_queue, 'queue.my_test_queue'
114
- end
115
-
116
- And, when you want to define a queue that will bind to a 'fanout-exchange', your queue name must begin with the term "fanout".
117
-
118
- RosettaQueue::Destinations.define do |queue|
119
- queue.map :test_queue, 'fanout.my_test_queue'
120
- end
121
-
122
- Finally, when defining your adapter, the synchronous AMQP adapter should be defined as :*amqp_synch*, and the evented adapter as :*amqp_evented*.
123
-
124
- ==== Evented AMQP adapter
125
-
126
- When publishing and subscribing using the evented AMQP adapter, you need to wrap that code in an event machine run block. For example, publishing a message might look like this:
127
-
128
- EM.run do
129
- Em.add_periodic_timer(1) do
130
- RosettaQueue::Producer.publish(:foo, "Hello World!")
131
- end
132
- end
133
-
134
- And a sample consumer might look like this:
135
-
136
- EM.run do
137
- RosettaQueue::Consumer.new(MyMessageHandler.new).receive
138
- end
139
-
140
- Although, if you are using the evented AMQP adapter to consume messages you could simply wrap your consumers in RosettaQueue's EventedManager:
141
-
142
- RosettaQueue::EventedManager.create do |m|
143
- m.add(MyFirstMessageHandler.new)
144
- m.add(MySecondMessageHandler.new)
145
- m.add(MyThirdMessageHandler.new)
146
-
147
- m.start
148
- end
149
-
150
-
151
- === Beanstalk
152
-
153
- You should set up and run a local instance of beanstalk for your
154
- tests.
155
-
156
- ==== beanstalkd
157
-
158
- Mac OSX users can install beanstalkd from ports. Linux users should
159
- check their package manager of choice. This is probably the optimal
160
- method.
161
-
162
- Diehards can clone git://github.com/kr/beanstalkd and build it from
163
- source. It is nontrivial.
164
-
165
- Once installed, you can launch beanstalk by running
166
-
167
- beanstalkd -p 11300
168
-
169
- This will make it listen on localhost on the standard port 11300,
170
- which the specs use. You might also want to daemonize beanstalk with
171
- the -d option but for testing this is not recommended, as the queue
172
- can get stuck with old messages, which will break your specs.
173
-
174
- ==== beanstalk-client
175
-
176
- You will also need the Ruby beanstalk-client gem.
177
-
178
- sudo gem install beanstalk-client
179
-
180
-
181
-
182
- == How to contribute
183
- ----------------------------------------------------------------
184
- Gems you will need:
185
- cucumber, rspec, yaml, stomp, amqp, bunny, beanstalk-client
186
-
187
- You should be able to run the rspec code examples (specs) without any brokers running with autospec or 'rake spec'.
188
-
189
- To run the cucumber features you will need the a messaging system setup that can speak stomp and AMQP. We have been using the following brokers in testing:
190
-
191
- === Apache ActiveMQ (for the stomp adapter)
192
- Go to http://activemq.apache.org/download.html to download the latest version, and see http://activemq.apache.org/getting-started.html for installation and configuration instructions.
193
- The stomp client and features should work with ApacheMQ out of the box. If you are running any ApacheMQ servers in production on your network you will want to disable the multicast autodiscovery in conf/activemq.xml. (Around lines 56 and 98.)
194
-
195
- === RabbitMQ (for the amqp adapter)
196
- Download the right tar from here: http://www.rabbitmq.com/download.html and follow the installation directions that comes with it.
197
- The features rely on a user of 'rosetta' being setup with the password of 'password' and added to the default virtualhost. You can set them up like so:
198
-
199
- rabbitmqctl add_user rosetta password
200
- rabbitmqctl map_user_vhost rosetta /
201
-
202
-
203
-
data/Rakefile CHANGED
@@ -30,4 +30,10 @@ rescue LoadError
30
30
  end
31
31
 
32
32
  desc "Default task runs specs"
33
- task :default => [:spec]
33
+ task :default => [:spec]
34
+
35
+ desc 'Removes trailing whitespace'
36
+ task :whitespace do
37
+ sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
38
+ end
39
+
@@ -1 +1 @@
1
- default: features --format pretty --require features
1
+ default: features --format pretty --require features --tags ~@unreliable
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require File.dirname(__FILE__) + '/../init.rb'
3
3
  require File.expand_path(File.dirname(__FILE__) + '/../lib/rosetta_queue/consumer_managers/threaded')
4
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
4
5
 
5
6
  module RosettaQueue
6
7
 
@@ -12,8 +13,8 @@ module RosettaQueue
12
13
  end
13
14
 
14
15
  Destinations.define do |dest|
15
- dest.map :foo, "queuep.foo"
16
- end
16
+ dest.map :foo, "queue.foo"
17
+ end
17
18
 
18
19
  class MessageHandlerFoo
19
20
  include RosettaQueue::MessageHandler
@@ -27,9 +28,18 @@ module RosettaQueue
27
28
 
28
29
  end
29
30
 
31
+ # consumer = RosettaQueue::Consumer.new(MessageHandlerFoo.new)
32
+ # # Thread.new(consumer) do |cons|
33
+ # consumer.receive
34
+ # # end
35
+ # puts "sleeping for 10"
36
+ # sleep 10
37
+ # puts "shutting consumer down"
38
+ # consumer.disconnect
39
+
30
40
  ThreadedManager.create do |m|
31
41
  m.add MessageHandlerFoo.new
32
42
  m.start
33
- end
43
+ end
34
44
 
35
45
  end
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require File.dirname(__FILE__) + '/../init.rb'
3
3
  require File.expand_path(File.dirname(__FILE__) + '/../lib/rosetta_queue/consumer_managers/threaded.rb')
4
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
4
5
 
5
6
  module RosettaQueue
6
7
 
@@ -8,12 +9,12 @@ module RosettaQueue
8
9
  a.user = "rosetta"
9
10
  a.password = "password"
10
11
  a.host = "localhost"
11
- a.type = 'amqp'
12
+ a.type = 'amqp_synch'
12
13
  end
13
14
 
14
15
  Destinations.define do |dest|
15
16
  dest.map :foo, "fanout.foo"
16
- end
17
+ end
17
18
 
18
19
  class MessageHandlerFoo
19
20
  include RosettaQueue::MessageHandler
@@ -23,6 +24,7 @@ module RosettaQueue
23
24
 
24
25
  def on_message(msg)
25
26
  puts "FOO received message: #{msg}"
27
+ ack
26
28
  end
27
29
 
28
30
  end
@@ -35,6 +37,7 @@ module RosettaQueue
35
37
 
36
38
  def on_message(msg)
37
39
  puts "BAR received message: #{msg}"
40
+ ack
38
41
  end
39
42
  end
40
43
 
@@ -44,6 +47,6 @@ module RosettaQueue
44
47
  m.add MessageHandlerFoo.new
45
48
  m.add MessageHandlerBar.new
46
49
  m.start
47
- end
50
+ end
48
51
 
49
52
  end
@@ -1,16 +1,17 @@
1
1
  require 'rubygems'
2
2
  require File.dirname(__FILE__) + '/../init.rb'
3
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
3
4
 
4
5
  RosettaQueue::Adapter.define do |a|
5
6
  a.user = "rosetta"
6
7
  a.password = "password"
7
8
  a.host = "localhost"
8
- a.type = "amqp"
9
+ a.type = "amqp_synch"
9
10
  end
10
11
 
11
12
  RosettaQueue::Destinations.define do |dest|
12
13
  dest.map :foo, "fanout.foo"
13
- end
14
+ end
14
15
 
15
16
 
16
17
  RosettaQueue::Producer.publish(:foo, "hello there")
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require File.dirname(__FILE__) + '/../init.rb'
3
+ RosettaQueue.logger = Logger.new(File.expand_path(File.dirname(__FILE__) + '/../../log/rosetta_queue.log'))
3
4
 
4
5
  RosettaQueue::Adapter.define do |a|
5
6
  a.user = "rosetta"
@@ -10,6 +11,6 @@ end
10
11
 
11
12
  RosettaQueue::Destinations.define do |dest|
12
13
  dest.map :foo, "queue.foo"
13
- end
14
+ end
14
15
 
15
16
  RosettaQueue::Producer.publish(:foo, "hello there")
@@ -6,26 +6,26 @@ Feature: Message Filtering
6
6
 
7
7
  Scenario Outline: receiving filter
8
8
  Given RosettaQueue is configured for '<Adapter>'
9
- And a point-to-point destination is set
9
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
10
10
  And a receiving filter is defined to prepend 'Foo' to all messages
11
- And the message "Hello World" is published to queue "foo"
12
- When the message on "foo" is consumed
11
+ And the message 'Hello World' is published to queue '<Queue>'
12
+ When the message on '<Queue>' is consumed
13
13
  Then the consumed message should equal "Foo Hello World"
14
14
 
15
15
  Examples:
16
- | Adapter |
17
- | amqp_synch |
18
- | stomp |
16
+ | Adapter | Queue | QueueAddress |
17
+ | amqp_synch | foo | queue.foo |
18
+ | stomp | foo | /queue/foo |
19
19
 
20
- Scenario Outline: sending filter
20
+ Scenario Outline: sending filter
21
21
  Given RosettaQueue is configured for '<Adapter>'
22
- And a point-to-point destination is set
22
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
23
23
  And a sending filter is defined to prepend 'Foo' to all messages
24
- And the message "Hello World" is published to queue "foo"
25
- When the message on "foo" is consumed
24
+ And the message 'Hello World' is published to queue '<Queue>'
25
+ When the message on '<Queue>' is consumed
26
26
  Then the consumed message should equal "Foo Hello World"
27
27
 
28
28
  Examples:
29
- | Adapter |
30
- | amqp_synch |
31
- | stomp |
29
+ | Adapter | Queue | QueueAddress |
30
+ | amqp_synch | foo | queue.foo |
31
+ | stomp | foo | /queue/foo |
@@ -4,37 +4,45 @@ Story: Producing and Consuming
4
4
  I want to publish and consume point-to-point using various messaging protocols
5
5
  So that I can reliably integrate my systems with a message broker
6
6
 
7
+ Background:
8
+ Given consumer logs have been cleared
9
+
7
10
  Scenario Outline: Point-to-Point
8
11
  Given RosettaQueue is configured for '<Adapter>'
9
- And a point-to-point destination is set
10
- When a message is published to queue 'foo'
11
- Then the message should be consumed
12
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
13
+ And a consumer is listening to queue '<Queue>'
14
+ When a message is published to '<Queue>'
15
+ Then the message should be consumed from '<Queue>'
12
16
 
13
17
  Examples:
14
- | Adapter |
15
- | amqp_synch |
16
- | stomp |
17
- | beanstalk |
18
+ | Adapter | Queue | QueueAddress |
19
+ | amqp_synch | foo | queue.foo |
20
+ | amqp_evented | bar | queue.bar |
21
+ | stomp | baz | /queue/baz |
22
+ #| beanstalk | baz | baz |
18
23
 
19
24
  Scenario Outline: Delete queue
20
25
  Given RosettaQueue is configured for '<Adapter>'
21
- And a point-to-point destination is set
22
- When a message is published to queue '<Queue>'
26
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
27
+ When a message is published to '<Queue>'
23
28
  And the queue '<Queue>' is deleted
24
29
  Then the queue '<Queue>' should no longer exist
25
30
 
26
31
  Examples:
27
- | Adapter | Queue |
28
- | amqp_synch | foo |
32
+ | Adapter | Queue | QueueAddress |
33
+ | amqp_synch | foo | queue.foo |
29
34
 
30
35
 
31
- # Scenario Outline: Publish-Subscribe
32
- # Given RosettaQueue is configured for '<Adapter>'
33
- # And a '<PublishSubscribe>' destination is set
34
- # When a message is published to 'foobar'
35
- # Then multiple messages should be consumed from the topic
36
+ @unreliable
37
+ Scenario Outline: Publish-Subscribe
38
+ Given RosettaQueue is configured for '<Adapter>'
39
+ And a destination is set with queue '<Queue>' and queue address '<QueueAddress>'
40
+ And multiple consumers are listening to queue '<Queue>'
41
+ When a message is published to '<Queue>'
42
+ Then multiple messages should be consumed from '<Queue>'
36
43
 
37
- # Examples:
38
- # | Adapter | PublishSubscribe |
39
- # | amqp_synch | fanout |
40
- # | stomp | topic |
44
+ Examples:
45
+ | Adapter | QueueAddress | Queue |
46
+ | amqp_synch | fanout.baz | baz |
47
+ | amqp_evented | queue.foo | foo |
48
+ | stomp | /topic/foo | bar |