synapse-core 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/synapse/auditing/audit_logger.rb +28 -0
- data/lib/synapse/auditing/data_provider.rb +42 -0
- data/lib/synapse/auditing/dispatch_interceptor.rb +29 -0
- data/lib/synapse/auditing/unit_listener.rb +53 -0
- data/lib/synapse/auditing.rb +4 -0
- data/lib/synapse/command.rb +34 -0
- data/lib/synapse/{duplication.rb → common/duplication.rb} +0 -0
- data/lib/synapse/{errors.rb → common/errors.rb} +0 -0
- data/lib/synapse/{identifier.rb → common/identifier.rb} +2 -1
- data/lib/synapse/{message.rb → common/message.rb} +0 -0
- data/lib/synapse/{message_builder.rb → common/message_builder.rb} +0 -0
- data/lib/synapse/domain.rb +5 -0
- data/lib/synapse/event_bus.rb +5 -0
- data/lib/synapse/event_sourcing/conflict_resolver.rb +4 -6
- data/lib/synapse/event_sourcing/member.rb +11 -2
- data/lib/synapse/event_sourcing/snapshot/taker.rb +0 -18
- data/lib/synapse/event_sourcing.rb +13 -0
- data/lib/synapse/event_store/mongo/cursor_event_stream.rb +3 -3
- data/lib/synapse/event_store/mongo.rb +8 -0
- data/lib/synapse/event_store.rb +11 -0
- data/lib/synapse/partitioning/amqp/amqp_queue_reader.rb +50 -0
- data/lib/synapse/partitioning/amqp/amqp_queue_writer.rb +31 -0
- data/lib/synapse/partitioning/amqp/key_resolver.rb +26 -0
- data/lib/synapse/partitioning/amqp.rb +3 -0
- data/lib/synapse/partitioning/memory_queue_reader.rb +31 -0
- data/lib/synapse/partitioning/memory_queue_writer.rb +19 -0
- data/lib/synapse/partitioning/message_receipt.rb +25 -0
- data/lib/synapse/partitioning/packing/json_packer.rb +93 -0
- data/lib/synapse/partitioning/packing/json_unpacker.rb +83 -0
- data/lib/synapse/partitioning/packing.rb +27 -0
- data/lib/synapse/partitioning/queue_reader.rb +32 -0
- data/lib/synapse/partitioning/queue_writer.rb +17 -0
- data/lib/synapse/partitioning.rb +20 -0
- data/lib/synapse/process_manager.rb +4 -0
- data/lib/synapse/repository/locking.rb +3 -8
- data/lib/synapse/repository.rb +7 -0
- data/lib/synapse/serialization/converter/bson.rb +28 -0
- data/lib/synapse/serialization/serializer/attribute.rb +48 -0
- data/lib/synapse/serialization/serializer.rb +1 -1
- data/lib/synapse/serialization.rb +46 -0
- data/lib/synapse/uow/factory.rb +5 -5
- data/lib/synapse/uow/uow.rb +13 -13
- data/lib/synapse/uow.rb +8 -0
- data/lib/synapse/upcasting/chain.rb +1 -1
- data/lib/synapse/upcasting.rb +5 -0
- data/lib/synapse/version.rb +1 -1
- data/lib/synapse/wiring.rb +3 -0
- data/lib/synapse.rb +26 -338
- data/test/auditing/data_provider_test.rb +30 -0
- data/test/auditing/dispatch_interceptor_test.rb +25 -0
- data/test/auditing/unit_listener_test.rb +70 -0
- data/test/partitioning/memory_test.rb +34 -0
- data/test/partitioning/packing/json_test.rb +61 -0
- data/test/test_ext.rb +14 -0
- data/test/test_helper.rb +4 -0
- metadata +45 -24
- data/test/event_sourcing/snapshot/deferred_taker_test.rb +0 -19
@@ -0,0 +1,28 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Auditing
|
3
|
+
# Represents a mechanism for auditing commands and the events produced by their execution
|
4
|
+
# @abstract
|
5
|
+
class AuditLogger
|
6
|
+
# Called when a command execution was finished successfully
|
7
|
+
#
|
8
|
+
# @abstract
|
9
|
+
# @param [CommandMessage] command
|
10
|
+
# @param [Object] return_value
|
11
|
+
# @param [Array<EventMessage>] events
|
12
|
+
# @return [undefined]
|
13
|
+
def on_success(command, return_value, events); end
|
14
|
+
|
15
|
+
# Called when a command execution results in an exception being raised
|
16
|
+
#
|
17
|
+
# The list of events may not be empty; in this case, some events could have been published
|
18
|
+
# to the event bus and/or appended to the event store.
|
19
|
+
#
|
20
|
+
# @abstract
|
21
|
+
# @param [CommandMessage] command
|
22
|
+
# @param [Exception] exception
|
23
|
+
# @param [Array<EventMessage>] events
|
24
|
+
# @return [undefined]
|
25
|
+
def on_failure(command, exception, events); end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Auditing
|
3
|
+
# Provides relevant information to events for auditing purposes
|
4
|
+
# @abstract
|
5
|
+
class AuditDataProvider
|
6
|
+
# Returns auditing information for the given command
|
7
|
+
#
|
8
|
+
# @abstract
|
9
|
+
# @param [CommandMessage] command
|
10
|
+
# @return [Hash]
|
11
|
+
def provide_data_for(command); end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Implementation of an audit provider that simply audits a command's metadata
|
15
|
+
class CommandMetadataProvider < AuditDataProvider
|
16
|
+
# @param [CommandMessage] command
|
17
|
+
# @return [Hash]
|
18
|
+
def provide_data_for(command)
|
19
|
+
command.metadata
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Implementation of an audit provider that attaches a command's identifier to each event
|
24
|
+
# produced as a result of the execution of that command
|
25
|
+
class CorrelationDataProvider < AuditDataProvider
|
26
|
+
# The default key to use when correlating events with commands
|
27
|
+
DEFAULT_KEY = :command_id
|
28
|
+
|
29
|
+
# @param [Symbol] correlation_key
|
30
|
+
# @return [undefined]
|
31
|
+
def initialize(correlation_key = DEFAULT_KEY)
|
32
|
+
@correlation_key = correlation_key
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [CommandMessage] command
|
36
|
+
# @return [Hash]
|
37
|
+
def provide_data_for(command)
|
38
|
+
Hash[@correlation_key, command.id]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Auditing
|
3
|
+
class AuditingDispatchInterceptor < Command::DispatchInterceptor
|
4
|
+
# @return [Array<AuditDataProvider>]
|
5
|
+
attr_accessor :data_providers
|
6
|
+
|
7
|
+
# @return [Array<AuditLogger>]
|
8
|
+
attr_accessor :loggers
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@data_providers = Array.new
|
12
|
+
@loggers = Array.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [CommandMessage] command
|
16
|
+
# @param [UnitOfWork] unit The current unit of work for this command dispatch
|
17
|
+
# @param [InterceptorChain] chain
|
18
|
+
# @return [Object] The result of the execution of the command
|
19
|
+
def intercept(command, unit, chain)
|
20
|
+
audit_listener = AuditingUnitOfWorkListener.new command, @data_providers, @loggers
|
21
|
+
unit.register_listener audit_listener
|
22
|
+
|
23
|
+
chain.proceed(command).tap do |result|
|
24
|
+
audit_listener.return_value = result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Auditing
|
3
|
+
class AuditingUnitOfWorkListener < UnitOfWork::UnitOfWorkListener
|
4
|
+
# @return [Array<EventMessage>]
|
5
|
+
attr_reader :recorded_events
|
6
|
+
|
7
|
+
# @return [Object]
|
8
|
+
attr_accessor :return_value
|
9
|
+
|
10
|
+
# @param [CommandMessage] command
|
11
|
+
# @param [Array<AuditDataProvider>] data_providers
|
12
|
+
# @param [Array<AuditLogger>] loggers
|
13
|
+
# @return [undefined]
|
14
|
+
def initialize(command, data_providers, loggers)
|
15
|
+
@command = command
|
16
|
+
@data_providers = data_providers
|
17
|
+
@loggers = loggers
|
18
|
+
@recorded_events = Array.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [UnitOfWork] unit
|
22
|
+
# @param [EventMessage] event
|
23
|
+
# @return [EventMessage]
|
24
|
+
def on_event_registered(unit, event)
|
25
|
+
audit_data = Hash.new
|
26
|
+
@data_providers.each do |provider|
|
27
|
+
audit_data.merge! provider.provide_data_for @command
|
28
|
+
end
|
29
|
+
|
30
|
+
event.and_metadata(audit_data).tap do |e|
|
31
|
+
@recorded_events.push e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [UnitOfWork] unit
|
36
|
+
# @return [undefined]
|
37
|
+
def after_commit(unit)
|
38
|
+
@loggers.each do |logger|
|
39
|
+
logger.on_success @command, @return_value, @recorded_events
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [UnitOfWork] unit
|
44
|
+
# @param [Error] cause
|
45
|
+
# @return [undefined]
|
46
|
+
def on_rollback(unit, cause = nil)
|
47
|
+
@loggers.each do |logger|
|
48
|
+
logger.on_success @command, cause, @recorded_events
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Command
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
# Optional filters and interceptors
|
6
|
+
autoload_at 'synapse/command/duplication' do
|
7
|
+
autoload :DuplicationFilter
|
8
|
+
autoload :DuplicationCleanupInterceptor
|
9
|
+
end
|
10
|
+
|
11
|
+
autoload_at 'synapse/command/filters/validation' do
|
12
|
+
autoload :ActiveModelValidationFilter
|
13
|
+
autoload :ActiveModelValidationError
|
14
|
+
end
|
15
|
+
|
16
|
+
autoload_at 'synapse/command/interceptors/serialization' do
|
17
|
+
autoload :SerializationOptimizingInterceptor
|
18
|
+
autoload :SerializationOptimizingListener
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'synapse/command/command_bus'
|
24
|
+
require 'synapse/command/command_callback'
|
25
|
+
require 'synapse/command/command_filter'
|
26
|
+
require 'synapse/command/command_handler'
|
27
|
+
require 'synapse/command/dispatch_interceptor'
|
28
|
+
require 'synapse/command/errors'
|
29
|
+
require 'synapse/command/gateway'
|
30
|
+
require 'synapse/command/interceptor_chain'
|
31
|
+
require 'synapse/command/message'
|
32
|
+
require 'synapse/command/rollback_policy'
|
33
|
+
require 'synapse/command/simple_command_bus'
|
34
|
+
require 'synapse/command/wiring'
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -64,13 +64,11 @@ module Synapse
|
|
64
64
|
|
65
65
|
# @return [DomainEventMessage]
|
66
66
|
def next_event
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
@delegate.next_event.tap do |event|
|
68
|
+
if @expected_version and event.sequence_number > @expected_version
|
69
|
+
@unseen_events.push event
|
70
|
+
end
|
71
71
|
end
|
72
|
-
|
73
|
-
event
|
74
72
|
end
|
75
73
|
|
76
74
|
# Delegators for domain event stream
|
@@ -4,6 +4,11 @@ module Synapse
|
|
4
4
|
# applied to the aggregate
|
5
5
|
module Member
|
6
6
|
extend ActiveSupport::Concern
|
7
|
+
include Wiring::MessageWiring
|
8
|
+
|
9
|
+
included do
|
10
|
+
self.wire_registry = Wiring::WireRegistry.new true
|
11
|
+
end
|
7
12
|
|
8
13
|
module ClassMethods
|
9
14
|
# Registers an instance variable as a child entity
|
@@ -53,10 +58,14 @@ module Synapse
|
|
53
58
|
# If the event is relative to this member, its parameters will be used to change
|
54
59
|
# the state of this member
|
55
60
|
#
|
56
|
-
# @abstract
|
57
61
|
# @param [EventMessage] event
|
58
62
|
# @return [undefined]
|
59
|
-
def handle_event(event)
|
63
|
+
def handle_event(event)
|
64
|
+
wire = self.wire_registry.wire_for event.payload_type
|
65
|
+
if wire
|
66
|
+
invoke_wire event, wire
|
67
|
+
end
|
68
|
+
end
|
60
69
|
|
61
70
|
private
|
62
71
|
|
@@ -17,24 +17,6 @@ module Synapse
|
|
17
17
|
def schedule_snapshot(type_identifier, aggregate_id); end
|
18
18
|
end
|
19
19
|
|
20
|
-
# Snapshot taker that uses EventMachine to defer scheduling of snapshots
|
21
|
-
class DeferredSnapshotTaker < SnapshotTaker
|
22
|
-
# @param [SnapshotTaker] delegate
|
23
|
-
# @return [undefined]
|
24
|
-
def initialize(delegate)
|
25
|
-
@delegate = delegate
|
26
|
-
end
|
27
|
-
|
28
|
-
# @param [String] type_identifier
|
29
|
-
# @param [Object] aggregate_id
|
30
|
-
# @return [undefined]
|
31
|
-
def schedule_snapshot(type_identifier, aggregate_id)
|
32
|
-
EventMachine.defer do
|
33
|
-
@delegate.schedule_snapshot type_identifier, aggregate_id
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
20
|
# Snapshot taker that uses the actual aggregate and its state to create a snapshot event
|
39
21
|
class AggregateSnapshotTaker < SnapshotTaker
|
40
22
|
# @param [SnapshotEventStore] event_store
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'synapse/event_sourcing/aggregate_factory'
|
2
|
+
require 'synapse/event_sourcing/conflict_resolver'
|
3
|
+
require 'synapse/event_sourcing/repository'
|
4
|
+
require 'synapse/event_sourcing/storage_listener'
|
5
|
+
require 'synapse/event_sourcing/stream_decorator'
|
6
|
+
|
7
|
+
require 'synapse/event_sourcing/member'
|
8
|
+
require 'synapse/event_sourcing/aggregate_root'
|
9
|
+
require 'synapse/event_sourcing/entity'
|
10
|
+
|
11
|
+
require 'synapse/event_sourcing/snapshot/count_stream'
|
12
|
+
require 'synapse/event_sourcing/snapshot/count_trigger'
|
13
|
+
require 'synapse/event_sourcing/snapshot/taker'
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'synapse/event_store/mongo/cursor_event_stream'
|
2
|
+
require 'synapse/event_store/mongo/event_store'
|
3
|
+
|
4
|
+
require 'synapse/event_store/mongo/storage_strategy'
|
5
|
+
require 'synapse/event_store/mongo/per_commit_strategy'
|
6
|
+
require 'synapse/event_store/mongo/per_event_strategy'
|
7
|
+
|
8
|
+
require 'synapse/event_store/mongo/template'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
module AMQP
|
4
|
+
# Implementation of a queue reader that subscribes to an AMQP queue
|
5
|
+
class AMQPQueueReader < QueueReader
|
6
|
+
# The behavior when a message is not acknowledged by a message handler
|
7
|
+
#
|
8
|
+
# When a message is explicitly rejected, this usually indicates that there was an
|
9
|
+
# error while processing the message. When the message is rejected, it can either be
|
10
|
+
# put back on the queue so that it can be retried later, or it can be routed as a dead
|
11
|
+
# letter if using RabbitMQ.
|
12
|
+
#
|
13
|
+
# @see http://www.rabbitmq.com/dlx.html
|
14
|
+
# @return [Boolean] Default value is true
|
15
|
+
attr_accessor :requeue_on_nack
|
16
|
+
|
17
|
+
# @param [AMQP::Queue] queue
|
18
|
+
# @param [AMQP::Channel] channel
|
19
|
+
# @param [MessageUnpacker] unpacker
|
20
|
+
# @return [undefined]
|
21
|
+
def initialize(queue, channel, unpacker)
|
22
|
+
@queue = queue
|
23
|
+
@channel = channel
|
24
|
+
@requeue_on_nack = true
|
25
|
+
end
|
26
|
+
|
27
|
+
# @yield [MessageReceipt] Receipt of the message taken off the queue
|
28
|
+
# @return [undefined]
|
29
|
+
def subscribe(&handler)
|
30
|
+
@queue.subscribe do |headers, packed|
|
31
|
+
receipt = MessageReceipt.new headers.delivery_tag, packed, @queue.name
|
32
|
+
handler.call receipt
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [MessageReceipt] receipt
|
37
|
+
# @return [undefined]
|
38
|
+
def ack_message(receipt)
|
39
|
+
@channel.acknowledge receipt.tag
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param [MessageReceipt] receipt
|
43
|
+
# @return [undefined]
|
44
|
+
def nack_message(receipt)
|
45
|
+
@channel.reject receipt.tag, @requeue_on_nack
|
46
|
+
end
|
47
|
+
end # AMQPQueueReader
|
48
|
+
end # AMQP
|
49
|
+
end # Partitioning
|
50
|
+
end # Synapse
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
module AMQP
|
4
|
+
# Implementation of a queue writer that publishes packed messages to an AMQP exchange
|
5
|
+
class AMQPQueueWriter < QueueWriter
|
6
|
+
# @return [Hash] Additional options that will be used when publishing messages
|
7
|
+
attr_accessor :publish_options
|
8
|
+
|
9
|
+
# @param [AMQP::Exchange] exchange
|
10
|
+
# @param [RoutingKeyResolver] key_resolver
|
11
|
+
# @return [undefined]
|
12
|
+
def initialize(exchange, key_resolver)
|
13
|
+
@exchange = exchange
|
14
|
+
@key_resolver = key_resolver
|
15
|
+
@publish_options = Hash.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Object] packed
|
19
|
+
# @param [Message] unpacked
|
20
|
+
# @return [undefined]
|
21
|
+
def put_message(packed, unpacked)
|
22
|
+
publish_options = {
|
23
|
+
routing_key: @key_resolver.resolve_key(unpacked)
|
24
|
+
}
|
25
|
+
|
26
|
+
@exchange.publish(packed, @publish_options.merge(publish_options))
|
27
|
+
end
|
28
|
+
end # AMQPQueueWriter
|
29
|
+
end # AMQP
|
30
|
+
end # Partitioning
|
31
|
+
end # Synapse
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
module AMQP
|
4
|
+
# Represents a mechanism for determining the routing key to use when publishing a message
|
5
|
+
class RoutingKeyResolver
|
6
|
+
# Returns the routing key to use when publishing the given message
|
7
|
+
#
|
8
|
+
# @param [Message] message
|
9
|
+
# @return [String]
|
10
|
+
def resolve_key(message); end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Implementation of a routing key resolver that uses the message payload's module name
|
14
|
+
class ModuleRoutingKeyResolver
|
15
|
+
# @param [Message] message
|
16
|
+
# @return [String]
|
17
|
+
def resolve_key(message)
|
18
|
+
type = message.payload_type.to_s
|
19
|
+
type.deconstantize.underscore.tap do |key|
|
20
|
+
key.tr! '/', '.'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end # AMQP
|
25
|
+
end # Partitioning
|
26
|
+
end # Synapse
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
# Queue reader that dequeues messages from an in-memory Ruby queue
|
4
|
+
class MemoryQueueReader < QueueReader
|
5
|
+
# @param [Queue] queue
|
6
|
+
# @param [String] name Name of the queue being read from
|
7
|
+
# @return [undefined]
|
8
|
+
def initialize(queue, name)
|
9
|
+
@queue = queue
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
# @yield [MessageReceipt] Receipt of the message taken off the queue
|
14
|
+
# @return [undefined]
|
15
|
+
def subscribe(&handler)
|
16
|
+
loop do
|
17
|
+
packed = @queue.pop
|
18
|
+
|
19
|
+
receipt = MessageReceipt.new 0, packed, @name
|
20
|
+
handler.call receipt
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [MessageReceipt] receipt
|
25
|
+
# @return [undefined]
|
26
|
+
def nack_message(receipt)
|
27
|
+
@queue.push receipt.packed
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
# Queue writer that pushes message into an in-memory Ruby queue
|
4
|
+
class MemoryQueueWriter < QueueWriter
|
5
|
+
# @param [Queue] queue
|
6
|
+
# @return [undefined]
|
7
|
+
def initialize(queue)
|
8
|
+
@queue = queue
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [Object] packed
|
12
|
+
# @param [Message] unpacked
|
13
|
+
# @return [undefined]
|
14
|
+
def put_message(packed, unpacked)
|
15
|
+
@queue.push packed
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
# Receipt used to track messages taken off of a queue
|
4
|
+
class MessageReceipt
|
5
|
+
# @return [Object] The transport mechanism's record of the message
|
6
|
+
attr_reader :tag
|
7
|
+
|
8
|
+
# @return [Object] The packed message from the transport
|
9
|
+
attr_reader :packed
|
10
|
+
|
11
|
+
# @return [String] The name of the queue the message was read from
|
12
|
+
attr_reader :queue_name
|
13
|
+
|
14
|
+
# @param [Object] tag
|
15
|
+
# @param [Object] packed
|
16
|
+
# @param [String] queue_name
|
17
|
+
# @return [undefined]
|
18
|
+
def initialize(tag, packed, queue_name)
|
19
|
+
@tag = tag
|
20
|
+
@packed = packed
|
21
|
+
@queue_name = queue_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Partitioning
|
3
|
+
# Implementation of a message packer that serializes the metadata and payload of any
|
4
|
+
# message and then serializes the entire message so it can go onto the wire.
|
5
|
+
class JsonMessagePacker < MessagePacker
|
6
|
+
# @param [Serializer] serializer
|
7
|
+
# @return [undefined]
|
8
|
+
def initialize(serializer)
|
9
|
+
@serializer = serializer
|
10
|
+
|
11
|
+
@serialization_target = String
|
12
|
+
# Ideally, we want to serialize the metadata and payload to hashes so that we don't
|
13
|
+
# duplicate serialization while serializing the message as a whole to a string
|
14
|
+
if serializer.can_serialize_to? Hash
|
15
|
+
@serialization_target = Hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Message] unpacked
|
20
|
+
# @return [String]
|
21
|
+
def pack_message(unpacked)
|
22
|
+
message_type = type_for unpacked
|
23
|
+
|
24
|
+
metadata = @serializer.serialize unpacked.metadata, @serialization_target
|
25
|
+
payload = @serializer.serialize unpacked.payload, @serialization_target
|
26
|
+
|
27
|
+
packed = {
|
28
|
+
message_type: message_type,
|
29
|
+
id: unpacked.id,
|
30
|
+
metadata: metadata.content,
|
31
|
+
payload: payload.content,
|
32
|
+
payload_type: payload.type.name,
|
33
|
+
payload_revision: payload.type.revision
|
34
|
+
}
|
35
|
+
|
36
|
+
if [:event, :domain_event].include? message_type
|
37
|
+
pack_event unpacked, packed
|
38
|
+
end
|
39
|
+
|
40
|
+
if :domain_event == message_type
|
41
|
+
pack_domain_event unpacked, packed
|
42
|
+
end
|
43
|
+
|
44
|
+
JSON.dump packed
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Packs additional attributes specific to event messages
|
50
|
+
#
|
51
|
+
# @param [EventMessage] unpacked
|
52
|
+
# @param [Hash] packed
|
53
|
+
# @return [undefined]
|
54
|
+
def pack_event(unpacked, packed)
|
55
|
+
additional = {
|
56
|
+
timestamp: unpacked.timestamp.to_i
|
57
|
+
}
|
58
|
+
packed.merge! additional
|
59
|
+
end
|
60
|
+
|
61
|
+
# Packs additional attributes specific to domain event messages
|
62
|
+
#
|
63
|
+
# @param [DomainEventMessage] unpacked
|
64
|
+
# @param [Hash] packed
|
65
|
+
# @return [undefined]
|
66
|
+
def pack_domain_event(unpacked, packed)
|
67
|
+
additional = {
|
68
|
+
aggregate_id: unpacked.aggregate_id.to_s,
|
69
|
+
sequence_number: unpacked.sequence_number
|
70
|
+
}
|
71
|
+
packed.merge! additional
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the packed type for the given message
|
75
|
+
#
|
76
|
+
# @raise [ArgumentError] If the given message isn't supported by this packer
|
77
|
+
# @param [Message] unpacked
|
78
|
+
# @return [Symbol]
|
79
|
+
def type_for(unpacked)
|
80
|
+
case unpacked
|
81
|
+
when Command::CommandMessage
|
82
|
+
:command
|
83
|
+
when Domain::DomainEventMessage
|
84
|
+
:domain_event
|
85
|
+
when Domain::EventMessage
|
86
|
+
:event
|
87
|
+
else
|
88
|
+
raise ArgumentError, 'Unknown message type'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end # JsonMessagePacker
|
92
|
+
end # Partitioning
|
93
|
+
end # Synapse
|