synapse-core 0.5.1 → 0.5.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 (101) hide show
  1. checksums.yaml +7 -0
  2. data/lib/synapse.rb +1 -1
  3. data/lib/synapse/command.rb +1 -1
  4. data/lib/synapse/command/command_handler.rb +1 -1
  5. data/lib/synapse/command/mapping.rb +71 -0
  6. data/lib/synapse/command/message.rb +0 -16
  7. data/lib/synapse/command/simple_command_bus.rb +5 -2
  8. data/lib/synapse/common/message.rb +16 -0
  9. data/lib/synapse/configuration.rb +1 -18
  10. data/lib/synapse/configuration/component/command_bus.rb +7 -24
  11. data/lib/synapse/configuration/component/command_bus/simple_command_bus.rb +31 -7
  12. data/lib/synapse/configuration/component/event_bus.rb +3 -8
  13. data/lib/synapse/configuration/component/event_sourcing.rb +11 -8
  14. data/lib/synapse/configuration/component/event_sourcing/aggregate_snapshot_taker.rb +48 -0
  15. data/lib/synapse/configuration/component/event_sourcing/interval_snapshot_policy.rb +33 -0
  16. data/lib/synapse/configuration/component/event_sourcing/repository.rb +36 -1
  17. data/lib/synapse/configuration/component/repository.rb +4 -8
  18. data/lib/synapse/configuration/component/serialization.rb +5 -16
  19. data/lib/synapse/configuration/component/uow.rb +3 -8
  20. data/lib/synapse/configuration/component/upcasting.rb +3 -8
  21. data/lib/synapse/configuration/container_builder.rb +29 -2
  22. data/lib/synapse/configuration/definition_builder.rb +47 -23
  23. data/lib/synapse/domain/message.rb +0 -16
  24. data/lib/synapse/event_bus.rb +1 -1
  25. data/lib/synapse/event_bus/event_listener.rb +1 -1
  26. data/lib/synapse/event_bus/mapping.rb +47 -0
  27. data/lib/synapse/event_sourcing.rb +3 -2
  28. data/lib/synapse/event_sourcing/aggregate_factory.rb +4 -3
  29. data/lib/synapse/event_sourcing/aggregate_root.rb +17 -0
  30. data/lib/synapse/event_sourcing/conflict_resolver.rb +3 -0
  31. data/lib/synapse/event_sourcing/member.rb +34 -6
  32. data/lib/synapse/event_sourcing/repository.rb +17 -0
  33. data/lib/synapse/event_sourcing/snapshot/aggregate_taker.rb +38 -0
  34. data/lib/synapse/event_sourcing/snapshot/policy.rb +27 -0
  35. data/lib/synapse/event_sourcing/snapshot/taker.rb +2 -37
  36. data/lib/synapse/event_sourcing/snapshot/unit_listener.rb +26 -0
  37. data/lib/synapse/event_sourcing/stream_decorator.rb +8 -6
  38. data/lib/synapse/event_store/errors.rb +2 -2
  39. data/lib/synapse/mapping.rb +2 -0
  40. data/lib/synapse/mapping/mapper.rb +75 -0
  41. data/lib/synapse/{wiring/wire.rb → mapping/mapping.rb} +8 -8
  42. data/lib/synapse/process_manager.rb +2 -2
  43. data/lib/synapse/process_manager/mapping/process.rb +44 -0
  44. data/lib/synapse/process_manager/{wiring → mapping}/process_manager.rb +13 -13
  45. data/lib/synapse/process_manager/process.rb +3 -3
  46. data/lib/synapse/repository/locking.rb +14 -8
  47. data/lib/synapse/upcasting/upcaster_chain.rb +2 -2
  48. data/lib/synapse/version.rb +1 -1
  49. data/test/command/{wiring_test.rb → mapping_test.rb} +11 -11
  50. data/test/configuration/component/command_bus/simple_command_bus_test.rb +30 -0
  51. data/test/configuration/component/event_bus/simple_event_bus_test.rb +2 -2
  52. data/test/configuration/component/event_sourcing/repository_test.rb +71 -0
  53. data/test/configuration/component/repository/simple_repository_test.rb +35 -0
  54. data/test/configuration/component/upcasting/upcaster_chain_test.rb +29 -0
  55. data/test/configuration/container_builder_test.rb +4 -6
  56. data/test/event_bus/{wiring_test.rb → mapping_test.rb} +6 -6
  57. data/test/event_sourcing/aggregate_factory_test.rb +5 -1
  58. data/test/event_sourcing/aggregate_root_test.rb +1 -0
  59. data/test/event_sourcing/fixtures.rb +21 -21
  60. data/test/event_sourcing/repository_test.rb +10 -0
  61. data/test/event_sourcing/snapshot/aggregate_taker_test.rb +1 -1
  62. data/test/event_sourcing/snapshot/interval_policy_test.rb +24 -0
  63. data/test/process_manager/{wiring → mapping}/fixtures.rb +7 -8
  64. data/test/process_manager/{wiring → mapping}/process_manager_test.rb +6 -6
  65. data/test/process_manager/{wiring → mapping}/process_test.rb +3 -3
  66. data/test/serialization/converter/chain_test.rb +2 -2
  67. data/test/serialization/converter/factory_test.rb +2 -2
  68. data/test/serialization/converter/identity_test.rb +1 -1
  69. data/test/serialization/converter/json_test.rb +2 -2
  70. data/test/serialization/converter/ox_test.rb +2 -2
  71. data/test/serialization/lazy_object_test.rb +1 -1
  72. data/test/serialization/message/metadata_test.rb +1 -1
  73. data/test/serialization/message/serialization_aware_message_test.rb +5 -5
  74. data/test/serialization/message/serialized_message_builder_test.rb +1 -1
  75. data/test/serialization/message/serialized_message_test.rb +5 -5
  76. data/test/serialization/message/serializer_test.rb +2 -2
  77. data/test/serialization/revision_resolver_test.rb +1 -1
  78. data/test/serialization/serialized_object_test.rb +2 -2
  79. data/test/serialization/serialized_type_test.rb +2 -2
  80. data/test/serialization/serializer/marshal_test.rb +1 -1
  81. data/test/serialization/serializer/oj_test.rb +1 -1
  82. data/test/serialization/serializer/ox_test.rb +2 -2
  83. data/test/serialization/serializer_test.rb +1 -1
  84. data/test/uow/factory_test.rb +1 -1
  85. data/test/uow/outer_commit_listener_test.rb +4 -4
  86. data/test/uow/provider_test.rb +5 -5
  87. data/test/uow/uow_test.rb +19 -17
  88. data/test/upcasting/chain_test.rb +1 -1
  89. data/test/upcasting/data_test.rb +3 -1
  90. metadata +30 -37
  91. data/lib/synapse/command/wiring.rb +0 -47
  92. data/lib/synapse/event_bus/wiring.rb +0 -20
  93. data/lib/synapse/event_sourcing/snapshot/count_stream.rb +0 -86
  94. data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +0 -91
  95. data/lib/synapse/process_manager/wiring/process.rb +0 -27
  96. data/lib/synapse/wiring.rb +0 -3
  97. data/lib/synapse/wiring/message_wiring.rb +0 -76
  98. data/lib/synapse/wiring/wire_registry.rb +0 -61
  99. data/test/event_sourcing/snapshot/integration_test.rb +0 -65
  100. data/test/wiring/wire_registry_test.rb +0 -60
  101. data/test/wiring/wire_test.rb +0 -51
