euston-daemons 1.1.0-java → 1.2.0-java
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.
- data/Gemfile +1 -0
- data/euston-daemons.gemspec +27 -52
- data/lib/euston-daemons/euston/daemon.rb +7 -1
- data/lib/euston-daemons/euston/daemon_component.rb +9 -49
- data/lib/euston-daemons/euston/daemon_component_host.rb +66 -0
- data/lib/euston-daemons/euston/daemon_environment.rb +10 -5
- data/lib/euston-daemons/euston/exceptions.rb +9 -0
- data/lib/euston-daemons/euston/stopwatch.rb +15 -0
- data/lib/euston-daemons/pipeline/config/environment.rb +74 -0
- data/lib/euston-daemons/pipeline/lib/command_logger/component.rb +54 -0
- data/lib/euston-daemons/pipeline/lib/command_logger/log.rb +31 -0
- data/lib/euston-daemons/pipeline/lib/command_processor/component.rb +50 -0
- data/lib/euston-daemons/pipeline/lib/command_processor/default_handlers/retry_failed_message.rb +34 -0
- data/lib/euston-daemons/pipeline/lib/command_processor/failed_message.rb +36 -0
- data/lib/euston-daemons/pipeline/lib/daemon.rb +77 -0
- data/lib/euston-daemons/pipeline/lib/event_processor/component.rb +67 -0
- data/lib/euston-daemons/pipeline/lib/event_processor/default_handlers/message_failure.rb +30 -0
- data/lib/euston-daemons/pipeline/lib/event_store_dispatcher/component.rb +68 -0
- data/lib/euston-daemons/pipeline/lib/message_buffer/buffer.rb +85 -0
- data/lib/euston-daemons/pipeline/lib/message_buffer/component.rb +59 -0
- data/lib/euston-daemons/pipeline/lib/snapshotter/component.rb +48 -0
- data/lib/euston-daemons/pipeline/rake_task.rb +45 -0
- data/lib/euston-daemons/rake_task.rb +40 -19
- data/lib/euston-daemons/rake_tasks.rb +1 -3
- data/lib/euston-daemons/version.rb +1 -1
- data/lib/euston-daemons.rb +3 -1
- data/spec/daemons/{command_handler_spec.rb → command_processor_spec.rb} +3 -3
- data/spec/daemons/{event_handler_spec.rb → event_processor_spec.rb} +2 -2
- data/spec/daemons/{command_buffer_publisher_spec.rb → message_buffer_spec.rb} +19 -23
- data/spec/daemons/{snapshot_client_spec.rb → snapshotter_spec.rb} +3 -5
- data/spec/spec_helper.rb +23 -9
- data/spec/support/filters.rb +4 -5
- metadata +73 -88
- data/lib/euston-daemons/command_processor_daemon/config/environment.rb +0 -34
- data/lib/euston-daemons/command_processor_daemon/lib/clients/command_handler.rb +0 -37
- data/lib/euston-daemons/command_processor_daemon/lib/command_handlers/retry_failed_message.rb +0 -35
- data/lib/euston-daemons/command_processor_daemon/lib/daemon.rb +0 -22
- data/lib/euston-daemons/command_processor_daemon/lib/mongo_models/failed_message.rb +0 -34
- data/lib/euston-daemons/command_processor_daemon/rake_task.rb +0 -36
- data/lib/euston-daemons/euston/daemon_client.rb +0 -24
- data/lib/euston-daemons/event_processor_daemon/config/environment.rb +0 -36
- data/lib/euston-daemons/event_processor_daemon/lib/clients/event_handler.rb +0 -42
- data/lib/euston-daemons/event_processor_daemon/lib/daemon.rb +0 -24
- data/lib/euston-daemons/event_processor_daemon/lib/event_handlers/message_failure.rb +0 -27
- data/lib/euston-daemons/event_processor_daemon/rake_task.rb +0 -39
- data/lib/euston-daemons/message_buffer_daemon/config/environment.rb +0 -42
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/command_buffer_cleanup.rb +0 -9
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/command_buffer_publisher.rb +0 -9
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/command_logger.rb +0 -9
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/euston_exchange_accessors.rb +0 -15
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/event_buffer_cleanup.rb +0 -9
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/event_buffer_publisher.rb +0 -9
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/event_logger.rb +0 -9
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/event_store_dispatcher.rb +0 -25
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/message_buffer_cleanup.rb +0 -52
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/message_buffer_publisher.rb +0 -37
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/message_logger.rb +0 -45
- data/lib/euston-daemons/message_buffer_daemon/lib/clients/mongo_model_accessors.rb +0 -21
- data/lib/euston-daemons/message_buffer_daemon/lib/daemon.rb +0 -17
- data/lib/euston-daemons/message_buffer_daemon/lib/mongo_models/command_buffer.rb +0 -11
- data/lib/euston-daemons/message_buffer_daemon/lib/mongo_models/command_log.rb +0 -11
- data/lib/euston-daemons/message_buffer_daemon/lib/mongo_models/event_buffer.rb +0 -11
- data/lib/euston-daemons/message_buffer_daemon/lib/mongo_models/event_log.rb +0 -11
- data/lib/euston-daemons/message_buffer_daemon/lib/mongo_models/message_buffer.rb +0 -57
- data/lib/euston-daemons/message_buffer_daemon/lib/mongo_models/message_log.rb +0 -29
- data/lib/euston-daemons/message_buffer_daemon/rake_task.rb +0 -32
- data/lib/euston-daemons/snapshot_daemon/lib/clients/snapshotter.rb +0 -43
- data/sample/Rakefile +0 -63
- data/sample/amqp_config.yml +0 -14
- data/sample/command_handlers.rb +0 -17
- data/sample/command_processor_daemon_config.yml +0 -9
- data/sample/event_handlers.rb +0 -21
- data/sample/event_processor_daemon_config.yml +0 -8
- data/sample/message_buffer_daemon_config.yml +0 -8
- data/sample/mongoid_config.yml +0 -13
- data/sample/pids/.placeholder +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
class Daemon < Euston::Daemon
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def env
|
|
8
|
+
DaemonEnvironment
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def post_shutdown_cleanup
|
|
12
|
+
env.amqp_connection.close
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def pre_registration_setup
|
|
16
|
+
channel = env.amqp_connection.create_channel
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
handler_finder = RabbitMq::HandlerFinder.new [AggregateRoot, Euston::CommandHandler], @log
|
|
20
|
+
handler_finder.namespaces.push Pipeline::CommandProcessor::DefaultHandlers, *DaemonEnvironment.command_handler_namespaces
|
|
21
|
+
@command_handler_references = handler_finder.find
|
|
22
|
+
|
|
23
|
+
binder = RabbitMq::CommandHandlerBinder.new channel, @command_handler_references, @log
|
|
24
|
+
binder.ensure_bindings_exist
|
|
25
|
+
|
|
26
|
+
handler_finder = RabbitMq::HandlerFinder.new [Euston::EventHandler], @log
|
|
27
|
+
handler_finder.namespaces.push Pipeline::EventProcessor::DefaultHandlers, *DaemonEnvironment.event_handler_namespaces
|
|
28
|
+
@event_handler_references = handler_finder.find
|
|
29
|
+
|
|
30
|
+
binder = RabbitMq::EventHandlerBinder.new channel, @event_handler_references, @log
|
|
31
|
+
binder.ensure_bindings_exist
|
|
32
|
+
rescue NativeException => e
|
|
33
|
+
@log.error "Caught the following native exception:\n\n#{e}\n\nDid you forget to create your rabbitmq vhost and user for this environment?"
|
|
34
|
+
raise e
|
|
35
|
+
ensure
|
|
36
|
+
channel.close
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def register_components
|
|
41
|
+
(1..env.command_loggers).each do |i|
|
|
42
|
+
component = CommandLogger::Component.new env.amqp_connection.create_channel, i, @log
|
|
43
|
+
register_component "command_logger_#{i}".to_sym, component
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
(1..env.command_processors).each do |i|
|
|
47
|
+
component = CommandProcessor::Component.new env.amqp_connection.create_channel, @command_handler_references, i, @log
|
|
48
|
+
register_component "command_processor_#{i}".to_sym, component
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
(1..env.event_store_dispatchers).each do |i|
|
|
52
|
+
component = EventStoreDispatcher::Component.new env.amqp_connection.create_channel, "#{Mac.addr.to_s} #{i}", @log
|
|
53
|
+
component = register_component "event_store_dispatcher_#{i}", component
|
|
54
|
+
component.wait_time = env.event_store_dispatcher_wait_time
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
(1..env.message_buffers).each do |i|
|
|
58
|
+
component = MessageBuffer::Component.new env.amqp_connection.create_channel, "#{Mac.addr.to_s} #{i}", @log
|
|
59
|
+
component = register_component "message_buffer_#{i}", component
|
|
60
|
+
component.wait_time = env.message_buffer_wait_time
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
(1..env.snapshotters).each do |i|
|
|
64
|
+
component = Snapshotter::Component.new Euston::Repository.event_store, env.snapshot_threshold, i, @log
|
|
65
|
+
component = register_component "snapshotter_#{i}", component
|
|
66
|
+
component.wait_time = env.snapshotter_wait_time
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
(1..env.event_processors).each do |i|
|
|
70
|
+
component = EventProcessor::Component.new env.amqp_connection.create_channel, @event_handler_references, i, @log
|
|
71
|
+
register_component "event_processor_#{i}".to_sym, component
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
module EventProcessor
|
|
5
|
+
class Component < Euston::DaemonComponent
|
|
6
|
+
include Euston::EventHandlerPrivateMethodNames
|
|
7
|
+
|
|
8
|
+
def initialize channel, references, id = 1, logger = Euston::NullLogger.instance
|
|
9
|
+
@channel = channel
|
|
10
|
+
@channel.prefetch = 1
|
|
11
|
+
@id = id
|
|
12
|
+
@references = references
|
|
13
|
+
@log = logger
|
|
14
|
+
@process_method = method(:process_message)
|
|
15
|
+
@stopwatch = Stopwatch.new.when(:finished => method(:log_elapsed_time))
|
|
16
|
+
|
|
17
|
+
@subscriptions = @references.map do |r|
|
|
18
|
+
{ :reference => r,
|
|
19
|
+
:subscription => RabbitMq::RetryingSubscription.new(@channel, r.name.to_s.underscore, @log)
|
|
20
|
+
.when(:message_received => @process_method) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def next_iteration
|
|
27
|
+
@subscriptions.each do |s|
|
|
28
|
+
@reference = s[:reference]
|
|
29
|
+
s[:subscription].get
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def process_message message
|
|
34
|
+
@stopwatch.time do
|
|
35
|
+
event_headers = EventHeaders.from_hash message[:headers]
|
|
36
|
+
event_body = message[:body]
|
|
37
|
+
|
|
38
|
+
handler_is_aggregate_root = @reference.handler.include? Euston::AggregateRoot
|
|
39
|
+
|
|
40
|
+
if handler_is_aggregate_root
|
|
41
|
+
id_getter_method = self.class.id_from_event_method_name event_headers.type, event_headers.version
|
|
42
|
+
id = @reference.handler.send id_getter_method, event_body
|
|
43
|
+
|
|
44
|
+
raise "Unable to extract the aggregate root id from an #{event_headers.type} event. Did you forget to add an { :id => :field_name } argument to your subscribes block?" if id.nil?
|
|
45
|
+
|
|
46
|
+
handler_instance = Euston::Repository.find @reference.handler, id
|
|
47
|
+
handler_instance.log = @log
|
|
48
|
+
handler_instance.consume_event_subscription event_headers.freeze, event_body.freeze
|
|
49
|
+
|
|
50
|
+
Euston::Repository.save handler_instance
|
|
51
|
+
else
|
|
52
|
+
handler_method = self.class.event_handler_method_name event_headers.type, event_headers.version
|
|
53
|
+
handler_instance = @reference.handler.new
|
|
54
|
+
handler_instance.log = @log if handler_instance.respond_to? :log=
|
|
55
|
+
handler_instance.send handler_method, event_headers.freeze, event_body.freeze
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def log_elapsed_time elapsed_time
|
|
61
|
+
@log.debug "Event handler client #{@reference.name.to_s.underscore} processed a message in #{elapsed_time} sec(s)"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
module EventProcessor
|
|
5
|
+
module DefaultHandlers
|
|
6
|
+
class MessageFailure
|
|
7
|
+
include Euston::EventHandler
|
|
8
|
+
|
|
9
|
+
subscribes :message_failed, 1 do |headers, event|
|
|
10
|
+
headers = event[:message][:headers].dup
|
|
11
|
+
failure = { :message_id => headers.delete(:id),
|
|
12
|
+
:type => headers.delete(:type),
|
|
13
|
+
:version => headers.delete(:version),
|
|
14
|
+
:message_timestamp => headers.delete(:timestamp),
|
|
15
|
+
:routing_key => event[:routing_key],
|
|
16
|
+
:body => event[:message][:body],
|
|
17
|
+
:headers => headers,
|
|
18
|
+
:error => event[:error],
|
|
19
|
+
:backtrace => event[:backtrace],
|
|
20
|
+
:failure_timestamp => Time.now.to_f }
|
|
21
|
+
|
|
22
|
+
failed_messages = CommandProcessor::FailedMessage.new DaemonEnvironment.event_store_mongodb
|
|
23
|
+
failed_messages.log_failure failure
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
module EventStoreDispatcher
|
|
5
|
+
class Component < DaemonComponent
|
|
6
|
+
extend RabbitMq::Exchanges
|
|
7
|
+
|
|
8
|
+
def initialize channel, id = 1, log = Euston::NullLogger.instance
|
|
9
|
+
@channel = channel
|
|
10
|
+
@channel.tx_select
|
|
11
|
+
@id = id
|
|
12
|
+
@log = log
|
|
13
|
+
@event_store = DaemonEnvironment.event_store
|
|
14
|
+
@stopwatch = Stopwatch.new.when(:finished => method(:log_elapsed_time))
|
|
15
|
+
@commands_exchange = self.class.get_exchange channel, :commands
|
|
16
|
+
@events_exchange = self.class.get_exchange channel, :events
|
|
17
|
+
@buffer = MessageBuffer::Buffer.new DaemonEnvironment.event_store_mongodb
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def log_elapsed_time elapsed_time
|
|
23
|
+
@log.debug "Event store dispatcher #{@id} dispatched #{@commits_dispatched} commit(s) in #{elapsed_time} sec(s)" if @commits_dispatched > 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def next_iteration
|
|
27
|
+
@commits_dispatched = 0
|
|
28
|
+
|
|
29
|
+
@stopwatch.time do
|
|
30
|
+
begin
|
|
31
|
+
@event_store.take_ownership_of_undispatched_commits @id
|
|
32
|
+
@commits = @event_store.get_undispatched_commits @id
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
@commits.each do |commit|
|
|
36
|
+
commit.events.each do |event|
|
|
37
|
+
hash = event.to_hash
|
|
38
|
+
@events_exchange.publish ActiveSupport::JSON.encode(hash), self.class.default_publish_options.merge(:routing_key => "events.#{hash[:headers][:type]}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
commit.commands.each do |command|
|
|
42
|
+
hash = command.to_hash
|
|
43
|
+
|
|
44
|
+
if hash[:headers][:dispatch_at].nil?
|
|
45
|
+
@commands_exchange.publish ActiveSupport::JSON.encode(hash), self.class.default_publish_options.merge(:routing_key => "commands.#{hash[:headers][:type]}")
|
|
46
|
+
else
|
|
47
|
+
@buffer.enqueue :commands, hash, hash[:headers][:dispatch_at]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@channel.tx_commit
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
@channel.tx_rollback
|
|
55
|
+
raise e
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
@event_store.mark_commits_as_dispatched @commits
|
|
59
|
+
|
|
60
|
+
@commits_dispatched += @commits.size
|
|
61
|
+
end until stopped || @commits.empty?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
module MessageBuffer
|
|
5
|
+
class Buffer
|
|
6
|
+
def initialize mongodb
|
|
7
|
+
name = 'message_buffer'
|
|
8
|
+
mongodb.create_collection name unless mongodb.collection_names.include? name
|
|
9
|
+
|
|
10
|
+
@name = name
|
|
11
|
+
@collection = mongodb.collection name
|
|
12
|
+
|
|
13
|
+
@collection.ensure_index [ ['message_id', Mongo::ASCENDING] ],
|
|
14
|
+
:unique => false,
|
|
15
|
+
:name => "#{name}_message_id_index"
|
|
16
|
+
|
|
17
|
+
@collection.ensure_index [ ['component_id', Mongo::ASCENDING],
|
|
18
|
+
['dispatch_at', Mongo::ASCENDING] ],
|
|
19
|
+
:unique => false,
|
|
20
|
+
:name => "#{name}_component_id_dispatch_at_index"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :name
|
|
24
|
+
|
|
25
|
+
def delete_dispatched_messages component_id
|
|
26
|
+
@collection.remove({ 'component_id' => component_id }, :multi => true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def enqueue exchange, message, dispatch_at = nil
|
|
30
|
+
messages = message
|
|
31
|
+
messages = [{ :hash => message, :dispatch_at => dispatch_at }] unless messages.is_a? Array
|
|
32
|
+
|
|
33
|
+
messages = messages.map do |m|
|
|
34
|
+
message_is_well_formed = m.is_a?(Hash) && m.has_key?(:hash) && m.has_key?(:dispatch_at)
|
|
35
|
+
m = { :hash => m, :dispatch_at => dispatch_at } unless message_is_well_formed
|
|
36
|
+
map_to_document exchange, m
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@collection.insert(messages) unless messages.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find_dispatchable_messages component_id
|
|
43
|
+
query = { 'component_id' => component_id }
|
|
44
|
+
fields = ['exchange', 'type', 'json']
|
|
45
|
+
sort = [ 'dispatch_at', Mongo::ASCENDING ]
|
|
46
|
+
|
|
47
|
+
@collection.find query, :fields => fields, :sort => sort
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get_by_id id
|
|
51
|
+
@collection.find_one 'message_id' => id
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def take_ownership_of_dispatchable_messages component_id
|
|
55
|
+
new_messages_eligible_for_dispatch = { 'component_id' => '',
|
|
56
|
+
'dispatch_at' => { '$lte' => Time.now.to_f } }
|
|
57
|
+
|
|
58
|
+
messages_stuck_in_other_components = { 'component_id' => { '$ne' => '' },
|
|
59
|
+
'dispatch_at' => { '$lte' => Time.now.to_f - 60 } }
|
|
60
|
+
|
|
61
|
+
query = { '$or' => [ new_messages_eligible_for_dispatch, messages_stuck_in_other_components ] }
|
|
62
|
+
doc = { '$set' => { 'component_id' => component_id } }
|
|
63
|
+
|
|
64
|
+
@collection.update query, doc, :multi => true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def map_to_document exchange, message
|
|
70
|
+
hash = message[:hash]
|
|
71
|
+
dispatch_at = (message[:dispatch_at] || Time.now.to_f).to_f
|
|
72
|
+
|
|
73
|
+
{ 'message_id' => hash[:headers][:id],
|
|
74
|
+
'exchange' => exchange,
|
|
75
|
+
'type' => hash[:headers][:type],
|
|
76
|
+
'component_id' => '',
|
|
77
|
+
'dispatch_at' => dispatch_at,
|
|
78
|
+
'dispatch_at_for_humans' => Time.at(dispatch_at),
|
|
79
|
+
'json' => ActiveSupport::JSON.encode(hash) }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
module MessageBuffer
|
|
5
|
+
class Component < Euston::DaemonComponent
|
|
6
|
+
extend RabbitMq::Exchanges
|
|
7
|
+
|
|
8
|
+
def initialize channel, id = 1, logger = Euston::NullLogger.instance
|
|
9
|
+
@channel = channel
|
|
10
|
+
@channel.tx_select
|
|
11
|
+
@id = "message_buffer #{id}"
|
|
12
|
+
@log = logger
|
|
13
|
+
@buffer = Buffer.new DaemonEnvironment.event_store_mongodb
|
|
14
|
+
@stopwatch = Stopwatch.new.when(:finished => method(:log_elapsed_time))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def dispatch_due_messages
|
|
20
|
+
@dispatched_count = 0
|
|
21
|
+
|
|
22
|
+
@stopwatch.time do
|
|
23
|
+
@buffer.take_ownership_of_dispatchable_messages @id
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
@buffer.find_dispatchable_messages(@id).each do |message|
|
|
27
|
+
exchange = self.class.get_exchange @channel, message['exchange']
|
|
28
|
+
exchange.publish message['json'], self.class.default_publish_options.merge(:routing_key => "#{exchange.name}.#{message['type']}")
|
|
29
|
+
@dispatched_count += 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@channel.tx_commit
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
@channel.tx_rollback
|
|
35
|
+
raise e
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@buffer.delete_dispatched_messages @id
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@dispatched_count
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def log_elapsed_time elapsed_time
|
|
45
|
+
@log.debug "Message buffer #{@id} dispatched #{@dispatched_count} message(s) in #{elapsed_time} sec(s)" if @dispatched_count > 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def next_iteration
|
|
49
|
+
@messages_dispatched = 0
|
|
50
|
+
|
|
51
|
+
begin
|
|
52
|
+
@messages_dispatched = dispatch_due_messages
|
|
53
|
+
end until stopped || @messages_dispatched.zero?
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
module Daemons
|
|
3
|
+
module Pipeline
|
|
4
|
+
module Snapshotter
|
|
5
|
+
class Component < Euston::DaemonComponent
|
|
6
|
+
def initialize event_store, threshold, id = 1, logger = Euston::NullLogger.instance
|
|
7
|
+
@event_store = event_store
|
|
8
|
+
@threshold = threshold
|
|
9
|
+
@id = id
|
|
10
|
+
@log = logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def next_iteration
|
|
16
|
+
begin
|
|
17
|
+
stream_heads = @event_store.get_streams_to_snapshot @threshold
|
|
18
|
+
@log.debug "Found #{stream_heads.length} stream(s) eligible for snapshotting (threshold is #{@threshold})" if stream_heads.any?
|
|
19
|
+
|
|
20
|
+
stream_heads.each do |stream_head|
|
|
21
|
+
pair = @event_store.get_snapshot_stream_pair stream_head.stream_id
|
|
22
|
+
|
|
23
|
+
loader = RabbitMq::ConstantLoader.new
|
|
24
|
+
loader.when(:hit => ->(klass) { take_snapshot klass, pair },
|
|
25
|
+
:miss => ->(type) { Safely.report! "Snapshotter was unable to find a class: #{type}" })
|
|
26
|
+
|
|
27
|
+
loader.load pair.stream.committed_headers[:aggregate_type]
|
|
28
|
+
end
|
|
29
|
+
end until stopped || stream_heads.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def take_snapshot klass, pair
|
|
33
|
+
instance = klass.hydrate pair.stream, pair.snapshot
|
|
34
|
+
snapshot = instance.take_snapshot
|
|
35
|
+
snapshot = EventStore::Snapshot.new pair.stream.stream_id,
|
|
36
|
+
pair.stream.stream_revision,
|
|
37
|
+
snapshot[:payload],
|
|
38
|
+
:version => snapshot[:version]
|
|
39
|
+
|
|
40
|
+
@log.debug "Writing snapshot: #{snapshot.inspect}"
|
|
41
|
+
|
|
42
|
+
@event_store.add_snapshot snapshot
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Euston
|
|
2
|
+
class PipelineRakeTask < Euston::Daemons::RakeTask
|
|
3
|
+
attr_accessor :amqp_config_path,
|
|
4
|
+
:command_handler_namespaces,
|
|
5
|
+
:daemon_config_path,
|
|
6
|
+
:event_handler_namespaces,
|
|
7
|
+
:mongo_config_path
|
|
8
|
+
|
|
9
|
+
def initialize environment
|
|
10
|
+
super(environment, :pipeline_daemon)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def before_creating_task
|
|
14
|
+
@daemon_path = File.expand_path(File.dirname __FILE__) + File::SEPARATOR
|
|
15
|
+
@daemon_class = 'Euston::Daemons::Pipeline::Daemon'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load_daemon_config_file
|
|
19
|
+
@data[:daemon_config] = ErbYaml.read @daemon_config_path, @data[:environment]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize_settings
|
|
23
|
+
@logger.debug "AMQP config path: #{@amqp_config_path}"
|
|
24
|
+
@data[:amqp_config_path] = @amqp_config_path
|
|
25
|
+
|
|
26
|
+
@logger.debug "Command handler namespaces: #{@command_handler_namespaces}"
|
|
27
|
+
@data[:command_handler_namespaces] = @command_handler_namespaces
|
|
28
|
+
|
|
29
|
+
@logger.debug "Daemon config path: #{@daemon_config_path}"
|
|
30
|
+
|
|
31
|
+
@logger.debug "Event handler namespaces: #{@event_handler_namespaces}"
|
|
32
|
+
@data[:event_handler_namespaces] = @event_handler_namespaces
|
|
33
|
+
|
|
34
|
+
@logger.debug "Mongo config path: #{@mongo_config_path}"
|
|
35
|
+
@data[:mongo_config_path] = @mongo_config_path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def load_environment
|
|
39
|
+
@logger.debug "Loading environment"
|
|
40
|
+
require 'euston-daemons/pipeline/config/environment'
|
|
41
|
+
|
|
42
|
+
Euston::Daemons::Pipeline::DaemonEnvironment.new(@data).setup
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -15,11 +15,8 @@ module Euston
|
|
|
15
15
|
# Daemon class. Must be supplied as a string.
|
|
16
16
|
attr_accessor :daemon_class
|
|
17
17
|
|
|
18
|
-
#
|
|
19
|
-
attr_accessor :
|
|
20
|
-
|
|
21
|
-
# # Path to write the daemon pid file to
|
|
22
|
-
attr_accessor :pid_path
|
|
18
|
+
# Callable. Receives the daemon environment object to allow the user to perform other related config operations.
|
|
19
|
+
attr_accessor :post_setup_callback
|
|
23
20
|
|
|
24
21
|
# Use verbose output. If this is set to true, the task will print the
|
|
25
22
|
# executed command to stdout.
|
|
@@ -40,11 +37,13 @@ module Euston
|
|
|
40
37
|
desc("Run a Euston daemon") unless ::Rake.application.last_comment
|
|
41
38
|
task name do
|
|
42
39
|
RakeFileUtils.send(:verbose, verbose) do
|
|
40
|
+
load_daemon_config_file
|
|
43
41
|
initialize_logger
|
|
42
|
+
initialize_settings
|
|
44
43
|
write_pid_file
|
|
45
44
|
log_startup
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
env = load_environment
|
|
46
|
+
post_setup_callback.call env unless post_setup_callback.nil?
|
|
48
47
|
launch_and_wait_for_exit
|
|
49
48
|
log_shutdown
|
|
50
49
|
remove_pid_file
|
|
@@ -54,16 +53,19 @@ module Euston
|
|
|
54
53
|
|
|
55
54
|
private
|
|
56
55
|
|
|
57
|
-
def validate_environment environment
|
|
58
|
-
environment = environment.to_s.downcase.to_sym
|
|
59
|
-
environments = [:development, :test, :staging, :production]
|
|
60
|
-
environment = :development unless environments.include? environment
|
|
61
|
-
environment
|
|
62
|
-
end
|
|
63
|
-
|
|
64
56
|
def initialize_logger
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
config = @data[:daemon_config]
|
|
58
|
+
log_path = config[:log_path]
|
|
59
|
+
|
|
60
|
+
raise "Required log path does not exist: #{log_path}" unless Dir.exist? log_path
|
|
61
|
+
|
|
62
|
+
@data[:logger] = @logger = Logger.new(File.join log_path, "#{@name}.#{@environment}.log")
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
@logger.level = Logger.const_get config[:log_level].upcase.to_sym
|
|
66
|
+
rescue
|
|
67
|
+
@logger.level = Logger::DEBUG
|
|
68
|
+
end
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def initialize_settings
|
|
@@ -81,6 +83,14 @@ module Euston
|
|
|
81
83
|
ns.new(@data).run
|
|
82
84
|
end
|
|
83
85
|
|
|
86
|
+
def load_daemon_config_file
|
|
87
|
+
# virtual
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_environment
|
|
91
|
+
# virtual
|
|
92
|
+
end
|
|
93
|
+
|
|
84
94
|
def log_shutdown
|
|
85
95
|
print_log_banner "Daemon shut down: #{@daemon_class}"
|
|
86
96
|
end
|
|
@@ -105,16 +115,27 @@ module Euston
|
|
|
105
115
|
File.delete @pid_file rescue Errno::ENOENT
|
|
106
116
|
end
|
|
107
117
|
|
|
118
|
+
def validate_environment environment
|
|
119
|
+
environment = environment.to_s.downcase.to_sym
|
|
120
|
+
environments = [:development, :test, :staging, :production]
|
|
121
|
+
environment = :development unless environments.include? environment
|
|
122
|
+
environment
|
|
123
|
+
end
|
|
124
|
+
|
|
108
125
|
def write_pid_file
|
|
109
|
-
|
|
126
|
+
pid_path = @data[:daemon_config][:pid_path]
|
|
127
|
+
|
|
128
|
+
raise "Required pid path does not exist: #{pid_path}" unless Dir.exist? pid_path
|
|
129
|
+
|
|
130
|
+
@pid_file = File.join pid_path, "#{@name}.#{@environment}.pid"
|
|
110
131
|
|
|
111
132
|
if defined? Java
|
|
112
|
-
@pid =
|
|
133
|
+
@pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split('@').first
|
|
113
134
|
else
|
|
114
135
|
@pid = File.readlink("/proc/self")
|
|
115
136
|
end
|
|
116
137
|
|
|
117
|
-
@logger.
|
|
138
|
+
@logger.error "Writing pid #{@pid} to #{@pid_file}"
|
|
118
139
|
|
|
119
140
|
File.open(@pid_file, 'w') { |f| f.puts @pid }
|
|
120
141
|
end
|
|
@@ -2,6 +2,4 @@ require 'euston-daemons'
|
|
|
2
2
|
|
|
3
3
|
require 'rake/tasklib'
|
|
4
4
|
require 'euston-daemons/rake_task'
|
|
5
|
-
require 'euston-daemons/
|
|
6
|
-
require 'euston-daemons/event_processor_daemon/rake_task'
|
|
7
|
-
require 'euston-daemons/message_buffer_daemon/rake_task'
|
|
5
|
+
require 'euston-daemons/pipeline/rake_task'
|
data/lib/euston-daemons.rb
CHANGED
|
@@ -10,7 +10,9 @@ if RUBY_PLATFORM.to_s == 'java'
|
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
require 'euston-daemons/euston/stopwatch'
|
|
14
|
+
require 'euston-daemons/euston/exceptions'
|
|
13
15
|
require 'euston-daemons/euston/daemon_environment'
|
|
14
|
-
require 'euston-daemons/euston/daemon_client'
|
|
15
16
|
require 'euston-daemons/euston/daemon_component'
|
|
17
|
+
require 'euston-daemons/euston/daemon_component_host'
|
|
16
18
|
require 'euston-daemons/euston/daemon'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
describe 'command handler
|
|
2
|
-
require 'euston-daemons/
|
|
1
|
+
describe 'command handler component', :purge_event_store, :purge_rabbitmq do
|
|
2
|
+
require 'euston-daemons/pipeline/lib/command_processor/component'
|
|
3
3
|
|
|
4
4
|
let(:logger) { Euston::NullLogger.instance }
|
|
5
5
|
let(:handlers) { [] }
|
|
6
6
|
let(:subscription) { StubRetryingSubscription.new }
|
|
7
|
-
let(:client) { Euston::
|
|
7
|
+
let(:client) { Euston::Daemons::Pipeline::CommandProcessor::Component.new @channel, handlers, 1, logger }
|
|
8
8
|
let(:aggregate) { Euston::Daemons::SampleModel::Counter2.new }
|
|
9
9
|
let(:saved_aggregates) { [] }
|
|
10
10
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
describe 'event handler client', :purge_event_store, :purge_rabbitmq do
|
|
2
|
-
require 'euston-daemons/
|
|
2
|
+
require 'euston-daemons/pipeline/lib/event_processor/component'
|
|
3
3
|
|
|
4
4
|
let(:logger) { Euston::NullLogger.instance }
|
|
5
5
|
let(:subscription) { StubRetryingSubscription.new }
|
|
6
|
-
let(:client) { Euston::
|
|
6
|
+
let(:client) { Euston::Daemons::Pipeline::EventProcessor::Component.new @channel, reference, 1, logger }
|
|
7
7
|
let(:aggregate) { Euston::Daemons::SampleModel::Counter2.new }
|
|
8
8
|
let(:saved_aggregates) { [] }
|
|
9
9
|
|