euston-rabbitmq 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +103 -0
  3. data/Rakefile +182 -0
  4. data/euston-rabbitmq.gemspec +74 -0
  5. data/lib/euston-rabbitmq/buffered_message_dispatcher.rb +48 -0
  6. data/lib/euston-rabbitmq/command_handler_bindings.rb +30 -0
  7. data/lib/euston-rabbitmq/command_handlers/retry_failed_message.rb +29 -0
  8. data/lib/euston-rabbitmq/errors.rb +5 -0
  9. data/lib/euston-rabbitmq/event_handler_binding.rb +114 -0
  10. data/lib/euston-rabbitmq/event_handler_bindings.rb +30 -0
  11. data/lib/euston-rabbitmq/event_handlers/message_failure.rb +27 -0
  12. data/lib/euston-rabbitmq/event_store_dispatcher.rb +45 -0
  13. data/lib/euston-rabbitmq/exchanges.rb +17 -0
  14. data/lib/euston-rabbitmq/handler_bindings.rb +115 -0
  15. data/lib/euston-rabbitmq/message_buffer.rb +68 -0
  16. data/lib/euston-rabbitmq/message_logger.rb +50 -0
  17. data/lib/euston-rabbitmq/queue.rb +30 -0
  18. data/lib/euston-rabbitmq/queues.rb +13 -0
  19. data/lib/euston-rabbitmq/read_model/failed_message.rb +36 -0
  20. data/lib/euston-rabbitmq/read_model/message_buffer.rb +52 -0
  21. data/lib/euston-rabbitmq/read_model/message_log.rb +37 -0
  22. data/lib/euston-rabbitmq/version.rb +5 -0
  23. data/lib/euston-rabbitmq.rb +28 -0
  24. data/spec/command_buffer_spec.rb +69 -0
  25. data/spec/event_buffer_spec.rb +69 -0
  26. data/spec/exchange_declaration_spec.rb +28 -0
  27. data/spec/message_failure_spec.rb +77 -0
  28. data/spec/mt_safe_queue_subscription_spec.rb +72 -0
  29. data/spec/safe_queue_subscription_spec.rb +50 -0
  30. data/spec/spec_helper.rb +65 -0
  31. data/spec/support/factories.rb +18 -0
  32. metadata +265 -0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,103 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ euston-rabbitmq (1.0.0)
