euston-rabbitmq 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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