euston-rabbitmq 1.0.1 → 1.0.2

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 (45) hide show
  1. data/Gemfile +2 -0
  2. data/Rakefile +0 -21
  3. data/euston-rabbitmq.gemspec +26 -37
  4. data/lib/euston-rabbitmq/{bindings → euston}/command_handler_binder.rb +2 -2
  5. data/lib/euston-rabbitmq/{errors.rb → euston/errors.rb} +1 -1
  6. data/lib/euston-rabbitmq/{bindings → euston}/event_handler_binder.rb +4 -4
  7. data/lib/euston-rabbitmq/{exchanges.rb → euston/exchanges.rb} +1 -1
  8. data/lib/euston-rabbitmq/{bindings → euston}/handler_binder.rb +2 -1
  9. data/lib/euston-rabbitmq/{queues.rb → euston/queues.rb} +1 -1
  10. data/lib/euston-rabbitmq/{subscriptions/retriable_subscription.rb → euston/retrying_subscription.rb} +17 -16
  11. data/lib/euston-rabbitmq/rabbitmq_client/queue.rb +70 -0
  12. data/lib/euston-rabbitmq/rabbitmq_client/reactive_message.rb +15 -0
  13. data/lib/euston-rabbitmq/{constant_loader.rb → reflection/constant_loader.rb} +0 -0
  14. data/lib/euston-rabbitmq/{handler_finder.rb → reflection/handler_finder.rb} +0 -0
  15. data/lib/euston-rabbitmq/{handler_reference.rb → reflection/handler_reference.rb} +0 -0
  16. data/lib/euston-rabbitmq/version.rb +1 -1
  17. data/lib/euston-rabbitmq.rb +8 -22
  18. data/spec/euston/command_handler_binder_spec.rb +24 -0
  19. data/spec/euston/event_handler_binder_spec.rb +36 -0
  20. data/spec/euston/exchanges_spec.rb +27 -0
  21. data/spec/euston/queues_spec.rb +24 -0
  22. data/spec/euston/retrying_subscription_spec.rb +74 -0
  23. data/spec/rabbitmq_client/queue_spec.rb +69 -0
  24. data/spec/{constant_loader_spec.rb → reflection/constant_loader_spec.rb} +3 -1
  25. data/spec/{handler_finder_spec.rb → reflection/handler_finder_spec.rb} +3 -1
  26. data/spec/spec_helper.rb +19 -52
  27. data/spec/support/filters.rb +10 -0
  28. data/spec/support/queue_subscription_thread_harness.rb +29 -0
  29. data/spec/support/rabbitmqadmin.rb +74 -0
  30. metadata +58 -117
  31. data/lib/euston-rabbitmq/command_handlers/retry_failed_message.rb +0 -29
  32. data/lib/euston-rabbitmq/event_handlers/message_failure.rb +0 -27
  33. data/lib/euston-rabbitmq/message_buffer.rb +0 -67
  34. data/lib/euston-rabbitmq/message_logger.rb +0 -50
  35. data/lib/euston-rabbitmq/queue.rb +0 -30
  36. data/lib/euston-rabbitmq/read_model/failed_message.rb +0 -36
  37. data/lib/euston-rabbitmq/read_model/message_buffer.rb +0 -57
  38. data/lib/euston-rabbitmq/read_model/message_log.rb +0 -37
  39. data/spec/command_buffer_spec.rb +0 -69
  40. data/spec/event_buffer_spec.rb +0 -69
  41. data/spec/exchange_declaration_spec.rb +0 -28
  42. data/spec/message_failure_spec.rb +0 -77
  43. data/spec/mt_safe_queue_subscription_spec.rb +0 -72
  44. data/spec/safe_queue_subscription_spec.rb +0 -50
  45. data/spec/support/factories.rb +0 -18