@@ -8,6 +8,7 @@ require 'synapse/event_sourcing/member'
8
8
  require 'synapse/event_sourcing/aggregate_root'
9
9
  require 'synapse/event_sourcing/entity'
10
10
 
11
- require 'synapse/event_sourcing/snapshot/count_stream'
12
- require 'synapse/event_sourcing/snapshot/count_trigger'
11
+ require 'synapse/event_sourcing/snapshot/policy'
13
12
  require 'synapse/event_sourcing/snapshot/taker'
13
+ require 'synapse/event_sourcing/snapshot/aggregate_taker'
14
+ require 'synapse/event_sourcing/snapshot/unit_listener'
@@ -21,7 +21,7 @@ module Synapse
21
21
  # @abstract
22
22
  # @return [String] Type identifier used to store the aggregate in the event store
23
23
  def type_identifier; end
24
- end
24
+ end # AggregateFactory
25
25
 
26
26
  # Aggregate factory that uses a convention to create instances of aggregates
27
27
  class GenericAggregateFactory < AggregateFactory
@@ -46,6 +46,7 @@ module Synapse
46
46
 
47
47
  if payload.is_a? AggregateRoot
48
48
  aggregate = payload
49
+ aggregate.reset_initial_version
49
50
  else
50
51
  aggregate = @aggregate_type.allocate
