synapse-core 0.1.2

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.
Files changed (155) hide show
  1. data/lib/synapse.rb +351 -0
  2. data/lib/synapse/command/command_bus.rb +45 -0
  3. data/lib/synapse/command/command_callback.rb +18 -0
  4. data/lib/synapse/command/command_filter.rb +17 -0
  5. data/lib/synapse/command/command_handler.rb +13 -0
  6. data/lib/synapse/command/dispatch_interceptor.rb +16 -0
  7. data/lib/synapse/command/duplication.rb +43 -0
  8. data/lib/synapse/command/errors.rb +27 -0
  9. data/lib/synapse/command/filters/validation.rb +32 -0
  10. data/lib/synapse/command/gateway.rb +34 -0
  11. data/lib/synapse/command/interceptor_chain.rb +31 -0
  12. data/lib/synapse/command/interceptors/serialization.rb +35 -0
  13. data/lib/synapse/command/message.rb +19 -0
  14. data/lib/synapse/command/rollback_policy.rb +22 -0
  15. data/lib/synapse/command/simple_command_bus.rb +138 -0
  16. data/lib/synapse/command/wiring.rb +47 -0
  17. data/lib/synapse/domain/aggregate_root.rb +121 -0
  18. data/lib/synapse/domain/event_container.rb +127 -0
  19. data/lib/synapse/domain/message.rb +82 -0
  20. data/lib/synapse/domain/message_builder.rb +34 -0
  21. data/lib/synapse/domain/stream.rb +108 -0
  22. data/lib/synapse/duplication.rb +60 -0
  23. data/lib/synapse/errors.rb +13 -0
  24. data/lib/synapse/event_bus/event_bus.rb +40 -0
  25. data/lib/synapse/event_bus/event_listener.rb +16 -0
  26. data/lib/synapse/event_bus/event_listener_proxy.rb +12 -0
  27. data/lib/synapse/event_bus/simple_event_bus.rb +69 -0
  28. data/lib/synapse/event_bus/wiring.rb +23 -0
  29. data/lib/synapse/event_sourcing/aggregate_factory.rb +69 -0
  30. data/lib/synapse/event_sourcing/aggregate_root.rb +104 -0
  31. data/lib/synapse/event_sourcing/conflict_resolver.rb +80 -0
  32. data/lib/synapse/event_sourcing/entity.rb +64 -0
  33. data/lib/synapse/event_sourcing/member.rb +72 -0
  34. data/lib/synapse/event_sourcing/repository.rb +119 -0
  35. data/lib/synapse/event_sourcing/snapshot/count_stream.rb +86 -0
  36. data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +91 -0
  37. data/lib/synapse/event_sourcing/snapshot/taker.rb +73 -0
  38. data/lib/synapse/event_sourcing/storage_listener.rb +34 -0
  39. data/lib/synapse/event_sourcing/stream_decorator.rb +25 -0
  40. data/lib/synapse/event_store/errors.rb +16 -0
  41. data/lib/synapse/event_store/event_store.rb +43 -0
  42. data/lib/synapse/event_store/in_memory.rb +59 -0
  43. data/lib/synapse/event_store/mongo/cursor_event_stream.rb +63 -0
  44. data/lib/synapse/event_store/mongo/event_store.rb +86 -0
  45. data/lib/synapse/event_store/mongo/per_commit_strategy.rb +253 -0
  46. data/lib/synapse/event_store/mongo/per_event_strategy.rb +143 -0
  47. data/lib/synapse/event_store/mongo/storage_strategy.rb +113 -0
  48. data/lib/synapse/event_store/mongo/template.rb +73 -0
  49. data/lib/synapse/identifier.rb +23 -0
  50. data/lib/synapse/message.rb +101 -0
  51. data/lib/synapse/message_builder.rb +38 -0
  52. data/lib/synapse/process_manager/correlation.rb +32 -0
  53. data/lib/synapse/process_manager/correlation_resolver.rb +14 -0
  54. data/lib/synapse/process_manager/correlation_set.rb +58 -0
  55. data/lib/synapse/process_manager/process.rb +71 -0
  56. data/lib/synapse/repository/errors.rb +26 -0
  57. data/lib/synapse/repository/lock_manager.rb +40 -0
  58. data/lib/synapse/repository/locking.rb +97 -0
  59. data/lib/synapse/repository/pessimistic_lock_manager.rb +61 -0
  60. data/lib/synapse/repository/repository.rb +109 -0
  61. data/lib/synapse/serialization/converter.rb +39 -0
  62. data/lib/synapse/serialization/converter/chain.rb +45 -0
  63. data/lib/synapse/serialization/converter/factory.rb +68 -0
  64. data/lib/synapse/serialization/converter/identity.rb +29 -0
  65. data/lib/synapse/serialization/converter/json.rb +31 -0
  66. data/lib/synapse/serialization/converter/ox.rb +31 -0
  67. data/lib/synapse/serialization/errors.rb +12 -0
  68. data/lib/synapse/serialization/lazy_object.rb +61 -0
  69. data/lib/synapse/serialization/message/data.rb +25 -0
  70. data/lib/synapse/serialization/message/metadata.rb +13 -0
  71. data/lib/synapse/serialization/message/serialization_aware.rb +17 -0
  72. data/lib/synapse/serialization/message/serialization_aware_message.rb +66 -0
  73. data/lib/synapse/serialization/message/serialized_message.rb +201 -0
  74. data/lib/synapse/serialization/message/serialized_message_builder.rb +64 -0
  75. data/lib/synapse/serialization/message/serialized_object_cache.rb +50 -0
  76. data/lib/synapse/serialization/message/serializer.rb +47 -0
  77. data/lib/synapse/serialization/revision_resolver.rb +30 -0
  78. data/lib/synapse/serialization/serialized_object.rb +37 -0
  79. data/lib/synapse/serialization/serialized_type.rb +31 -0
  80. data/lib/synapse/serialization/serializer.rb +98 -0
  81. data/lib/synapse/serialization/serializer/marshal.rb +32 -0
  82. data/lib/synapse/serialization/serializer/oj.rb +34 -0
  83. data/lib/synapse/serialization/serializer/ox.rb +31 -0
  84. data/lib/synapse/uow/factory.rb +28 -0
  85. data/lib/synapse/uow/listener.rb +79 -0
  86. data/lib/synapse/uow/listener_collection.rb +93 -0
  87. data/lib/synapse/uow/nesting.rb +262 -0
  88. data/lib/synapse/uow/provider.rb +71 -0
  89. data/lib/synapse/uow/storage_listener.rb +14 -0
  90. data/lib/synapse/uow/transaction_manager.rb +27 -0
  91. data/lib/synapse/uow/uow.rb +178 -0
  92. data/lib/synapse/upcasting/chain.rb +78 -0
  93. data/lib/synapse/upcasting/context.rb +58 -0
  94. data/lib/synapse/upcasting/data.rb +30 -0
  95. data/lib/synapse/upcasting/single_upcaster.rb +57 -0
  96. data/lib/synapse/upcasting/upcaster.rb +55 -0
  97. data/lib/synapse/version.rb +3 -0
  98. data/lib/synapse/wiring/message_wiring.rb +41 -0
  99. data/lib/synapse/wiring/wire.rb +55 -0
  100. data/lib/synapse/wiring/wire_registry.rb +61 -0
  101. data/test/command/duplication_test.rb +54 -0
  102. data/test/command/gateway_test.rb +25 -0
  103. data/test/command/interceptor_chain_test.rb +26 -0
  104. data/test/command/serialization_test.rb +37 -0
  105. data/test/command/simple_command_bus_test.rb +141 -0
  106. data/test/command/validation_test.rb +42 -0
  107. data/test/command/wiring_test.rb +73 -0
  108. data/test/domain/aggregate_root_test.rb +57 -0
  109. data/test/domain/fixtures.rb +31 -0
  110. data/test/domain/message_test.rb +61 -0
  111. data/test/domain/stream_test.rb +35 -0
  112. data/test/duplication_test.rb +40 -0
  113. data/test/event_bus/wiring_test.rb +46 -0
  114. data/test/event_sourcing/aggregate_factory_test.rb +28 -0
  115. data/test/event_sourcing/aggregate_root_test.rb +76 -0
  116. data/test/event_sourcing/entity_test.rb +34 -0
  117. data/test/event_sourcing/fixtures.rb +85 -0
  118. data/test/event_sourcing/repository_test.rb +102 -0
  119. data/test/event_sourcing/snapshot/aggregate_taker_test.rb +39 -0
  120. data/test/event_sourcing/snapshot/deferred_taker_test.rb +19 -0
  121. data/test/event_sourcing/snapshot/integration_test.rb +65 -0
  122. data/test/event_sourcing/storage_listener_test.rb +77 -0
  123. data/test/event_store/in_memory_test.rb +47 -0
  124. data/test/process_manager/correlation_set_test.rb +49 -0
  125. data/test/process_manager/correlation_test.rb +24 -0
  126. data/test/process_manager/process_test.rb +52 -0
  127. data/test/repository/locking_test.rb +101 -0
  128. data/test/serialization/converter/factory_test.rb +33 -0
  129. data/test/serialization/converter/identity_test.rb +17 -0
  130. data/test/serialization/converter/json_test.rb +31 -0
  131. data/test/serialization/converter/ox_test.rb +40 -0
  132. data/test/serialization/fixtures.rb +17 -0
  133. data/test/serialization/lazy_object_test.rb +32 -0
  134. data/test/serialization/message/metadata_test.rb +19 -0
  135. data/test/serialization/message/serialization_aware_message_test.rb +88 -0
  136. data/test/serialization/message/serialized_message_builder_test.rb +41 -0
  137. data/test/serialization/message/serialized_message_test.rb +140 -0
  138. data/test/serialization/message/serializer_test.rb +50 -0
  139. data/test/serialization/revision_resolver_test.rb +12 -0
  140. data/test/serialization/serialized_object_test.rb +36 -0
  141. data/test/serialization/serialized_type_test.rb +27 -0
  142. data/test/serialization/serializer/marshal_test.rb +22 -0
  143. data/test/serialization/serializer/oj_test.rb +24 -0
  144. data/test/serialization/serializer/ox_test.rb +36 -0
  145. data/test/serialization/serializer_test.rb +20 -0
  146. data/test/test_helper.rb +19 -0
  147. data/test/uow/factory_test.rb +23 -0
  148. data/test/uow/outer_commit_listener_test.rb +50 -0
  149. data/test/uow/provider_test.rb +70 -0
  150. data/test/uow/uow_test.rb +337 -0
  151. data/test/upcasting/chain_test.rb +29 -0
  152. data/test/upcasting/fixtures.rb +66 -0
  153. data/test/wiring/wire_registry_test.rb +60 -0
  154. data/test/wiring/wire_test.rb +51 -0
  155. metadata +263 -0