@@ -1,30 +0,0 @@
1
- module AMQP
2
- class Queue
3
- include Hollywood
4
-
5
- def safe_subscribe
6
- cb = Hollywood.instance_method(:callback).bind self
7
-
8
- self.subscribe :ack => true do |header, body|
9
- rt = ::RobustThread.new(:args => [header, body], :label => "#{self.name} queue subscriber") do |header, body|
10
- begin
11
- message = ::ActiveSupport::JSON.decode body
12
- message.recursive_symbolize_keys!
13
-
14
- begin
15
- cb.call :message_received, message
16
- header.ack
17
- rescue Euston::EventStore::ConcurrencyError
18
- header.reject :requeue => true
19
- rescue StandardError => e
20
- cb.call :message_failed, message, e, header
21
- end
22
- rescue StandardError => e
23
- cb.call :message_decode_failed, body, e
24
- header.ack
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,36 +0,0 @@
1
- module Euston
2
- module RabbitMq
3
- module ReadModel
4
- class FailedMessage
5
- def initialize
6
- name = 'failed_messages'
7
-
8
- mongodb = Euston::RabbitMq.event_store_mongodb
9
- mongodb.create_collection name unless mongodb.collection_names.include? name
10
-
11
- @collection = mongodb.collection name
12
- @collection.ensure_index [ ['failure_timestamp', Mongo::ASCENDING] ], :unique => false, :name => 'failed_messages_failure_timestamp_index'
13
- end
14
-
15
- def get_by_id id
16
- @collection.find_one({ '_id' => id })
17
- end
18
-
19
- def find_all
20
- @collection.find({}, { :sort => [ 'failure_timestamp', Mongo::DESCENDING ] })
21
- end
22
-
23
- def log_failure failure
24
- failure.recursive_stringify_keys!
25
- failure['_id'] = failure.delete 'message_id'
26
-
27
- @collection.save(failure, :safe => { :fsync => true })
28
- end
29
-
30
- def remove_by_id id
31
- @collection.remove({ '_id' => id }, :safe => { :fsync => true })
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,57 +0,0 @@
1
- module Euston
2
- module RabbitMq
3
- module ReadModel
4
- class MessageBuffer
5
- class << self
6
- def commands
7
- @commands ||= MessageBuffer.new "buffered_commands"
8
- end
9
-
10
- def events
11
- @events ||= MessageBuffer.new "buffered_events"
12
- end
13
- end
14
-
15
- def initialize name
16
- mongodb = Euston::RabbitMq.event_store_mongodb
17
- mongodb.create_collection name unless mongodb.collection_names.include? name
18
-
19
- @collection = mongodb.collection name
20
- @collection.ensure_index [ ['next_attempt', Mongo::ASCENDING] ], :unique => false, :name => "#{name}_next_attempt_index"
21
- end
22
-
23
- def buffer_new_message message
24
- @collection.save({ '_id' => message[:headers][:id],
25
- 'type' => message[:headers][:type],
26
- 'next_attempt' => Time.now.to_f,
27
- 'json' => ::ActiveSupport::JSON.encode(message) }, :safe => { :fsync => true })
28
- end
29
-
30
- def find_next_message
31
- query = { 'next_attempt' => { '$lte' => Time.now.to_f } }
32
- @collection.find_one(query)
33
- end
34
-
35
- def find_due_messages
36
- query = { 'next_attempt' => { '$lte' => Time.now.to_f } }
37
- order = [ 'next_attempt', Mongo::ASCENDING ]
38
-
39
- @collection.find(query).sort(order)
40
- end
41
-
42
- def get_by_id id
43
- @collection.find_one({ '_id' => id })
44
- end
45
-
46
- def set_next_attempt message
47
- @collection.update({ '_id' => message['_id'] },
48
- { '$set' => { 'next_attempt' => Time.now.to_f + 10 } }, :safe => { :fsync => true })
49
- end
50
-
51
- def remove_published_message id
52
- @collection.remove({ '_id' => id }, :safe => { :fsync => true })
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,37 +0,0 @@
1
- module Euston
2
- module RabbitMq
3
- module ReadModel
4
- class MessageLog
5
- class << self
6
- def commands
7
- @commands ||= MessageLog.new "command_log"
8
- end
9
-
10
- def events
11
- @events ||= MessageLog.new "event_log"
12
- end
13
- end
14
-
15
- def initialize name
16
- mongodb = Euston::RabbitMq.event_store_mongodb
17
- mongodb.create_collection name unless mongodb.collection_names.include? name
18
-
19
- @collection = mongodb.collection name
20
- @collection.ensure_index [ ['timestamp', Mongo::ASCENDING] ], :unique => false, :name => "#{name}_timestamp_index"
21
- end
22
-
23
- def find_all
24
- @collection.find({}, { :sort => [ 'timestamp', Mongo::DESCENDING ] })
25
- end
26
-
27
- def log_new_message message
28
- @collection.save({ '_id' => message[:headers][:id],
29
- 'type' => message[:headers][:type],
30
- 'version' => message[:headers][:version],
31
- 'timestamp' => Time.now.to_f,
32
- 'json' => ::ActiveSupport::JSON.encode(message) }, :safe => { :fsync => true })
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,69 +0,0 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
2
-
3
- unless RUBY_PLATFORM == 'java'
4
- describe 'command buffer' do
5
- include EustonRmqSpec
6
-
7
- context 'the command buffer is created' do
8
- include Euston::RabbitMq::Queues
9
-
10
- amqp_before do
11
- @buffer = Euston::RabbitMq::MessageBuffer.new @channel, :commands
12
- @queue = get_queue @channel, :commands_buffer
13
- end
14
-
15
- it 'declares the commands buffer queue' do
16
- done(0.5) {
17
- @queue.name.should == 'commands_buffer'
18
- @queue.opts.should include(:durable => true)
19
- }
20
- end
21
-
22
- it 'binds the command buffer queue to the commands exchange with a wildcard routing key' do
23
- received = []
24
- data = Factory.command
25
-
26
- delayed(1) {
27
- exchange = @channel.find_exchange 'commands'
28
- @queue.when(:message_received => ->(message) { received << message })
29
- exchange.publish ::ActiveSupport::JSON.encode(data), :routing_key => "commands.#{Euston.uuid.generate}"
30
- }
31
-
32
- done(2) {
33
- received.should have(1).item
34
- received.first.should == data
35
- }
36
- end
37
- end
38
-
39
- context 'a command is pushed into the buffer by client code' do
40
- amqp_before :each do
41
- @buffer = Euston::RabbitMq::MessageBuffer.new @channel, :commands
42
- @command = Factory.command
43
- @buffer.push @command
44
- @read_model = Euston::RabbitMq::ReadModel::MessageBuffer.new 'buffered_commands'
45
- end
46
-
47
- it 'writes the command to persistent storage' do
48
- @read_model.find_due_messages.to_a.should have(1).items
49
-
50
- due_command = @read_model.find_due_messages.first
51
- due_command['_id'].should == @command[:headers][:id]
52
- due_command['type'].should == @command[:headers][:type]
53
- due_command['json'].should == ::ActiveSupport::JSON.encode(@command)
54
-
55
- done(0.3)
56
- end
57
-
58
- it 'removes the command from the buffer storage when it is obtained from the buffer queue' do
59
- delayed(1) {
60
- @buffer.dispatch_due_messages
61
- }
62
-
63
- done(3) {
64
- @read_model.get_by_id(@command[:headers][:id]).should be_nil
65
- }
66
- end
67
- end
68
- end
69
- end
@@ -1,69 +0,0 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
2
-
3
- unless RUBY_PLATFORM == 'java'
4
- describe 'event buffer' do
5
- include EustonRmqSpec
6
-
7
- context 'the event buffer is created' do
8
- include Euston::RabbitMq::Queues
9
-
10
- amqp_before do
11
- @buffer = Euston::RabbitMq::MessageBuffer.new @channel, :events
12
- @queue = get_queue @channel, :events_buffer
13
- end
14
-
15
- it 'declares the events buffer queue' do
16
- done(0.5) {
17
- @queue.name.should == 'events_buffer'
18
- @queue.opts.should include(:durable => true)
19
- }
20
- end
21
-
22
- it 'binds the event buffer queue to the events exchange with a wildcard routing key' do
23
- received = []
24
- data = Factory.command
25
-
26
- delayed(0.3) {
27
- exchange = @channel.find_exchange 'events'
28
- @queue.when(:message_received => ->(message) { received << message })
29
- exchange.publish ::ActiveSupport::JSON.encode(data), :routing_key => "events.#{Euston.uuid.generate}"
30
- }
31
-
32
- done(2) {
33
- received.should have(1).item
34
- received.first.should == data
35
- }
36
- end
37
- end
38
-
39
- context 'an event is pushed into the buffer by client code' do
40
- amqp_before :each do
41
- @buffer = Euston::RabbitMq::MessageBuffer.new @channel, :events
42
- @command = Factory.command
43
- @buffer.push @command
44
- @read_model = Euston::RabbitMq::ReadModel::MessageBuffer.new 'buffered_events'
45
- end
46
-
47
- it 'writes the event to persistent storage' do
48
- @read_model.find_due_messages.to_a.should have(1).items
49
-
50
- due_command = @read_model.find_due_messages.first
51
- due_command['_id'].should == @command[:headers][:id]
52
- due_command['type'].should == @command[:headers][:type]
53
- due_command['json'].should == ::ActiveSupport::JSON.encode(@command)
54
-
55
- done(0.3)
56
- end
57
-
58
- it 'removes the event from the buffer storage when it is obtained from the buffer queue' do
59
- delayed(1) {
60
- @buffer.dispatch_due_messages
61
- }
62
-
63
- done(3) {
64
- @read_model.get_by_id(@command[:headers][:id]).should be_nil
65
- }
66
- end
67
- end
68
- end
69
- end
@@ -1,28 +0,0 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
2
-
3
- unless RUBY_PLATFORM == 'java'
4
- describe 'exchange declaration' do
5
- include EustonRmqSpec
6
-
7
- context 'declaring the commands exchange' do
8
- include Euston::RabbitMq::Exchanges
9
- it 'declares the commands exchange' do
10
- exchange = get_exchange @channel, :commands
11
-
12
- done(0.3) {
13
- exchange.name.should == 'commands'
14
- exchange.should be_durable
15
- }
16
- end
17
-
18
- it 'declares the events exchange' do
19
- exchange = get_exchange @channel, :events
20
-
21
- done(0.3) {
22
- exchange.name.should == 'events'
23
- exchange.should be_durable
24
- }
25
- end
26
- end
27
- end
28
- end
@@ -1,77 +0,0 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
2
-
3
- unless RUBY_PLATFORM == 'java'
4
- module MessageFailureTracking
5
- module FailingCommandHandlers
6
- class DeliverWidget
7
- class << self
8
- attr_accessor :fail_next
9
- end
10
-
11
- include Euston::CommandHandler
12
-
13
- version 1 do |headers, command|
14
- if self.class.fail_next
15
- raise 'User-generated failure'
16
- else
17
- publish headers, command
18
- end
19
- end
20
- end
21
- end
22
- end
23
-
24
- describe 'message failure tracking' do
25
- include EustonRmqSpec
26
- include Euston::RabbitMq::Exchanges
27
- include Euston::RabbitMq::Queues
28
-
29
- amqp_before do
30
- @exchange = get_exchange @channel, :commands
31
- @handler = ::MessageFailureTracking::FailingCommandHandlers::DeliverWidget
32
-
33
- command_handlers = Euston::RabbitMq::CommandHandlerBindings.new @channel
34
- command_handlers.add_namespace Euston::RabbitMq::CommandHandlers
35
- command_handlers.add_namespace ::MessageFailureTracking::FailingCommandHandlers
36
- command_handlers.finalize_bindings
37
-
38
- event_handlers = Euston::RabbitMq::EventHandlerBindings.new @channel
39
- event_handlers.add_namespace Euston::RabbitMq::EventHandlers
40
- event_handlers.finalize_bindings
41
-
42
- @command = { :headers => { :id => Euston.uuid.generate,
43
- :type => 'deliver_widget',
44
- :version => 1 },
45
- :body => { :id => Euston.uuid.generate,
46
- :name => 'Bill Smith' } }
47
- end
48
-
49
- def publish
50
- json = ::ActiveSupport::JSON.encode(@command)
51
- opts = default_publish_options.merge(:routing_key => 'commands.deliver_widget')
52
- delayed(0.3) { @exchange.publish json, opts }
53
- end
54
-
55
- context 'a failing command is published' do
56
- it 'moves the message to the failed-messages queue' do
57
- @handler.fail_next = true
58
- received = 0
59
-
60
- delayed(0.3) {
61
- @queue = get_queue @channel, :message_failure
62
- @queue.when(:message_received => ->(message) { received = received + 1 })
63
-
64
- publish
65
- }
66
-
67
- done(3) {
68
- received.should == 1
69
- }
70
- end
71
- end
72
-
73
- context 'a failed message is retried' do
74
-
75
- end
76
- end
77
- end
@@ -1,72 +0,0 @@
1
- require File.expand_path('../spec_helper.rb', __FILE__)
2
-
3
- if RUBY_PLATFORM == 'java'
4
- require 'spec_helper'
5
-
6
- describe 'message-failure-aware queue subscription' do
7
- include EustonRmqSpec
8
-
9
- PUBLISH_OPTS = { :immediate => false,
10
- :mandatory => true,
11
- :persistent => true,
12
- :routing_key => 'messages.xyz' }
13
-
14
- before :each do
15
- @queue = @exchange = nil
16
- @exchange = @channel.topic "test-#{Euston.uuid.generate}", :durable => true, :nowait => false
17
- @queue = @channel.queue "test-#{Euston.uuid.generate}", :durable => true, :nowait => false
18
-
19
- @queue.bind @exchange, :routing_key => 'messages.#'
20
- @th = Thread.new do
21
- Thread.current[:failed_count] = 0
22
- Thread.current[:succeed_count] = 0
23
-
24
- def on_message_decode_failed(message, error)
25
- Thread.current[:failed_count] += 1
26
- end
27
-
28
- def on_message_failed(message, error, msg)
29
- Thread.current[:failed_count] += 1
30
- end
31
-
32
- def on_message_received(message)
33
- if message[:a] == 'oops'
34
- raise 'Something bad happened'
35
- else
36
- Thread.current[:succeed_count] += 1
37
- end
38
- end
39
-
40
- @queue.when(:message_decode_failed => method(:on_message_decode_failed),
41
- :message_failed => method(:on_message_failed),
42
- :message_received => method(:on_message_received))
43
-
44
- @queue.safe_subscribe
45
- end
46
-
47
- @th.abort_on_exception = true
48
- end
49
-
50
- after :each do
51
- @th.kill
52
- end
53
-
54
- it 'adds a failure-aware subscription function to the amqp queue object' do
55
- @queue.class.should == AMQP::Queue
56
- @queue.should respond_to(:safe_subscribe)
57
-
58
- @exchange.publish ' *897d- -', PUBLISH_OPTS
59
- sleep 0.25
60
- @th[:failed_count].should == 1
61
-
62
- jsn = JSON.generate({ :a => :b })
63
- @exchange.publish jsn, PUBLISH_OPTS
64
- sleep 0.25
65
- @th[:succeed_count].should == 1
66
-
67
- @exchange.publish JSON.generate({ :a => 'oops' }), PUBLISH_OPTS
68
- sleep 0.25
69
- @th[:failed_count].should == 2
70
- end
71
- end
72
- end