51
52
  end
@@ -64,6 +65,6 @@ module Synapse
64
65
  def post_process(aggregate)
65
66
  aggregate
66
67
  end
67
- end
68
- end
68
+ end # GenericAggregateFactory
69
+ end # EventSourcing
69
70
  end
@@ -19,11 +19,26 @@ module Synapse
19
19
  end
20
20
  end
21
21
 
22
+ # The sequence number of the first event that the aggregate was initialized from
23
+ #
24
+ # If the aggregate was initialized from a snapshot, this should be reset to the sequence
25
+ # number of the last event in the snapshot. Otherwise, this will be the sequence number
26
+ # of the first event contained in the event stream used to initialize the aggregate.
27
+ #
28
+ # @return [Integer]
29
+ attr_reader :initial_version
30
+
22
31
  # @return [Integer] The sequence number of the last committed event
23
32
  def version
24
33
  last_committed_sequence_number
25
34
  end
26
35
 
36
+ # Resets the initial version to the current version of the aggregate
37
+ # @return [undefined]
38
+ def reset_initial_version
39
+ @initial_version = last_committed_sequence_number
40
+ end
41
+
27
42
  # Initializes the state of this aggregate from the given domain event stream
28
43
  #
29
44
  # @raise [RuntimeError] If aggregate has already been initialized
@@ -36,6 +51,8 @@ module Synapse
36
51
 
37
52
  pre_initialize
38
53
 
54
+ @initial_version = stream.peek.sequence_number
55
+
39
56
  last_sequence_number = nil
40
57
 
41
58
  stream.each do |event|
@@ -2,11 +2,14 @@ module Synapse
2
2
  module EventSourcing
3
3
  # Represents a mechanism that is capable of detecting conflicts between applied changes
4
4
  # to the aggregate and unseen changes made to the aggregate.
5
+ #
6
+ # @abstract
5
7
  class ConflictResolver
6
8
  # Checks the list of changes applied to the aggregate and compares it to the list of
7
9
  # events already applied to the aggregate. If a conflict is detected, this should throw
8
10
  # an exception. Otherwise, the changes will be applied.
9
11
  #
12
+ # @abstract
10
13
  # @raise [ConflictingModificationException] If any conflicts were detected
11
14
  # @param [Array] applied_changes List of changes applied to the aggregate
12
15
  # @param [Array] committed_changes List of events that were unexpected by the command handler
@@ -4,7 +4,17 @@ module Synapse
4
4
  # applied to the aggregate
5
5
  module Member
6
6
  extend ActiveSupport::Concern
7
- include Wiring::MessageWiring
7
+
8
+ included do
9
+ # @return [Mapper::Mapping]
10
+ class_attribute :command_mapper
11
+
12
+ # @return [Mapper::Mapping]
13
+ class_attribute :event_mapper
14
+
15
+ self.command_mapper = Mapping::Mapper.new false
16
+ self.event_mapper = Mapping::Mapper.new true
17
+ end
8
18
 