@@ -0,0 +1,143 @@
1
+ module Synapse
2
+ module EventStore
3
+ module Mongo
4
+ # Storage strategy that stores each event as its own document
5
+ class DocumentPerEventStrategy < StorageStrategy
6
+ # @param [String] type_identifier Type identifier for the aggregate
7
+ # @param [Array] events Domain events to be committed
8
+ # @return [Array]
9
+ def create_documents(type_identifier, events)
10
+ documents = Array.new
11
+
12
+ events.each do |event|
13
+ document = EventDocument.new
14
+ document.from_event event, type_identifier, @serializer
15
+
16
+ documents.push document.to_hash
17
+ end
18
+
19
+ documents
20
+ end
21
+
22
+ # @param [Hash] hash
23
+ # @param [Object] aggregate_id
24
+ # @return [Array]
25
+ def extract_events(hash, aggregate_id)
26
+ document = EventDocument.new
27
+ document.from_hash(hash).to_events(aggregate_id, @serializer, @upcaster_chain)
28
+ end
29
+
30
+ # Mongo document that represents a single domain event
31
+ class EventDocument < Serialization::SerializedDomainEventData
32
+ # @return [String]
33
+ attr_reader :id
34
+
35
+ # @return [Time]
36
+ attr_reader :timestamp
37
+
38
+ # @return [Object]
39
+ attr_reader :aggregate_id
40
+
41
+ # @return [Integer]
42
+ attr_reader :sequence_number
43
+
44
+ # @param [SerializedObject]
45
+ def metadata
46
+ Serialization::SerializedMetadata.new @metadata, @metadata.class
47
+ end
48
+
49
+ # @param [SerializedObject]
50
+ def payload
51
+ Serialization::SerializedObject.new @payload, @payload.class,
52
+ Serialization::SerializedType.new(@payload_type, @payload_revision)
53
+ end
54
+
55
+ # @param [DomainEventMessage] event
56
+ # @param [String] type_identifier
57
+ # @param [Serializer] serializer
58
+ # @return [EventDocument]
59
+ def from_event(event, type_identifier, serializer)
60
+ serialization_target = String
61
+ if serializer.can_serialize_to? Hash
62
+ serialization_target = Hash
63
+ end
64
+
65
+ serialized_metadata = serializer.serialize_metadata event, serialization_target
66
+ serialized_payload = serializer.serialize_payload event, serialization_target
67
+
68
+ @id = event.id
69
+ @metadata = serialized_metadata.content
70
+ @payload = serialized_payload.content
71
+ @payload_type = serialized_payload.type.name
72
+ @payload_revision = serialized_payload.type.revision
73
+ @timestamp = event.timestamp
74
+ @aggregate_id = event.aggregate_id
75
+ @aggregate_type = type_identifier
76
+ @sequence_number = event.sequence_number
77
+
78
+ self
79
+ end
80
+
81
+ # @param [Hash] hash
82
+ # @return [EventDocument]
83
+ def from_hash(hash)
84
+ hash.symbolize_keys!
85
+
86
+ @id = hash.fetch :_id
87
+ @metadata = hash.fetch :metadata
88
+ @payload = hash.fetch :payload
89
+ @payload_type = hash.fetch :payload_type
90
+ @payload_revision = hash.fetch :payload_revision
91
+ @timestamp = hash.fetch :timestamp
92
+ @aggregate_id = hash.fetch :aggregate_id
93
+ @aggregate_type = hash.fetch :aggregate_type
94
+ @sequence_number = hash.fetch :sequence_number
95
+
96
+ self
97
+ end
98
+
99
+ # @return [Hash]
100
+ def to_hash
101
+ { _id: @id,
102
+ metadata: @metadata,
103
+ payload: @payload,
104
+ payload_type: @payload_type,
105
+ payload_revision: @payload_revision,
106
+ timestamp: @timestamp,
107
+ aggregate_id: @aggregate_id,
108
+ aggregate_type: @aggregate_type,
109
+ sequence_number: @sequence_number }
110
+ end
111
+
112
+ # @param [Object] aggregate_id
113
+ # @param [Serializer] serializer
114
+ # @param [UpcasterChain] upcaster_chain
115
+ # @return [Array]
116
+ def to_events(aggregate_id, serializer, upcaster_chain)
117
+ events = Array.new
118
+
119
+ context = Upcasting::SerializedDomainEventUpcastingContext.new self, aggregate_id, serializer
120
+ upcast_objects = upcaster_chain.upcast payload, context
121
+ upcast_objects.each do |upcast_object|
122
+ upcast_data = Upcasting::UpcastSerializedDomainEventData.new self, aggregate_id, upcast_object
123
+
124
+ builder = Serialization::SerializedDomainEventMessageBuilder.new
125
+
126
+ # Prevent duplicate serialization of metadata if it was accessed during upcasting
127
+ metadata = context.serialized_metadata
128
+ if metadata.deserialized?
129
+ builder.metadata = Serialization::DeserializedObject.new metadata.deserialized
130
+ end
131
+
132
+ builder.from_data upcast_data, serializer
133
+
134
+ events.push builder.build
135
+ end
136
+
137
+ events
138
+ end
139
+ end # EventDocument
140
+ end # DocumentPerEventStrategy
141
+ end # Mongo
142
+ end # EventStore
143
+ end # Synapse
@@ -0,0 +1,113 @@
1
+ module Synapse
2
+ module EventStore
3
+ module Mongo
4
+ # Represents a mechanism used to structure how events are stored in the database
5
+ # @abstract
6
+ class StorageStrategy
7
+ # @param [MongoTemplate] template
8
+ # @param [Serializer] serializer
9
+ # @param [UpcasterChain] upcaster_chain
10
+ # @return [undefined]
11
+ def initialize(template, serializer, upcaster_chain)
12
+ @template = template
13
+ @serializer = Serialization::MessageSerializer.new serializer
14
+ @upcaster_chain = upcaster_chain
15
+ end
16
+
17
+ # Creates documents that will represent the events being committed to the event store
18
+ #
19
+ # @abstract
20
+ # @param [String] type_identifier Type identifier for the aggregate
21
+ # @param [Array] events Domain events to be committed
22
+ # @return [Array]
23
+ def create_documents(type_identifier, events); end
24
+
25
+ # Extracts individual event messages from the given document
26
+ #
27
+ # The given aggregate identifier is passed so that event messages can have the actual
28
+ # identifier object instead of the serialized aggregate identifier.
29
+ #
30
+ # @abstract
31
+ # @param [Hash] document
32
+ # @param [Object] aggregate_id
33
+ # @return [Array]
34
+ def extract_events(document, aggregate_id); end
35
+
36
+ # Aliases of the Mongo constants for ascending and descending
37
+ ASCENDING = ::Mongo::ASCENDING
38
+ DESCENDING = ::Mongo::DESCENDING
39
+
40
+ # Provides a cursor for accessing all events for an aggregate with the given identifier
41
+ # and type identifier, with a sequence number equal to or greater than the given first
42
+ # sequence number
43
+ #
44
+ # The returned documents should be ordered chronologically, typically by using the
45
+ # sequence number.
46
+ #
47
+ # @param [String] type_identifier
48
+ # @param [Object] aggregate_id
49
+ # @param [Integer] first_sequence_number
50
+ # @return [Mongo::Cursor]
51
+ def fetch_events(type_identifier, aggregate_id, first_sequence_number)
52
+ filter = {
53
+ aggregate_id: aggregate_id,
54
+ aggregate_type: type_identifier,
55
+ sequence_number: {
56
+ '$gte' => first_sequence_number
57
+ }
58
+ }
59
+
60
+ sort = {
61
+ sequence_number: ASCENDING
62
+ }
63
+
64
+ @template.event_collection.find(filter).sort(sort)
65
+ end
66
+
67
+ # Finds the document containing the most recent snapshot event for an aggregate with the
68
+ # given identifier and type identifier
69
+ #
70
+ # @param [String] type_identifier
71
+ # @param [Object] aggregate_id
72
+ # @return [Mongo::Cursor]
73
+ def fetch_last_snapshot(type_identifier, aggregate_id)
74
+ filter = {
75
+ aggregate_id: aggregate_id,
76
+ aggregate_type: type_identifier
77
+ }
78
+
79
+ sort = {
80
+ sequence_number: DESCENDING
81
+ }
82
+
83
+ @template.snapshot_collection.find(filter).sort(sort).limit(1)
84
+ end
85
+
86
+ # Ensures that the correct indexes are in place
87
+ # @return [undefined]
88
+ def ensure_indexes
89
+ options = {
90
+ name: 'unique_aggregate_index',
91
+ unique: true
92
+ }
93
+
94
+ spec = {
95
+ aggregate_id: ASCENDING,
96
+ aggregate_type: ASCENDING,
97
+ sequence_number: ASCENDING
98
+ }
99
+
100
+ @template.event_collection.ensure_index spec, options
101
+
102
+ spec = {
103
+ aggregate_id: ASCENDING,
104
+ aggregate_type: ASCENDING,
105
+ sequence_number: DESCENDING
106
+ }
107
+
108
+ @template.snapshot_collection.ensure_index spec, options
109
+ end
110
+ end # StorageStrategy
111
+ end # Mongo
112
+ end # EventStore
113
+ end # Synapse
@@ -0,0 +1,73 @@
1
+ module Synapse
2
+ module EventStore
3
+ module Mongo
4
+ # Represents a mechanism for accessing collections required by the Mongo event store
5
+ # @abstract
6
+ class MongoTemplate
7
+ # Returns a reference to the collection containing domain events
8
+ #
9
+ # @abstract
10
+ # @return [Mongo::Collection]
11
+ def event_collection; end
12
+
13
+ # Returns a reference to the collection containing snapshot events
14
+ #
15
+ # @abstract
16
+ # @return [Mongo::Collection]
17
+ def snapshot_collection; end
18
+ end
19
+
20
+ class DefaultMongoTemplate
21
+ # @return [String] Name of the database to use
22
+ attr_accessor :database_name
23
+
24
+ # @return [String] Username to authenticate with (optional)
25
+ attr_accessor :username
26
+
27
+ # @return [String] Password to authenticate with (optional)
28
+ attr_accessor :password
29
+
30
+ # @return [String] Name of the collection containing domain events
31
+ attr_accessor :event_collection
32
+
33
+ # @return [String] Name of the collection containing snapshot events
34
+ attr_accessor :snapshot_collection
35
+
36
+ # @param [Mongo::MongoClient] client
37
+ # @return [undefined]
38
+ def initialize(client)
39
+ @client = client
40
+
41
+ @database_name = 'synapse'
42
+ @event_collection_name = 'domain_events'
43
+ @snapshot_collection_name = 'snapshot_events'
44
+ end
45
+
46
+ # @return [Mongo::Collection]
47
+ def event_collection
48
+ database.collection @event_collection_name
49
+ end
50
+
51
+ # @return [Mongo::Collection]
52
+ def snapshot_collection
53
+ database.collection @snapshot_collection_name
54
+ end
55
+
56
+ private
57
+
58
+ # @return [Mongo::DB]
59
+ def database
60
+ unless @database
61
+ @database = @client.db @database_name
62
+
63
+ if @username and @password
64
+ @database.authenticate @username, @password
65
+ end
66
+ end
67
+
68
+ @database
69
+ end
70
+ end # DefaultMongoTemplate
71
+ end # Mongo
72
+ end # EventStore
73
+ end # Synapse
@@ -0,0 +1,23 @@
1
+ require 'securerandom'
2
+
3
+ module Synapse
4
+ # Represents a mechanism for generating a unique identifier for domain objects
5
+ class IdentifierFactory
6
+ class_attribute :instance
7
+ end
8
+
9
+ # Implementation of an identifier factory that generates pseudo-random GUIDs
10
+ #
11
+ # @example The identifier format produced by this factory
12
+ # factory = GuidIdentifierFactory.new
13
+ # factory.generate # => "8f0c580b-5a0f-4fc7-9025-c39072ae073d"
14
+ class GuidIdentifierFactory
15
+ # Generates a pseudo-random GUID
16
+ # @return [String] The newly generated unique identifier
17
+ def generate
18
+ SecureRandom.uuid
19
+ end
20
+ end
21
+
22
+ ActiveSupport.run_load_hooks :identifier_factory, IdentifierFactory
23
+ end
@@ -0,0 +1,101 @@
1
+ module Synapse
2
+ # Representation of a message containing a payload and metadata
3
+ #
4
+ # Instead of using this class directly, it is recommended to use a subclass specifically
5
+ # for commands, events or domain events.
6
+ #
7
+ # Two messages with the same identifier should be interpreted as different representations
8
+ # of the same conceptual message. In such case, the metadata may be different for both
9
+ # representations. The payload *may* be identical.
10
+ class Message
11
+ # Unique identifier of this message
12
+ # @return [String]
13
+ attr_reader :id
14
+
15
+ # Metadata attached to this message by the application
16
+ # @return [Hash]
17
+ attr_reader :metadata
18
+
19
+ # Payload of this message; examples include commands and events. A payload is expected to
20
+ # be immutable to provide thread safety.
21
+ #
22
+ # @return [Object]
23
+ attr_reader :payload
24
+
25
+ # @param [String] id
26
+ # @param [Hash] metadata
27
+ # @param [Object] payload
28
+ # @return [undefined]
29
+ def initialize(id, metadata, payload)
30
+ @id = id
31
+ @metadata = metadata
32
+ @payload = payload
33
+
34
+ @metadata.freeze
35
+ end
36
+
37
+ # Returns the class of the payload of this message; use this instead of calling payload
38
+ # and class, in case of lazily deserializing messages.
39
+ #
40
+ # @return [Class]
41
+ def payload_type
42
+ @payload.class
43
+ end
44
+
45
+ # Returns a copy of this message with the given metadata merged in
46
+ #
47
+ # @param [Hash] additional_metadata
48
+ # @return [Message]
49
+ def and_metadata(additional_metadata)
50
+ if additional_metadata.empty?
51
+ return self
52
+ end
53
+
54
+ builder = self.class.builder.new
55
+ build_duplicate builder, @metadata.merge(additional_metadata)
56
+ builder.build
57
+ end
58
+
59
+ # Returns a copy of this message with the metadata replaced with the given metadata
60
+ #
61
+ # @param [Hash] replacement_metadata
62
+ # @return [Message]
63
+ def with_metadata(replacement_metadata)
64
+ if @metadata == replacement_metadata
65
+ return self
66
+ end
67
+
68
+ builder = self.class.builder.new
69
+ build_duplicate builder, replacement_metadata
70
+ builder.build
71
+ end
72
+
73
+ # Yields a message builder that can be used to produce a message
74
+ #
75
+ # @see MessageBuilder#build
76
+ # @yield [MessageBuilder]
77
+ # @return [Message]
78
+ def self.build(&block)
79
+ builder.build(&block)
80
+ end
81
+
82
+ # Returns the type of builder that can be used to build this type of message
83
+ # @return [Class]
84
+ def self.builder
85
+ MessageBuilder
86
+ end
87
+
88
+ protected
89
+
90
+ # Populates a duplicated message with attributes from this message
91
+ #
92
+ # @param [MessageBuilder] builder
93
+ # @param [Hash] metadata
94
+ # @return [undefined]
95
+ def build_duplicate(builder, metadata)
96
+ builder.id = @id
97
+ builder.metadata = metadata
98
+ builder.payload = @payload
99
+ end
100
+ end
101
+ end