5
+ activesupport (~> 3.0.0)
6
+ amqp (~> 0.8.0)
7
+ bson_ext (~> 1.3.1)
8
+ euston (~> 1.0.0)
9
+ euston-eventstore (~> 1.0.0)
10
+ eventmachine (~> 0.12.10)
11
+ hash-keys (~> 1.0.0)
12
+ hollywood (~> 1.0.0)
13
+ mongo (~> 1.3.1)
14
+ require_all (~> 1.2.0)
15
+ robustthread (~> 0.5.2)
16
+ safely (~> 0.3.0)
17
+
18
+ GEM
19
+ remote: http://rubygems.org/
20
+ specs:
21
+ activesupport (3.0.10)
22
+ amq-client (0.8.3)
23
+ amq-protocol (>= 0.8.0)
24
+ eventmachine
25
+ amq-protocol (0.8.1)
26
+ amqp (0.8.0)
27
+ amq-client (~> 0.8.3)
28
+ amq-protocol (~> 0.8.0)
29
+ eventmachine
30
+ awesome_print (0.4.0)
31
+ bson (1.3.1)
32
+ bson (1.3.1-java)
33
+ bson_ext (1.3.1)
34
+ diff-lcs (1.1.3)
35
+ euston (1.0.0)
36
+ activesupport (~> 3.0.9)
37
+ euston-eventstore (~> 1.0.0)
38
+ require_all (~> 1.2.0)
39
+ euston-eventstore (1.0.4)
40
+ activesupport (~> 3.0.9)
41
+ bson_ext (~> 1.3.1)
42
+ hash-keys (~> 1.0.0)
43
+ json (~> 1.5.0)
44
+ mongo (~> 1.3.1)
45
+ require_all (~> 1.2.0)
46
+ uuid (~> 2.3.0)
47
+ euston-eventstore (1.0.4-java)
48
+ activesupport (~> 3.0.9)
49
+ hash-keys (~> 1.0.0)
50
+ jmongo (~> 1.0.0)
51
+ json-jruby (~> 1.5.0)
52
+ require_all (~> 1.2.0)
53
+ uuid (~> 2.3.0)
54
+ evented-spec (0.9.0)
55
+ eventmachine (0.12.10)
56
+ eventmachine (0.12.10-java)
57
+ faker (1.0.0)
58
+ i18n (~> 0.4)
59
+ fuubar (0.0.6)
60
+ rspec (~> 2.0)
61
+ rspec-instafail (~> 0.1.8)
62
+ ruby-progressbar (~> 0.0.10)
63
+ hash-keys (1.0.0)
64
+ hollywood (1.0.0)
65
+ i18n (0.6.0)
66
+ jmongo (1.0.3)
67
+ require_all (~> 1.2)
68
+ json (1.5.0)
69
+ json (1.5.0-java)
70
+ json-jruby (1.5.0-java)
71
+ json (= 1.5.0)
72
+ macaddr (1.4.0)
73
+ systemu (~> 2.2.0)
74
+ mongo (1.3.1)
75
+ bson (>= 1.3.1)
76
+ require_all (1.2.0)
77
+ robustthread (0.5.2)
78
+ rspec (2.6.0)
79
+ rspec-core (~> 2.6.0)
80
+ rspec-expectations (~> 2.6.0)
81
+ rspec-mocks (~> 2.6.0)
82
+ rspec-core (2.6.4)
83
+ rspec-expectations (2.6.0)
84
+ diff-lcs (~> 1.1.2)
85
+ rspec-instafail (0.1.8)
86
+ rspec-mocks (2.6.0)
87
+ ruby-progressbar (0.0.10)
88
+ safely (0.3.0)
89
+ systemu (2.2.0)
90
+ uuid (2.3.4)
91
+ macaddr (~> 1.0)
92
+
93
+ PLATFORMS
94
+ java
95
+ ruby
96
+
97
+ DEPENDENCIES
98
+ awesome_print (~> 0.4.0)
99
+ euston-rabbitmq!
100
+ evented-spec (~> 0.9.0)
101
+ faker (~> 1.0.0)
102
+ fuubar (~> 0.0.0)
103
+ rspec (~> 2.6.0)
data/Rakefile ADDED
@@ -0,0 +1,182 @@
1
+ require 'date'
2
+ require 'rspec/core/rake_task'
3
+
4
+ #############################################################################
5
+ #
6
+ # Helper functions
7
+ #
8
+ #############################################################################
9
+
10
+ def name
11
+ @name ||= Dir['*.gemspec'].first.split('.').first
12
+ end
13
+
14
+ def version
15
+ line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
16
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
17
+ end
18
+
19
+ def date
20
+ Date.today.to_s
21
+ end
22
+
23
+ def rubyforge_project
24
+ name
25
+ end
26
+
27
+ def gemspec_file
28
+ "#{name}.gemspec"
29
+ end
30
+
31
+ def gem_file
32
+ "#{name}-#{version}.gem"
33
+ end
34
+
35
+ def replace_header(head, header_name, provider = nil)
36
+ if provider
37
+ value = send(provider)
38
+ else
39
+ value = "'#{send(header_name)}'"
40
+ end
41
+
42
+ provider ||= header_name
43
+ head.sub!(/(\.#{header_name}\s*= ).*/) { "#{$1}#{value}"}
44
+ end
45
+
46
+ def platform
47
+ jruby? ? '-java' : ''
48
+ end
49
+
50
+ def platform_dependant_gem_file
51
+ "#{name}-#{version}#{platform}.gem"
52
+ end
53
+
54
+ def platform_dependent_version
55
+ "'#{version}#{platform}'"
56
+ end
57
+
58
+ def jruby?
59
+ RUBY_PLATFORM.to_s == 'java'
60
+ end
61
+
62
+ def trim_array_ends array
63
+ array.shift
64
+ array.pop
65
+ array
66
+ end
67
+
68
+ #############################################################################
69
+ #
70
+ # Custom tasks
71
+ #
72
+ #############################################################################
73
+
74
+ default_rspec_opts = %w[--colour --format Fuubar]
75
+
76
+ desc "Run all examples"
77
+ RSpec::Core::RakeTask.new(:spec) do |t|
78
+ t.rspec_opts = default_rspec_opts
79
+ end
80
+
81
+ desc "Resets the testing vhost in rabbitmq"
82
+ task :reset_rabbitmq do
83
+ vhosts = `rabbitmqctl list_vhosts`.split("\n")
84
+ vhosts = trim_array_ends vhosts
85
+ vhost = '/euston-rabbitmq-test'
86
+
87
+ puts `rabbitmqctl delete_vhost #{vhost}` if vhosts.include? vhost
88
+ puts `rabbitmqctl add_vhost #{vhost}`
89
+
90
+ users = `rabbitmqctl list_users`.split("\n")
91
+ users = trim_array_ends users
92
+ users = users.map { |u| u.split("\t").shift }
93
+
94
+ user, password = 'euston-rabbitmq-tester', 'password'
95
+
96
+ puts `rabbitmqctl delete_user #{user}` if users.include? user
97
+ puts `rabbitmqctl add_user #{user} #{password}`
98
+ puts `rabbitmqctl set_permissions -p #{vhost} #{user} ".*" ".*" ".*"`
99
+ puts `rabbitmqctl set_permissions -p #{vhost} guest ".*" ".*" ".*"` if users.include? 'guest'
100
+ end
101
+
102
+ #############################################################################
103
+ #
104
+ # Packaging tasks
105
+ #
106
+ #############################################################################
107
+
108
+ def built_gem
109
+ @built_gem ||= Dir["#{name}*.gem"].first
110
+ end
111
+
112
+ desc "Create tag v#{platform_dependent_version} and build and push #{platform_dependant_gem_file} to Rubygems"
113
+ task :release => :build do
114
+ unless `git branch` =~ /^\* master$/
115
+ puts "You must be on the master branch to release!"
116
+ exit!
117
+ end
118
+
119
+ sh "git commit --allow-empty -a -m 'Release #{platform_dependent_version}'"
120
+ sh "git tag v#{platform_dependent_version}"
121
+ sh "git push origin master"
122
+ sh "git push origin v#{platform_dependent_version}"
123
+
124
+ command = "gem push pkg/#{platform_dependant_gem_file}"
125
+
126
+ if jruby?
127
+ puts "--------------------------------------------------------------------------------------"
128
+ puts "can't push to rubygems using jruby at the moment, so switch to mri and run: #{command}"
129
+ puts "--------------------------------------------------------------------------------------"
130
+ else
131
+ sh command
132
+ end
133
+ end
134
+
135
+ desc "Build #{platform_dependant_gem_file} into the pkg directory"
136
+ task :build => :gemspec do
137
+ sh "mkdir -p pkg"
138
+ sh "gem build #{gemspec_file}"
139
+ sh "mv #{built_gem} pkg"
140
+ end
141
+
142
+ desc "Generate #{gemspec_file}"
143
+ task :gemspec => :validate do
144
+ # read spec file and split out manifest section
145
+ spec = File.read(gemspec_file)
146
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
147
+
148
+ # replace name version and date
149
+ replace_header(head, :name)
150
+ replace_header(head, :version)
151
+ replace_header(head, :date)
152
+ #comment this out if your rubyforge_project has a different name
153
+ #replace_header(head, :rubyforge_project)
154
+
155
+ # determine file list from git ls-files
156
+ files = `git ls-files`.
157
+ split("\n").
158
+ sort.
159
+ reject { |file| file =~ /^\./ }.
160
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
161
+ map { |file| " #{file}" }.
162
+ join("\n")
163
+
164
+ # piece file back together and write
165
+ manifest = " s.files = %w[\n#{files}\n ]\n"
166
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
167
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
168
+ puts "Updated #{gemspec_file}"
169
+ end
170
+
171
+ desc "Validate #{gemspec_file}"
172
+ task :validate do
173
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
174
+ unless libfiles.empty?
175
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
176
+ exit!
177
+ end
178
+ unless Dir['VERSION*'].empty?
179
+ puts "A `VERSION` file at root level violates Gem best practices."
180
+ exit!
181
+ end
182
+ end
@@ -0,0 +1,74 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'euston-rabbitmq'
3
+ s.version = '1.0.0'
4
+ s.platform = RUBY_PLATFORM.to_s == 'java' ? 'java' : Gem::Platform::RUBY
5
+ s.authors = ['Lee Henson', 'Guy Boertje']
6
+ s.email = ['lee.m.henson@gmail.com', 'guyboertje@gmail.com']
7
+ s.summary = %q{RabbitMq bindings for Euston.}
8
+ s.description = "JRuby RabbitMq bindings"
9
+ s.homepage = 'http://github.com/leemhenson/euston-rabbitmq'
10
+
11
+ # = MANIFEST =
12
+ s.files = %w[
13
+ Gemfile
14
+ Gemfile.lock
15
+ Rakefile
16
+ euston-rabbitmq.gemspec
17
+ lib/euston-rabbitmq.rb
18
+ lib/euston-rabbitmq/buffered_message_dispatcher.rb
19
+ lib/euston-rabbitmq/command_handler_bindings.rb
20
+ lib/euston-rabbitmq/command_handlers/retry_failed_message.rb
21
+ lib/euston-rabbitmq/errors.rb
22
+ lib/euston-rabbitmq/event_handler_binding.rb
23
+ lib/euston-rabbitmq/event_handler_bindings.rb
24
+ lib/euston-rabbitmq/event_handlers/message_failure.rb
25
+ lib/euston-rabbitmq/event_store_dispatcher.rb
26
+ lib/euston-rabbitmq/exchanges.rb
27
+ lib/euston-rabbitmq/handler_bindings.rb
28
+ lib/euston-rabbitmq/message_buffer.rb
29
+ lib/euston-rabbitmq/message_logger.rb
30
+ lib/euston-rabbitmq/queue.rb
31
+ lib/euston-rabbitmq/queues.rb
32
+ lib/euston-rabbitmq/read_model/failed_message.rb
33
+ lib/euston-rabbitmq/read_model/message_buffer.rb
34
+ lib/euston-rabbitmq/read_model/message_log.rb
35
+ lib/euston-rabbitmq/version.rb
36
+ spec/command_buffer_spec.rb
37
+ spec/event_buffer_spec.rb
38
+ spec/exchange_declaration_spec.rb
39
+ spec/message_failure_spec.rb
40
+ spec/mt_safe_queue_subscription_spec.rb
41
+ spec/safe_queue_subscription_spec.rb
42
+ spec/spec_helper.rb
43
+ spec/support/factories.rb
44
+ ]
45
+ # = MANIFEST =
46
+
47
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
48
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
49
+
50
+ s.add_dependency 'activesupport', '~> 3.0.0'
51
+ s.add_dependency 'euston', '~> 1.0.0'
52
+ s.add_dependency 'euston-eventstore', '~> 1.0.0'
53
+ s.add_dependency 'hash-keys', '~> 1.0.0'
54
+ s.add_dependency 'hollywood', '~> 1.0.0'
55
+ s.add_dependency 'require_all', '~> 1.2.0'
56
+ s.add_dependency 'robustthread', '~> 0.5.2'
57
+ s.add_dependency 'safely', '~> 0.3.0'
58
+
59
+ if RUBY_PLATFORM.to_s == 'java'
60
+ s.add_dependency 'jmongo', '~> 1.0.0'
61
+ s.add_dependency 'jessica', '~> 1.0.0'
62
+ else
63
+ s.add_dependency 'amqp', '~> 0.8.0'
64
+ s.add_dependency 'bson_ext', '~> 1.3.1'
65
+ s.add_dependency 'eventmachine', '~> 0.12.10'
66
+ s.add_dependency 'mongo', '~> 1.3.1'
67
+ s.add_development_dependency 'evented-spec', '~> 0.9.0'
68
+ end
69
+
70
+ s.add_development_dependency 'awesome_print', '~> 0.4.0'
71
+ s.add_development_dependency 'faker', '~> 1.0.0'
72
+ s.add_development_dependency 'fuubar', '~> 0.0.0'
73
+ s.add_development_dependency 'rspec', '~> 2.6.0'
74
+ end
@@ -0,0 +1,48 @@
1
+ module Euston
2
+ module RabbitMq
3
+ class BufferedMessageDispatcher
4
+ class << self
5
+ def command_dispatcher channel
6
+ @command_dispatcher ||= BufferedMessageDispatcher.new(Euston::RabbitMq::MessageBuffer.commands_buffer channel)
7
+ end
8
+
9
+ def event_dispatcher channel
10
+ @event_dispatcher ||= BufferedMessageDispatcher.new(Euston::RabbitMq::MessageBuffer.events_buffer channel)
11
+ end
12
+ end
13
+
14
+ include Euston::RabbitMq::Exchanges
15
+
16
+ def initialize buffer
17
+ @buffer = buffer
18
+ @buffer.when(:no_messages_were_due) { @sleep = true }
19
+ end
20
+
21
+ attr_writer :wait_time
22
+
23
+ def wait_time
24
+ @wait_time ||= 1
25
+ end
26
+
27
+ def stop
28
+ @stop = true
29
+ end
30
+
31
+ def start
32
+ @stop = false
33
+
34
+ dispatch_loop = Proc.new do
35
+ @sleep = false
36
+
37
+ begin
38
+ @buffer.dispatch_due_messages
39
+ end while !@sleep
40
+
41
+ EM.add_timer(wait_time) { dispatch_loop.call } unless @stop
42
+ end
43
+
44
+ EM.add_timer(wait_time) { dispatch_loop.call }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ module Euston
2
+ module RabbitMq
3
+ class CommandHandlerBindings < HandlerBindings
4
+ def finalize_bindings
5
+ super
6
+ attach_queue_hook_listeners queue
7
+ end
8
+
9
+ protected
10
+
11
+ def bind_handler handler_type
12
+ queue.bind @exchanges[:commands], :routing_key => "commands.#{handler_type.to_s.underscore}"
13
+ end
14
+
15
+ def call_handler message
16
+ command_headers = CommandHeaders.from_hash(message[:headers]).freeze
17
+ command_body = message[:body].freeze
18
+ handler_type = command_headers.type.to_s.camelize.to_sym
19
+ find_namespaces_containing_handler_type(handler_type).each do |namespace|
20
+ handler = namespace.const_get handler_type
21
+ handler.new.send "__version__#{command_headers.version}", command_headers, command_body
22
+ end
23
+ end
24
+
25
+ def queue
26
+ @queue ||= get_queue @channel, :command_handlers
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module Euston
2
+ module RabbitMq
3
+ module CommandHandlers
4
+ class RetryFailedMessage
5
+ include Euston::CommandHandler
6
+
7
+ version 1 do |headers, command|
8
+ model = Euston::RabbitMq::ReadModel::FailedMessage.new
9
+ message = model.get_by_id command[:id]
10
+
11
+ unless message.nil?
12
+ routing_key = message['routing_key']
13
+ exchange = routing_key.split('.').first
14
+
15
+ headers = message['headers'].merge(:id => command[:id],
16
+ :type => message['type'],
17
+ :version => message['version'] )
18
+
19
+ buffer = Euston::RabbitMq::MessageBuffer.send("#{exchange}_buffer")
20
+ buffer.push :headers => headers,
21
+ :body => message['body']
22
+
23
+ model.remove_by_id command[:id]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module Euston
2
+ module RabbitMq
3
+ class MessageDecodeFailedError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,114 @@
1
+ module Euston
2
+ module RabbitMq
3
+ class EventHandlerBinding
4
+ include Euston::RabbitMq::Queues
5
+ include Euston::RabbitMq::Exchanges
6
+
7
+ PREFIX = '__event_handler__'
8
+
9
+ def initialize(channel, namespace, handler_type)
10
+ @channel, @namespace, @handler_type = channel, namespace, handler_type
11
+
12
+ name = @handler_type.to_s.underscore
13
+ @queue = get_queue(@channel, name)
14
+ instance_variable_set "@#{name}".to_sym, @queue
15
+ @handler_class = @namespace.const_get @handler_type
16
+ @exchange = get_exchange(@channel, :events)
17
+ end
18
+
19
+ def bind
20
+ bindable_methods.each do |method|
21
+ @queue.bind @exchange, :routing_key => "events.#{method}"
22
+ end
23
+ self
24
+ end
25
+
26
+ def listen
27
+ attach_queue_hook_listeners @queue
28
+ end
29
+
30
+ private
31
+
32
+ def attach_queue_hook_listeners queue
33
+ queue.when(:message_decode_failed => method(:log_decode_failure),
34
+ :message_failed => method(:handle_failure),
35
+ :message_received => method(:call_handler))
36
+
37
+ queue.safe_subscribe
38
+ end
39
+
40
+ def call_handler(message)
41
+ event_headers = EventHeaders.from_hash message[:headers]
42
+ event_body = message[:body]
43
+
44
+ @handler_class.new.send "#{PREFIX}#{event_headers.type}__#{event_headers.version}", event_headers.freeze, event_body.freeze
45
+ end
46
+
47
+ def bindable_methods
48
+ methods = @handler_class.public_instance_methods(false)
49
+ methods = methods.select { |method| method.to_s.start_with? PREFIX }
50
+ methods = methods.map { |method| method.to_s[PREFIX.length, method.to_s.length - PREFIX.length] }
51
+ methods = methods.map { |method| method.split('__').first }
52
+ methods.uniq
53
+ end
54
+
55
+ def create_message_failed_message error, failed_message, amqp_header
56
+ {
57
+ :headers =>
58
+ {
59
+ :id => Euston.uuid.generate.to_s,
60
+ :type => :message_failed,
61
+ :version => 1,
62
+ :timestamp => Time.now.to_f
63
+ },
64
+
65
+ :body =>
66
+ {
67
+ :message => failed_message,
68
+ :routing_key => amqp_header.method.routing_key,
69
+ :error => error.message,
70
+ :backtrace => error.backtrace
71
+ }
72
+ }
73
+ end
74
+
75
+ def handle_failure message, error, amqp_header
76
+ failures = message[:failures] || 0
77
+ failures = failures + 1
78
+ message[:failures] = failures
79
+
80
+ begin
81
+ if failures == 3
82
+ message.delete :failures
83
+ message = create_message_failed_message error, message, amqp_header
84
+
85
+ options = { :key => 'events.message_failed' }
86
+ exchange_ref = :events
87
+ else
88
+ options = { :key => amqp_header.method.routing_key }
89
+ exchange_ref = amqp_header.method.routing_key.split('.').first.to_sym
90
+ end
91
+
92
+ options = default_publish_options.merge options
93
+ exchange = get_exchange(@channel, exchange_ref)
94
+ message = ::ActiveSupport::JSON.encode message
95
+
96
+ exchange.publish message, options
97
+
98
+ amqp_header.ack
99
+ rescue StandardError => e
100
+ amqp_header.reject :requeue => true
101
+ raise e
102
+ end
103
+ end
104
+
105
+ def log_decode_failure message, error
106
+ text = "A handler queue subscription failed. [Error] #{error.message} [Payload] #{message}"
107
+ err = Euston::RabbitMq::MessageDecodeFailedError.new text
108
+ err.set_backtrace error.backtrace
109
+
110
+ Safely.report! err
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,30 @@
1
+ module Euston
2
+ module RabbitMq
3
+ class EventHandlerBindings < HandlerBindings
4
+ PREFIX = '__event_handler__'
5
+
6
+ protected
7
+
8
+ def bind_handler handler_type
9
+ name = handler_type.to_s.underscore
10
+ queue = get_queue @channel, name
11
+ instance_variable_set "@#{name}".to_sym, queue
12
+
13
+ namespace = find_namespaces_containing_handler_type(handler_type).first
14
+ handler_class = namespace.const_get handler_type
15
+ methods = handler_class.public_instance_methods(false)
16
+ methods = methods.select { |method| method.to_s.start_with? PREFIX }
17
+ methods = methods.map { |method| method.to_s[PREFIX.length, method.to_s.length - PREFIX.length] }
18
+ methods = methods.map { |method| method.split('__').first }
19
+ methods.uniq.each { |method| queue.bind @exchanges[:events], :routing_key => "events.#{method}" }
20
+
21
+ attach_queue_hook_listeners queue do |message|
22
+ event_headers = EventHeaders.from_hash message[:headers]
23
+ event_body = message[:body]
24
+
25
+ handler_class.new.send "#{PREFIX}#{event_headers.type}__#{event_headers.version}", event_headers.freeze, event_body.freeze
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module Euston
2
+ module RabbitMq
3
+ module EventHandlers
4
+ class MessageFailure
5
+ include Euston::EventHandler
6
+
7
+ consumes :message_failed, 1 do |headers, event|
8
+ headers = event[:message][:headers].dup
9
+ failure = { :message_id => headers.delete(:id),
10
+ :type => headers.delete(:type),
11
+ :version => headers.delete(:version),
12
+ :message_timestamp => headers.delete(:timestamp),
13
+ :routing_key => event[:routing_key],
14
+ :body => event[:message][:body],
15
+ :headers => headers,
16
+ :error => event[:error],
17
+ :backtrace => event[:backtrace],
18
+ :failure_timestamp => Time.now.to_f }
19
+
20
+ model = Euston::RabbitMq::ReadModel::FailedMessage
21
+ model = model.new
22
+ model.log_failure failure
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ module Euston
2
+ module RabbitMq
3
+ class EventStoreDispatcher
4
+ def initialize channel
5
+ @channel = channel
6
+ @event_buffer = Euston::RabbitMq::MessageBuffer.events_buffer channel
7
+ @event_store = Euston::RabbitMq.event_store
8
+ end
9
+
10
+ attr_writer :wait_time
11
+
12
+ def wait_time
13
+ @wait_time ||= 1
14
+ end
15
+
16
+ def stop
17
+ @stop = true
18
+ end
19
+
20
+ def start
21
+ @stop = false
22
+
23
+ dispatch_loop = Proc.new do
24
+ begin
25
+ begin
26
+ commits = @event_store.get_undispatched_commits
27
+
28
+ commits.each do |commit|
29
+ commit.events.each { |event| @event_buffer.push event.to_hash }
30
+
31
+ @event_store.mark_commit_as_dispatched commit
32
+ end
33
+ end while !commits.empty?
34
+ rescue StandardError => e
35
+ DaemonKit::logger.error "#{e}\n#{e.backtrace}"
36
+ end
37
+
38
+ EM.add_timer(wait_time) { dispatch_loop.call } unless @stop
39
+ end
40
+
41
+ EM.add_timer(wait_time) { dispatch_loop.call }
42
+ end
43
+ end
44
+ end
45
+ end