9
19
  module ClassMethods
10
20
  # Registers an instance variable as a child entity
@@ -22,6 +32,24 @@ module Synapse
22
32
  def child_entities
23
33
  @child_entities ||= Set.new
24
34
  end
35
+
36
+ # @see Mapper#map
37
+ # @param [Class] type
38
+ # @param [Object...] args
39
+ # @param [Proc] block
40
+ # @return [undefined]
41
+ def map_command(type, *args, &block)
42
+ commnad_mapper.map type, *args, &block
43
+ end
44
+
45
+ # @see Mapper#map
46
+ # @param [Class] type
47
+ # @param [Object...] args
48
+ # @param [Proc] block
49
+ # @return [undefined]
50
+ def map_event(type, *args, &block)
51
+ event_mapper.map type, *args, &block
52
+ end
25
53
  end
26
54
 
27
55
  protected
@@ -57,9 +85,9 @@ module Synapse
57
85
  # @param [EventMessage] event
58
86
  # @return [undefined]
59
87
  def handle_event(event)
60
- wire = self.wire_registry.wire_for event.payload_type
61
- if wire
62
- invoke_wire event, wire
88
+ mapping = self.event_mapper.mapping_for event.payload_type
89
+ if mapping
90
+ mapping.invoke self, event.payload
63
91
  end
64
92
  end
65
93
 
@@ -72,6 +100,6 @@ module Synapse
72
100
  entity.is_a? Member
73
101
  end
74
102
  end
75
- end
76
- end
103
+ end # Member
104
+ end # EventSourcing
77
105
  end
@@ -12,6 +12,12 @@ module Synapse
12
12
  # @return [EventStore]
13
13
  attr_reader :event_store
14
14
 
15
+ # @return [SnapshotPolicy]
16
+ attr_accessor :snapshot_policy
17
+
18
+ # @return [SnapshotTaker]
19
+ attr_accessor :snapshot_taker
20
+
15
21
  # @return [Array<EventStreamDecorator>]
16
22
  attr_reader :stream_decorators
17
23
 
@@ -76,6 +82,17 @@ module Synapse
76
82
  aggregate
77
83
  end
78
84
 
85
+ # @param [AggregateRoot] aggregate
86
+ # @return [undefined]
87
+ def post_registration(aggregate)
88
+ if @snapshot_policy and @snapshot_taker
89
+ listener =
90
+ SnapshotUnitOfWorkListener.new type_identifier, aggregate, @snapshot_policy, @snapshot_taker
91
+
92
+ register_listener listener
93
+ end
94
+ end
95
+
79
96
  # @return [Class]
80
97
  def aggregate_type
81
98
  @aggregate_factory.aggregate_type
