synapse-core 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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