euston-daemons 1.0.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 +4 -0
- data/Gemfile.lock +101 -0
- data/LICENSE +19 -0
- data/Rakefile +161 -0
- data/euston-daemons.gemspec +69 -0
- data/lib/euston-daemons.rb +15 -0
- data/lib/euston-daemons/command_processor_daemon/config/environment.rb +14 -0
- data/lib/euston-daemons/command_processor_daemon/lib/components/command_handler_component.rb +55 -0
- data/lib/euston-daemons/command_processor_daemon/lib/daemon.rb +43 -0
- data/lib/euston-daemons/command_processor_daemon/lib/settings.rb +19 -0
- data/lib/euston-daemons/command_processor_daemon/rake_task.rb +31 -0
- data/lib/euston-daemons/event_processor_daemon/config/environment.rb +20 -0
- data/lib/euston-daemons/event_processor_daemon/lib/components/event_handler_component.rb +72 -0
- data/lib/euston-daemons/event_processor_daemon/lib/daemon.rb +72 -0
- data/lib/euston-daemons/event_processor_daemon/lib/settings.rb +22 -0
- data/lib/euston-daemons/event_processor_daemon/rake_task.rb +37 -0
- data/lib/euston-daemons/framework/basic_component.rb +33 -0
- data/lib/euston-daemons/framework/component_shutdown.rb +22 -0
- data/lib/euston-daemons/framework/daemon.rb +27 -0
- data/lib/euston-daemons/framework/queue.rb +71 -0
- data/lib/euston-daemons/message_buffer_daemon/config/environment.rb +26 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/components/buffer_component.rb +74 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/components/event_store_component.rb +51 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/daemon.rb +48 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/message_logger.rb +54 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/publisher.rb +56 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/read_model/message_buffer.rb +54 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/read_model/message_log.rb +36 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/settings.rb +14 -0
- data/lib/euston-daemons/message_buffer_daemon/lib/subscriber.rb +60 -0
- data/lib/euston-daemons/message_buffer_daemon/rake_task.rb +30 -0
- data/lib/euston-daemons/rake_task.rb +116 -0
- data/lib/euston-daemons/version.rb +5 -0
- metadata +235 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require_rel '../lib'
|
2
|
+
|
3
|
+
Safely::Strategy::Log.logger = EUSTON_LOG
|
4
|
+
Cqrs.uuid = Uuid
|
5
|
+
AMQP.settings.merge! ErbYaml.read(AMQP_CONFIG_PATH, EUSTON_ENV)
|
6
|
+
Euston::MessageBufferDaemon::Settings.configure ErbYaml.read(DAEMON_CONFIG_PATH, EUSTON_ENV)
|
7
|
+
|
8
|
+
hash = ErbYaml.read(MONGOID_CONFIG_PATH, EUSTON_ENV)
|
9
|
+
|
10
|
+
event_connection = Mongo::Connection.new hash[:host], hash[:port], :safe => hash[:safe] #, :logger => EUSTON_LOG
|
11
|
+
read_connection = Mongo::Connection.new hash[:host], hash[:port], :safe => hash[:safe] #, :logger => EUSTON_LOG
|
12
|
+
|
13
|
+
event_database = hash[:event_store_database]
|
14
|
+
|
15
|
+
Cqrs::RabbitMq.event_store_mongodb = Mongo::DB.new event_database, event_connection
|
16
|
+
Cqrs::RabbitMq.read_model_mongodb = Mongo::DB.new hash[:read_model_database], read_connection
|
17
|
+
|
18
|
+
EventStore::Persistence::Mongodb::Config.instance.logger = EUSTON_LOG
|
19
|
+
EventStore::Persistence::Mongodb::Config.instance.database = event_database
|
20
|
+
|
21
|
+
Cqrs::RabbitMq.event_store = EventStore::Persistence::Mongodb::MongoPersistenceFactory.build
|
22
|
+
Cqrs::RabbitMq.event_store.init
|
23
|
+
|
24
|
+
Cqrs::Repository.event_store = EventStore::OptimisticEventStore.new Cqrs::RabbitMq.event_store
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
class BufferComponent
|
4
|
+
include Euston::Daemons::ComponentShutdown
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def command_component pub_channel, sub_channel
|
8
|
+
self.new Publisher.commands_buffer(pub_channel), Subscriber.commands_buffer(sub_channel)
|
9
|
+
end
|
10
|
+
|
11
|
+
def event_component pub_channel, sub_channel
|
12
|
+
self.new Publisher.events_buffer(pub_channel), Subscriber.events_buffer(sub_channel)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :pub_thread, :sub_thread
|
17
|
+
attr_writer :wait_time, :subscribe_timeout
|
18
|
+
|
19
|
+
def initialize pub_buffer, sub_buffer
|
20
|
+
@pub_buffer = pub_buffer
|
21
|
+
@sub_buffer = sub_buffer
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait_time
|
25
|
+
@wait_time ||= 0.2
|
26
|
+
end
|
27
|
+
|
28
|
+
def subscribe_timeout
|
29
|
+
@subscribe_timeout ||= 2000
|
30
|
+
end
|
31
|
+
|
32
|
+
def thread_state
|
33
|
+
{:publish => @pub_thread.status, :subscribe => @sub_thread.status}
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
@pub_thread[:stop], @sub_thread[:stop] = true, true
|
38
|
+
@pub_buffer.disconnect
|
39
|
+
@sub_buffer.disconnect
|
40
|
+
end
|
41
|
+
|
42
|
+
def start
|
43
|
+
@pub_thread = Thread.new do
|
44
|
+
until Thread.current[:stop] do
|
45
|
+
begin
|
46
|
+
@msg_dispatched_count = 0
|
47
|
+
begin
|
48
|
+
@msg_dispatched_count = @pub_buffer.dispatch_due_messages
|
49
|
+
check_exception_and_shutdown
|
50
|
+
end until Thread.current[:stop] || @msg_dispatched_count.zero?
|
51
|
+
rescue => e
|
52
|
+
Thread.current[:exception] = e
|
53
|
+
end
|
54
|
+
check_exception_and_shutdown
|
55
|
+
sleep self.wait_time
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@sub_thread = Thread.new do
|
60
|
+
until Thread.current[:stop] do
|
61
|
+
begin
|
62
|
+
@sub_buffer.dequeue_buffered_messages(self.subscribe_timeout)
|
63
|
+
check_exception_and_shutdown
|
64
|
+
rescue => e
|
65
|
+
Thread.current[:exception] = e
|
66
|
+
end
|
67
|
+
check_exception_and_shutdown
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
class EventStoreComponent
|
4
|
+
include Euston::Daemons::ComponentShutdown
|
5
|
+
|
6
|
+
attr_reader :cli_thread
|
7
|
+
attr_writer :wait_time
|
8
|
+
|
9
|
+
def initialize channel
|
10
|
+
@channel = channel
|
11
|
+
end
|
12
|
+
|
13
|
+
def wait_time
|
14
|
+
@wait_time ||= 0.2
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
@cli_thread = Thread.new do
|
19
|
+
@event_buffer = Euston::MessageBufferDaemon::ReadModel::MessageBuffer.events
|
20
|
+
@event_store = Cqrs::RabbitMq.event_store
|
21
|
+
until Thread.current[:stop] do
|
22
|
+
begin
|
23
|
+
loop do
|
24
|
+
commits = @event_store.get_undispatched_commits
|
25
|
+
break if commits.empty?
|
26
|
+
commits.each do |commit|
|
27
|
+
commit.events.each { |event| @event_buffer.buffer_new_message event.to_hash }
|
28
|
+
@event_store.mark_commit_as_dispatched commit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
Thread.current[:exception] = e
|
33
|
+
end
|
34
|
+
check_exception_and_shutdown
|
35
|
+
sleep self.wait_time
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def thread_state
|
41
|
+
@cli_thread.status
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
@cli_thread[:stop] = true
|
46
|
+
@channel.disconnect
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
class Daemon
|
4
|
+
include Euston::Daemon
|
5
|
+
|
6
|
+
attr_reader :clients, :queue
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
@queue = Queue.new
|
10
|
+
|
11
|
+
@clients = {
|
12
|
+
:commands_logger => Euston::Daemons::BasicComponent.new(MessageLogger.commands_logger AMQP::Channel.new),
|
13
|
+
:events_logger => Euston::Daemons::BasicComponent.new(MessageLogger.events_logger AMQP::Channel.new),
|
14
|
+
:command_buffer_handler => BufferComponent.command_component(AMQP::Channel.new, AMQP::Channel.new),
|
15
|
+
:event_buffer_handler => BufferComponent.event_component(AMQP::Channel.new, AMQP::Channel.new),
|
16
|
+
:event_store_dispatcher => EventStoreComponent.new(AMQP::Channel.new)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
Thread.abort_on_exception = true
|
22
|
+
|
23
|
+
@clients.each do |name, component|
|
24
|
+
EUSTON_LOG.debug("Starting component: #{name}")
|
25
|
+
component.daemon = self
|
26
|
+
component.start
|
27
|
+
sleep 0.350
|
28
|
+
end
|
29
|
+
|
30
|
+
@clients.each do |name, component|
|
31
|
+
EUSTON_LOG.debug("Thread state of #{name}: #{component.thread_state.inspect}")
|
32
|
+
end
|
33
|
+
|
34
|
+
EUSTON_LOG.debug "Components started"
|
35
|
+
|
36
|
+
@queue.pop #<-------- stops here until interrupted
|
37
|
+
|
38
|
+
@clients.each do |name, component|
|
39
|
+
EUSTON_LOG.debug "Stopping component: #{name}"
|
40
|
+
component.stop
|
41
|
+
end
|
42
|
+
|
43
|
+
report_shutdown_reasons
|
44
|
+
EUSTON_LOG.debug "Components stopped"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Euston
|
2
|
+
class MessageLogger
|
3
|
+
class << self
|
4
|
+
def commands_logger channel
|
5
|
+
self.new channel, :commands
|
6
|
+
end
|
7
|
+
|
8
|
+
def events_logger channel
|
9
|
+
self.new channel, :events
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
include Cqrs::RabbitMq::Exchanges
|
14
|
+
include Cqrs::RabbitMq::Queues
|
15
|
+
|
16
|
+
def initialize channel, exchange_name
|
17
|
+
@channel = channel
|
18
|
+
@exchange = get_exchange channel, exchange_name
|
19
|
+
@read_model = Euston::MessageBufferDaemon::ReadModel::MessageLog.send exchange_name
|
20
|
+
|
21
|
+
@queue = get_queue channel, "#{exchange_name}_log"
|
22
|
+
@queue.bind @exchange, :routing_key => "#{exchange_name}.#"
|
23
|
+
|
24
|
+
@queue.when(:message_decode_failed => method(:log_failure),
|
25
|
+
:message_failed => method(:message_failed),
|
26
|
+
:message_received => method(:write_message_to_log))
|
27
|
+
end
|
28
|
+
|
29
|
+
def message_failed(message, error, header)
|
30
|
+
log_failure message, error
|
31
|
+
header.ack
|
32
|
+
end
|
33
|
+
|
34
|
+
def disconnect
|
35
|
+
@channel.disconnect
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
@queue.safe_subscribe
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_failure message, error
|
43
|
+
text = "A log queue subscription failed. [Error] #{error.message} [Payload] #{message}"
|
44
|
+
err = Cqrs::RabbitMq::MessageDecodeFailedError.new text
|
45
|
+
err.set_backtrace error.backtrace
|
46
|
+
|
47
|
+
Safely.report! err
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_message_to_log message
|
51
|
+
@read_model.log_new_message message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
class Publisher
|
4
|
+
class << self
|
5
|
+
def commands_buffer channel
|
6
|
+
self.new channel, :commands
|
7
|
+
end
|
8
|
+
|
9
|
+
def events_buffer channel
|
10
|
+
self.new channel, :events
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
include Cqrs::RabbitMq::Exchanges
|
15
|
+
|
16
|
+
def initialize channel, exchange_name
|
17
|
+
@read_model = Euston::MessageBufferDaemon::ReadModel::MessageBuffer.send exchange_name
|
18
|
+
|
19
|
+
@channel = channel
|
20
|
+
@exchange = get_exchange @channel, exchange_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def disconnect
|
24
|
+
@channel.disconnect
|
25
|
+
end
|
26
|
+
|
27
|
+
def dispatch_one_due_message
|
28
|
+
if (message = @read_model.find_next_message)
|
29
|
+
@read_model.set_next_attempt message
|
30
|
+
protected_publish(message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def dispatch_due_messages
|
35
|
+
dispatched = 0
|
36
|
+
@read_model.find_due_messages.each do |message|
|
37
|
+
@read_model.set_next_attempt message
|
38
|
+
break unless protected_publish(message)
|
39
|
+
dispatched += 1
|
40
|
+
end
|
41
|
+
dispatched
|
42
|
+
end
|
43
|
+
|
44
|
+
def protected_publish(message)
|
45
|
+
ret = true
|
46
|
+
begin
|
47
|
+
@exchange.publish message['json'], default_publish_options.merge(:routing_key => "#{@exchange.name}.#{message['type']}")
|
48
|
+
rescue NativeException => e
|
49
|
+
Thread.current[:exception] = e
|
50
|
+
ret = false
|
51
|
+
end
|
52
|
+
ret
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
module ReadModel
|
4
|
+
class MessageBuffer
|
5
|
+
class << self
|
6
|
+
def commands
|
7
|
+
self.new "buffered_commands", Cqrs::RabbitMq.read_model_mongodb
|
8
|
+
end
|
9
|
+
|
10
|
+
def events
|
11
|
+
self.new "buffered_events", Cqrs::RabbitMq.event_store_mongodb
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize name, mongodb
|
16
|
+
mongodb.create_collection name unless mongodb.collection_names.include? name
|
17
|
+
|
18
|
+
@collection = mongodb.collection name
|
19
|
+
@collection.ensure_index [ ['next_attempt', Mongo::ASCENDING] ], :unique => false, :name => "#{name}_next_attempt_index"
|
20
|
+
end
|
21
|
+
|
22
|
+
def buffer_new_message message
|
23
|
+
@collection.save({ 'doc_id' => message[:headers][:id],
|
24
|
+
'type' => message[:headers][:type],
|
25
|
+
'next_attempt' => (Time.now.to_f - 1.0),
|
26
|
+
'json' => ActiveSupport::JSON.encode(message) }, :safe => { :fsync => true })
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_next_message
|
30
|
+
query = { 'next_attempt' => { '$lte' => Time.now.to_f } }
|
31
|
+
@collection.find_one(query)
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_due_messages
|
35
|
+
query = { 'next_attempt' => { '$lte' => Time.now.to_f }}
|
36
|
+
@collection.find(query)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_by_id id
|
40
|
+
@collection.find_one({ 'doc_id' => id }) #doc or nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_next_attempt message
|
44
|
+
@collection.update({ 'doc_id' => message['doc_id'] },
|
45
|
+
{ '$set' => { 'next_attempt' => Time.now.to_f + 10 } }, :safe => { :fsync => true })
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_published_message id
|
49
|
+
@collection.remove({ 'doc_id' => id }, :safe => { :fsync => true })
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
module ReadModel
|
4
|
+
class MessageLog
|
5
|
+
class << self
|
6
|
+
def commands
|
7
|
+
self.new "command_log", Cqrs::RabbitMq.read_model_mongodb
|
8
|
+
end
|
9
|
+
|
10
|
+
def events
|
11
|
+
self.new "event_log", Cqrs::RabbitMq.event_store_mongodb
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize name, mongodb
|
16
|
+
mongodb.create_collection name unless mongodb.collection_names.include? name
|
17
|
+
|
18
|
+
@collection = mongodb.collection name
|
19
|
+
@collection.ensure_index [ ['timestamp', Mongo::ASCENDING] ], :unique => false, :name => "#{name}_timestamp_index"
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_all
|
23
|
+
@collection.find({}, { :sort => [ 'timestamp', Mongo::DESCENDING ] })
|
24
|
+
end
|
25
|
+
|
26
|
+
def log_new_message message
|
27
|
+
@collection.save({ '_id' => message[:headers][:id],
|
28
|
+
'type' => message[:headers][:type],
|
29
|
+
'version' => message[:headers][:version],
|
30
|
+
'timestamp' => Time.now.to_f,
|
31
|
+
'json' => ActiveSupport::JSON.encode(message) }, :safe => { :fsync => true })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Euston
|
2
|
+
module MessageBufferDaemon
|
3
|
+
class Subscriber
|
4
|
+
class << self
|
5
|
+
def commands_buffer channel
|
6
|
+
self.new channel, :commands
|
7
|
+
end
|
8
|
+
|
9
|
+
def events_buffer channel
|
10
|
+
self.new channel, :events
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
include Cqrs::RabbitMq::Exchanges
|
15
|
+
include Cqrs::RabbitMq::Queues
|
16
|
+
include Hollywood
|
17
|
+
|
18
|
+
def initialize channel, exchange_name
|
19
|
+
@read_model = Euston::MessageBufferDaemon::ReadModel::MessageBuffer.send exchange_name
|
20
|
+
|
21
|
+
@channel = channel
|
22
|
+
@channel.prefetch(1)
|
23
|
+
@exchange = get_exchange @channel, exchange_name
|
24
|
+
@queue = get_queue @channel, "#{exchange_name}_buffer"
|
25
|
+
@queue.bind @exchange, :routing_key => "#{exchange_name}.#"
|
26
|
+
|
27
|
+
@queue.when(:message_decode_failed => method(:log_failure),
|
28
|
+
:message_failed => method(:message_failed),
|
29
|
+
:message_received => method(:remove_message_from_buffer))
|
30
|
+
|
31
|
+
@consumer = @queue.consumer
|
32
|
+
end
|
33
|
+
|
34
|
+
def disconnect
|
35
|
+
@channel.disconnect
|
36
|
+
end
|
37
|
+
|
38
|
+
def dequeue_buffered_messages(timeout) #will block until message or timeout
|
39
|
+
@queue.safe_subscribe_with_timeout(@consumer, timeout)
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_failure message, error
|
43
|
+
text = "A buffer queue subscription failed. [Error] #{error.message} [Payload] #{message}"
|
44
|
+
err = Cqrs::RabbitMq::MessageDecodeFailedError.new text
|
45
|
+
err.set_backtrace error.backtrace
|
46
|
+
|
47
|
+
Safely.report! err
|
48
|
+
end
|
49
|
+
|
50
|
+
def message_failed(message, error, header)
|
51
|
+
header.ack!
|
52
|
+
log_failure message, error
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_message_from_buffer message
|
56
|
+
@read_model.remove_published_message message[:headers][:id]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|