@@ -0,0 +1,38 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Snapshot taker that uses the actual aggregate and its state to create a snapshot event
4
+ class AggregateSnapshotTaker < SnapshotTaker
5
+ # @param [SnapshotEventStore] event_store
6
+ # @return [undefined]
7
+ def initialize(event_store)
8
+ @aggregate_factories = Hash.new
9
+ @event_store = event_store
10
+ end
11
+
12
+ # @param [AggregateFactory] factory
13
+ # @return [undefined]
14
+ def register_factory(factory)
15
+ @aggregate_factories.store factory.type_identifier, factory
16
+ end
17
+
18
+ # @param [String] type_identifier
19
+ # @param [Object] aggregate_id
20
+ # @return [undefined]
21
+ def schedule_snapshot(type_identifier, aggregate_id)
22
+ stream = @event_store.read_events type_identifier, aggregate_id
23
+ factory = @aggregate_factories.fetch type_identifier
24
+
25
+ aggregate = factory.create_aggregate aggregate_id, stream.peek
26
+ aggregate.initialize_from_stream stream
27
+
28
+ snapshot = Domain::DomainEventMessage.build do |builder|
29
+ builder.payload = aggregate
30
+ builder.aggregate_id = aggregate.id
31
+ builder.sequence_number = aggregate.version
32
+ end
33
+
34
+ @event_store.append_snapshot_event type_identifier, snapshot
35
+ end
36
+ end # AggregateSnapshotTaker
37
+ end # EventSourcing
38
+ end
@@ -0,0 +1,27 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Represents a mechanism for determining if an aggregate should have a snapshot taken
4
+ class SnapshotPolicy
5
+ # Returns true if a snapshot should be scheduled for the given aggregate
6
+ #
7
+ # @param [AggregateRoot] aggregate
8
+ # @return [Boolean]
9
+ def should_snapshot?(aggregate); end
10
+ end # SnapshotPolicy
11
+
12
+ # Snapshot policy that takes a snapshot if the number of events committed in an aggregate since
13
+ # the last snapshot goes over the configured threshold
14
+ class IntervalSnapshotPolicy < SnapshotPolicy
15
+ # @return [undefined]
16
+ def initialize(threshold)
17
+ @threshold = threshold
18
+ end
19
+
20
+ # @param [AggregateRoot] aggregate
21
+ # @return [Boolean]
22
+ def should_snapshot?(aggregate)
23
+ (aggregate.version - (aggregate.initial_version or 0)) >= @threshold
24
+ end
25
+ end # IntervalSnapshotPolicy
26
+ end # EventSourcing
27
+ end
@@ -15,41 +15,6 @@ module Synapse
15
15
  # @param [Object] aggregate_id
16
16
  # @return [undefined]
17
17
  def schedule_snapshot(type_identifier, aggregate_id); end
18
- end
19
-
20
- # Snapshot taker that uses the actual aggregate and its state to create a snapshot event
21
- class AggregateSnapshotTaker < SnapshotTaker
22
- # @param [SnapshotEventStore] event_store
23
- # @return [undefined]
24
- def initialize(event_store)
25
- @aggregate_factories = Hash.new
26
- @event_store = event_store
27
- end
28
-
29
- # @param [AggregateFactory] factory
30
- # @return [undefined]
31
- def register_factory(factory)
32
- @aggregate_factories.store factory.type_identifier, factory
33
- end
34
-
35
- # @param [String] type_identifier
36
- # @param [Object] aggregate_id
37
- # @return [undefined]
38
- def schedule_snapshot(type_identifier, aggregate_id)
39
- stream = @event_store.read_events type_identifier, aggregate_id
40
- factory = @aggregate_factories.fetch type_identifier
41
-
42
- aggregate = factory.create_aggregate aggregate_id, stream.peek
43
- aggregate.initialize_from_stream stream
44
-
45
- snapshot = Domain::DomainEventMessage.build do |builder|
46
- builder.payload = aggregate
47
- builder.aggregate_id = aggregate.id
48
- builder.sequence_number = aggregate.version
49
- end
50
-
51
- @event_store.append_snapshot_event type_identifier, snapshot
52
- end
53
- end
54
- end
18
+ end # SnapshotTaker
19
+ end # EventSourcing
55
20
  end
@@ -0,0 +1,26 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Unit of work listener that schedules snapshots
4
+ class SnapshotUnitOfWorkListener < UnitOfWork::UnitOfWorkListener
5
+ # @param [String] type_identifier
6
+ # @param [AggregateRoot] aggregate
7
+ # @param [SnapshotPolicy] policy
8
+ # @param [SnapshotTaker] taker
9
+ # @return [undefined]
10
+ def initialize(type_identifier, aggregate, policy, taker)
11
+ @type_identifier = type_identifier
12
+ @aggregate = aggregate
13
+ @policy = policy
14
+ @taker = taker
15
+ end
16
+
17
+ # @param [UnitOfWork] unit
18
+ # @return [undefined]
19
+ def on_cleanup(unit)
20
+ if @policy.should_snapshot? @aggregate
21
+ @taker.schedule_snapshot @type_identifier, @aggregate.id
22
+ end
23
+ end
24
+ end # SnapshotUnitOfWorkListener
25
+ end # EventSourcing
26
+ end
@@ -5,21 +5,23 @@ module Synapse
5
5
  class EventStreamDecorator
6
6
  # Decorates an event stream when it is read from the event store
7
7
  #
8
- # @abstract
9
8
  # @param [String] type_identifier
10
9
  # @param [Object] aggregate_id
11
10
  # @param [DomainEventStream] stream
12
11
  # @return [DomainEventStream]
13
- def decorate_for_read(type_identifier, aggregate_id, stream); end
12
+ def decorate_for_read(type_identifier, aggregate_id, stream)
13
+ stream
14
+ end
14
15
 
15
16
  # Decorates an event stream when it is appended to the event store
16
17
  #
17
- # @abstract
18
18
  # @param [String] type_identifier
19
19
  # @param [AggregateRoot] aggregate
20
20
  # @param [DomainEventStream] stream
21
21
  # @return [DomainEventStream]
22
- def decorate_for_append(type_identifier, aggregate, stream); end
23
- end
24
- end
22
+ def decorate_for_append(type_identifier, aggregate, stream)
23
+ stream
24
+ end
25
+ end # EventStreamDecorator
26
+ end # EventSourcing
25
27
  end
@@ -11,6 +11,6 @@ module Synapse
11
11
  def initialize(type_identifier, aggregate_id)
12
12
  super 'Stream not found for [%s] [%s]' % [type_identifier, aggregate_id]
13
13
  end
14
- end
15
- end
14
+ end # StreamNotFoundError
15
+ end # EventStore
16
16
  end
@@ -0,0 +1,2 @@
1
+ require 'synapse/mapping/mapper'
2
+ require 'synapse/mapping/mapping'
@@ -0,0 +1,75 @@
1
+ module Synapse
2
+ module Mapping
3
+ class Mapper
4
+ # @param [Boolean] duplicates_allowed
5
+ # @return [undefined]
6
+ def initialize(duplicates_allowed)
7
+ @duplicates_allowed = duplicates_allowed
8
+ @mappings = Array.new
9
+ end
10
+
11
+ # Yields the type that each mapping is registered for
12
+ #
13
+ # @yield [Class]
14
+ # @return [undefined]
15
+ def each_type
16
+ @mappings.each do |mapping|
17
+ yield mapping.type
18
+ end
19
+ end
20
+
21
+ # @raise [DuplicateMappingError] If duplicates aren't allowed and another mapping exists that
22
+ # maps the exact same type as the given mapping
23
+ # @param [Class] type
24
+ # @param [Object...] args
25
+ # @param [Proc] block
26
+ # @return [undefined]
27
+ def map(type, *args, &block)
28
+ options = args.extract_options!
29
+ mapping = create_from type, options, &block
30
+
31
+ unless @duplicates_allowed
32
+ if @mappings.include? mapping
33
+ raise DuplicateMappingError
34
+ end
35
+ end
36
+
37
+ @mappings.push mapping
38
+ @mappings.sort!
39
+ end
40
+
41
+ # Retrieves the most specific mapping for a given type, if any
42
+ #
43
+ # @param [Class] target_type
44
+ # @return [Mapping]
45
+ def mapping_for(target_type)
46
+ @mappings.find do |mapping|
47
+ mapping.type >= target_type
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # @param [Class] type
54
+ # @param [Hash] options
55
+ # @param [Proc] block
56
+ # @return [Mapping]
57
+ def create_from(type, options, &block)
58
+ to = options.delete :to
59
+ unless to
60
+ unless block
61
+ raise ArgumentError, 'Expected block or option :to'
62
+ end
63
+
64
+ to = block
65
+ end
66
+
67
+ Mapping.new type, options, to
68
+ end
69
+ end
70
+
71
+ # Raised if a mapping registry doesn't allow duplicates and an attempt is made to map the same
72
+ # type to multiple handlers
73
+ class DuplicateMappingError < NonTransientError; end
74
+ end
75
+ end