event_sourcery 0.14.0 → 0.15.0

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +3 -1
  4. data/lib/event_sourcery/aggregate_root.rb +80 -1
  5. data/lib/event_sourcery/config.rb +32 -5
  6. data/lib/event_sourcery/errors.rb +8 -3
  7. data/lib/event_sourcery/event.rb +61 -0
  8. data/lib/event_sourcery/event_body_serializer.rb +16 -0
  9. data/lib/event_sourcery/event_processing/error_handlers/constant_retry.rb +9 -2
  10. data/lib/event_sourcery/event_processing/error_handlers/error_handler.rb +4 -1
  11. data/lib/event_sourcery/event_processing/error_handlers/exponential_backoff_retry.rb +15 -6
  12. data/lib/event_sourcery/event_processing/error_handlers/no_retry.rb +1 -0
  13. data/lib/event_sourcery/event_processing/esp_process.rb +6 -1
  14. data/lib/event_sourcery/event_processing/esp_runner.rb +2 -0
  15. data/lib/event_sourcery/event_processing/event_stream_processor.rb +42 -1
  16. data/lib/event_sourcery/event_processing/event_stream_processor_registry.rb +16 -0
  17. data/lib/event_sourcery/event_store/poll_waiter.rb +10 -0
  18. data/lib/event_sourcery/event_store/signal_handling_subscription_master.rb +7 -0
  19. data/lib/event_sourcery/event_store/subscription.rb +15 -0
  20. data/lib/event_sourcery/memory/config.rb +36 -0
  21. data/lib/event_sourcery/memory/event_store.rb +59 -0
  22. data/lib/event_sourcery/memory/projector.rb +25 -0
  23. data/lib/event_sourcery/memory/tracker.rb +19 -0
  24. data/lib/event_sourcery/repository.rb +28 -0
  25. data/lib/event_sourcery/rspec/event_store_shared_examples.rb +59 -74
  26. data/lib/event_sourcery/version.rb +2 -1
  27. data/lib/event_sourcery.rb +28 -0
  28. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a6cabf34f27962b52571d80fb0a505186cef6e69
4
- data.tar.gz: 3f07fcaf03c74190b2531099f1b4ca10d8312355
3
+ metadata.gz: c1952be35c93c960587ad23886643f74d4825e18
4
+ data.tar.gz: 58fbfc8343e6c673b6f003a7d478e05dbdd11fef
5
5
  SHA512:
6
- metadata.gz: 0a25a29c4d626ae5a179668b4663dc20dc81799948ee38506ce26b3e7b10d7c31e483baf8978507e9cb199b7eda616051b7d8c90e05b5d6af99713430a4fcb83
7
- data.tar.gz: 334f2eb26deba53e6459c00caffec74403ee039fdee14d68c45243f889f64690d20082fb98e1006e50ceefaa647d8770090f9d25785b39ddbe9efe97bca01a67
6
+ metadata.gz: b2aecc3786ef6dfbb7063c50201f0c7b0ff9ad73a368258cc090204c0bd7b865308e9d138475c9f8cdeac3598b6f588de630ccb021335e6f35af1932674a835e
7
+ data.tar.gz: f423097efb6782559310b3ce0697753338a2eceeadddb9e6a199ca5eebf83fb814985b8f1da76389f34dbda63f05f9edbb64c1c348826462177e2ea6e539d470
data/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.0] - 2017-11-29
10
+ ### Added
11
+ - Added in the first version of the yard documentation.
12
+
13
+ ### Changed
14
+ - Improved EventProcessingError messages
15
+ - Fixed typo in constant name `EventSourcery::EventProcessing::ErrorHandlers::ConstantRetry::DEFAULT_RETRY_INTERVAL`
16
+ - Fixed typo in constant name `EventSourcery::EventProcessing::ErrorHandlers::ExponentialBackoffRetry::DEFAULT_RETRY_INTERVAL`
17
+ - Fixed typo in constant name `EventSourcery::EventProcessing::ErrorHandlers::ExponentialBackoffRetry::MAX_RETRY_INTERVAL`
18
+ - Errors of type `Exception` are now logged before being allowed to propagate.
19
+
9
20
  ## [0.14.0] - 2016-6-21
10
21
  ### Added
11
22
  - Added `Event#to_h` method. This returns a hash of the event attributes.
data/README.md CHANGED
@@ -56,6 +56,8 @@ The [example EventSourcery application](https://github.com/envato/event_sourcery
56
56
 
57
57
  Otherwise you will generally need to add both event_sourcery and [event_sourcery-postgres](https://github.com/envato/event_sourcery-postgres) to your application.
58
58
 
59
+ If Event Sourcing or CQRS is a new concept to you, we highly recommend you watch [An In-Depth Look at Event Sourcing With CQRS](https://www.youtube.com/watch?v=EqpalkqJD8M&t=2680s). It explores some of the theory behind both Event Sourcing & CQRS and will help you better understand the building blocks of the Event Sourcery framework.
60
+
59
61
  ## Configuration
60
62
 
61
63
  There are several ways to configure EventSourcery to your liking. The following presents some examples:
@@ -63,7 +65,7 @@ There are several ways to configure EventSourcery to your liking. The following
63
65
  ```ruby
64
66
  EventSourcery.configure do |config|
65
67
  # Add custom reporting of errors occurring during event processing.
66
- # One might set up Rollbar here.
68
+ # One might set up an error reporting service like Rollbar here.
67
69
  config.on_event_processor_error = proc { |exception, processor_name| … }
68
70
 
69
71
  # Enable EventSourcery logging.
@@ -1,5 +1,55 @@
1
1
  module EventSourcery
2
+ #
3
+ # EventSourcery::AggregateRoot provides a foundation for writing your own aggregate root classes.
4
+ # You can use it by including it in your classes, as show in the example code.
5
+ #
6
+ # Excerpt from {https://github.com/envato/event_sourcery/blob/master/docs/core-concepts.md EventSourcery Core Concepts} on Aggregates follows:
7
+ # === Aggregates and Command Handling
8
+ #
9
+ # An aggregate is a cluster of domain objects that can be treated as a single unit.
10
+ # Every transaction is scoped to a single aggregate. An aggregate will have one of its component objects be
11
+ # the aggregate root. Any references from outside the aggregate should only go to the aggregate root.
12
+ # The root can thus ensure the integrity of the aggregate as a whole.
13
+ #
14
+ # — DDD Aggregate
15
+ #
16
+ # Clients execute domain transactions against the system by issuing commands against aggregate roots.
17
+ # The result of these commands is new events being saved to the event store.
18
+ # A typical EventSourcery application will have one or more aggregate roots with multiple commands.
19
+ #
20
+ # The following partial example is taken from the EventSourceryTodoApp.
21
+ # Refer a more complete example {https://github.com/envato/event_sourcery_todo_app/blob/master/app/aggregates/todo.rb here}.
22
+ #
23
+ # @example
24
+ # module EventSourceryTodoApp
25
+ # module Aggregates
26
+ # class Todo
27
+ # include EventSourcery::AggregateRoot
28
+ #
29
+ # # An event handler that updates the aggregate's state from a event
30
+ # apply TodoAdded do |event|
31
+ # @added = true
32
+ # end
33
+ #
34
+ # # Method on the aggregate that processes a command and emits an event as a result
35
+ # def add(payload)
36
+ # raise UnprocessableEntity, "Todo #{id.inspect} already exists" if added
37
+ #
38
+ # apply_event(TodoAdded,
39
+ # aggregate_id: id,
40
+ # body: payload,
41
+ # )
42
+ # end
43
+ #
44
+ # private
45
+ #
46
+ # attr_reader :added
47
+ # end
48
+ # end
49
+ # end
2
50
  module AggregateRoot
51
+ # Raised when the aggregate doesn't have a method to handle a given event.
52
+ # Consider implementing one if you get this error.
3
53
  UnknownEventError = Class.new(RuntimeError)
4
54
 
5
55
  def self.included(base)
@@ -10,8 +60,20 @@ module EventSourcery
10
60
  end
11
61
 
12
62
  module ClassMethods
63
+ # Collection of event handlers for the events that this aggregate cares about
64
+ #
65
+ # @return Hash
13
66
  attr_reader :event_handlers
14
67
 
68
+ # Register an event handler for the specified event(s)
69
+ #
70
+ # @param event_classes one or more event types for which the handler is for
71
+ # @param block the event handler
72
+ #
73
+ # @example
74
+ # apply TodoAdded do |event|
75
+ # @added = true
76
+ # end
15
77
  def apply(*event_classes, &block)
16
78
  event_classes.each do |event_class|
17
79
  @event_handlers[event_class.type] << block
@@ -19,6 +81,11 @@ module EventSourcery
19
81
  end
20
82
  end
21
83
 
84
+ # Load an aggregate instance based on the given ID and events
85
+ #
86
+ # @param id [String] ID (a UUID represented as a string) of the aggregate instance to be loaded
87
+ # @param events [Array] Events from which the aggregate's current state will be formed
88
+ # @param on_unknown_event [Proc] Optional. The proc to be run if an unknown event type (for which no event handler is registered using {ClassMethods#apply}) is to be loaded.
22
89
  def initialize(id, events, on_unknown_event: EventSourcery.config.on_unknown_event)
23
90
  @id = id.to_str
24
91
  @version = 0
@@ -27,8 +94,20 @@ module EventSourcery
27
94
  load_history(events)
28
95
  end
29
96
 
30
- attr_reader :changes, :version
97
+ # Collection of new events that are yet to be persisted
98
+ #
99
+ # @return Array
100
+ attr_reader :changes
31
101
 
102
+ # Current version of the aggregate. This is the same as the number of events
103
+ # currently loaded by the aggregate.
104
+ #
105
+ # @return Integer
106
+ attr_reader :version
107
+
108
+ # Clears any changes present in {changes}
109
+ #
110
+ # @api private
32
111
  def clear_changes
33
112
  @changes.clear
34
113
  end
@@ -2,37 +2,64 @@ require 'logger'
2
2
 
3
3
  module EventSourcery
4
4
  class Config
5
- attr_accessor :on_unknown_event,
6
- :on_event_processor_error,
7
- :event_type_serializer,
8
- :error_handler_class
5
+ # The default Proc to be run when an aggregate loads an event type that
6
+ # it doesn't know how to handle.
7
+ # What's specified here can be overridden when instantiating an aggregate
8
+ # instance. {AggregateRoot#initialize}
9
+ #
10
+ # If no custom Proc is set, by default behaviour is to raise {AggregateRoot::UnknownEventError}
11
+ #
12
+ # @return Proc
13
+ attr_accessor :on_unknown_event
14
+
15
+ # A Proc to be executed on an event processor error.
16
+ # App specific custom logic can be provided.
17
+ # i.e. report to an error reporting service like Rollbar.
18
+ #
19
+ # @return Proc
20
+ attr_accessor :on_event_processor_error
21
+
22
+ # @return EventStore::EventTypeSerializers::Underscored
23
+ attr_accessor :event_type_serializer
24
+
25
+ # @return EventProcessing::ErrorHandlers::ConstantRetry
26
+ attr_accessor :error_handler_class
9
27
 
10
28
  attr_writer :logger,
11
29
  :event_body_serializer,
12
30
  :event_builder
13
31
 
32
+ # @api private
14
33
  def initialize
15
34
  @on_unknown_event = proc { |event, aggregate|
16
35
  raise AggregateRoot::UnknownEventError, "#{event.type} is unknown to #{aggregate.class.name}"
17
36
  }
18
37
  @on_event_processor_error = proc { |exception, processor_name|
19
- # app specific custom logic ie. report to rollbar
38
+ # app specific custom logic ie. report to an error reporting service like Rollbar.
20
39
  }
21
40
  @event_store = nil
22
41
  @event_type_serializer = EventStore::EventTypeSerializers::Underscored.new
23
42
  @error_handler_class = EventProcessing::ErrorHandlers::ConstantRetry
24
43
  end
25
44
 
45
+ # Logger instance used by EventSourcery.
46
+ # By default EventSourcery will log to STDOUT with a log level of Logger::DEBUG
26
47
  def logger
27
48
  @logger ||= ::Logger.new(STDOUT).tap do |logger|
28
49
  logger.level = Logger::DEBUG
29
50
  end
30
51
  end
31
52
 
53
+ # The event builder used by an event store to build event instances.
54
+ # By default {EventStore::EventBuilder} will be used.
55
+ # Provide a custom builder here to change how an event is built.
32
56
  def event_builder
33
57
  @event_builder || EventStore::EventBuilder.new(event_type_serializer: @event_type_serializer)
34
58
  end
35
59
 
60
+ # The event body serializer used by the default event builder
61
+ # ({EventStore::EventBuilder}). By default {EventBodySerializer} will be used.
62
+ # Provide a custom serializer here to change how the event body is serialized.
36
63
  def event_body_serializer
37
64
  @event_body_serializer ||= EventBodySerializer.new
38
65
  .add(Hash, EventBodySerializer::HashSerializer)
@@ -6,14 +6,19 @@ module EventSourcery
6
6
  AtomicWriteToMultipleAggregatesNotSupported = Class.new(Error)
7
7
 
8
8
  class EventProcessingError < Error
9
- attr_reader :event
9
+ attr_reader :event, :processor
10
10
 
11
- def initialize(event)
11
+ def initialize(event:, processor:)
12
12
  @event = event
13
+ @processor = processor
13
14
  end
14
15
 
15
16
  def message
16
- cause.message if cause
17
+ parts = []
18
+ parts << "#<#{processor.class} @@processor_name=#{processor.processor_name.inspect}>"
19
+ parts << "#<#{event.class} @id=#{event.id.inspect}, @uuid=#{event.uuid.inspect}, @type=#{event.type.inspect}>"
20
+ parts << "#<#{cause.class}: #{cause.message}>"
21
+ parts.join("\n") + "\n"
17
22
  end
18
23
  end
19
24
  end
@@ -1,7 +1,11 @@
1
1
  module EventSourcery
2
+ # Represents an Event
2
3
  class Event
3
4
  include Comparable
4
5
 
6
+ # Event type
7
+ #
8
+ # Will return `nil` if called on an instance of {EventSourcery::Event}.
5
9
  def self.type
6
10
  unless self == Event
7
11
  EventSourcery.config.event_type_serializer.serialize(self)
@@ -10,6 +14,42 @@ module EventSourcery
10
14
 
11
15
  attr_reader :id, :uuid, :aggregate_id, :type, :body, :version, :created_at, :correlation_id, :causation_id
12
16
 
17
+ # @!attribute [r] id
18
+ # @return [Integer] unique identifier at the persistent layer
19
+
20
+ # @!attribute [r] uuid
21
+ # @return [String] unique identifier (UUID) for this event.
22
+
23
+ # @!attribute [r] aggregate_id
24
+ # @return [String] aggregate instance UUID to which this event belongs to.
25
+
26
+ # @!attribute [r] type
27
+ # @return event type
28
+
29
+ # @!attribute [r] body
30
+ # @return [Hash] Content of the event body.
31
+
32
+ # @!attribute [r] version
33
+ # @return [String] event version. Used by some event stores to guard against concurrency errors.
34
+
35
+ # @!attribute [r] created_at
36
+ # @return [Time] Created at timestamp (in UTC) for the event.
37
+
38
+ # @!attribute [r] correlation_id
39
+ # @return [String] UUID attached to the event that allows reference to a particular transaction or event chain. This value is often supplied as part of a command issued by clients.
40
+
41
+ # @!attribute [r] causation_id
42
+ # @return [String] UUID of the event that caused this event.
43
+
44
+ #
45
+ # @param id [Integer] Optional. Unique identifier at the persistent layer. By default this will be set by the underlying persistence layer when persisting the event.
46
+ # @param uuid [String] UUID as a string. Optional. Unique identifier for this event. A random UUID will be generated by default.
47
+ # @param aggregate_id [String] UUID as a string. Aggregate instance UUID to which this event belongs to.
48
+ # @param type [Class] Optional. Event type. {Event.type} will be used by default.
49
+ # @param version [String] Optional. Event's aggregate version. Used by some event stores to guard against concurrency errors.
50
+ # @param created_at [Time] Optional. Created at timestamp (in UTC) for the event.
51
+ # @param correlation_id [String] Optional. UUID attached to the event that allows reference to a particular transaction or event chain. This value is often supplied as part of a command issued by clients.
52
+ # @param causation_id [String] Optional. UUID of the event that caused this event.
13
53
  def initialize(id: nil,
14
54
  uuid: SecureRandom.uuid,
15
55
  aggregate_id: nil,
@@ -30,6 +70,7 @@ module EventSourcery
30
70
  @causation_id = causation_id
31
71
  end
32
72
 
73
+ # Is this event persisted?
33
74
  def persisted?
34
75
  !id.nil?
35
76
  end
@@ -46,10 +87,30 @@ module EventSourcery
46
87
  id <=> other.id if other.is_a? Event
47
88
  end
48
89
 
90
+ # create a new event identical to the old event except for the provided changes
91
+ #
92
+ # @param attributes [Hash]
93
+ # @return Event
94
+ # @example
95
+ # old_event = EventSourcery::Event.new(type: "item_added", causation_id: nil)
96
+ # new_event = old_event.with(causation_id: "05781bd6-796a-4a58-8573-b109f683fd99")
97
+ #
98
+ # new_event.type # => "item_added"
99
+ # new_event.causation_id # => "05781bd6-796a-4a58-8573-b109f683fd99"
100
+ #
101
+ # old_event.type # => "item_added"
102
+ # old_event.causation_id # => nil
103
+ #
104
+ # # Of course, with can accept any number of event attributes:
105
+ #
106
+ # new_event = old_event.with(id: 42, version: 77, body: { 'attr' => 'value' })
49
107
  def with(**attributes)
50
108
  self.class.new(**to_h.merge!(attributes))
51
109
  end
52
110
 
111
+ # returns a hash of the event attributes
112
+ #
113
+ # @return Hash
53
114
  def to_h
54
115
  {
55
116
  id: id,
@@ -1,5 +1,10 @@
1
1
  module EventSourcery
2
+ # EventBodySerializer is used for serializing event bodies when persisting events.
2
3
  class EventBodySerializer
4
+ # Serialize the given event body, with the default or the provided, serializer
5
+ #
6
+ # @param event_body event body to be serialized
7
+ # @param serializer Optional. Serializer to be used. By default {Config#event_body_serializer EventSourcery.config.event_body_serializer} will be used.
3
8
  def self.serialize(event_body,
4
9
  serializer: EventSourcery.config.event_body_serializer)
5
10
  serializer.serialize(event_body)
@@ -9,16 +14,25 @@ module EventSourcery
9
14
  @serializers = Hash.new(IdentitySerializer)
10
15
  end
11
16
 
17
+ # Register a serializer (instance or block) for the specified type
18
+ #
19
+ # @param type The type for which the provided serializer will be used for
20
+ # @param serializer Optional. A serializer instance for the given type
21
+ # @param block_serializer [Proc] Optional. A block that performs the serialization
12
22
  def add(type, serializer = nil, &block_serializer)
13
23
  @serializers[type] = serializer || block_serializer
14
24
  self
15
25
  end
16
26
 
27
+ # Serialize the given event body
28
+ #
29
+ # @param object event body to be serialized
17
30
  def serialize(object)
18
31
  serializer = @serializers[object.class]
19
32
  serializer.call(object, &method(:serialize))
20
33
  end
21
34
 
35
+ # Default serializer for Hash objects
22
36
  module HashSerializer
23
37
  def self.call(hash, &serialize)
24
38
  hash.each_with_object({}) do |(key, value), memo|
@@ -27,12 +41,14 @@ module EventSourcery
27
41
  end
28
42
  end
29
43
 
44
+ # Default serializer for Array objects
30
45
  module ArraySerializer
31
46
  def self.call(array, &serialize)
32
47
  array.map(&serialize)
33
48
  end
34
49
  end
35
50
 
51
+ # Default catch all implementation for serializing any object
36
52
  module IdentitySerializer
37
53
  def self.call(object)
38
54
  object
@@ -1,14 +1,21 @@
1
1
  module EventSourcery
2
2
  module EventProcessing
3
3
  module ErrorHandlers
4
+ # Strategies for error handling.
4
5
  class ConstantRetry
5
6
  include EventSourcery::EventProcessing::ErrorHandlers::ErrorHandler
6
- DEFAULT_RETRY_INVERAL = 1
7
+
8
+ # The retry interval used with {with_error_handling}.
9
+ #
10
+ # @api private
11
+ DEFAULT_RETRY_INTERVAL = 1
12
+
7
13
  def initialize(processor_name:)
8
14
  @processor_name = processor_name
9
- @retry_interval = DEFAULT_RETRY_INVERAL
15
+ @retry_interval = DEFAULT_RETRY_INTERVAL
10
16
  end
11
17
 
18
+ # Will yield the block and attempt to retry after a defined retry interval {DEFAULT_RETRY_INTERVAL}.
12
19
  def with_error_handling
13
20
  yield
14
21
  rescue => error
@@ -2,10 +2,13 @@ module EventSourcery
2
2
  module EventProcessing
3
3
  module ErrorHandlers
4
4
  module ErrorHandler
5
+ # The default with_error_handling method. Will always raise NotImplementedError
6
+ #
7
+ # @raise [NotImplementedError]
5
8
  def with_error_handling
6
9
  raise NotImplementedError, 'Please implement #with_error_handling method'
7
10
  end
8
-
11
+
9
12
  private
10
13
 
11
14
  def report_error(error)
@@ -3,14 +3,23 @@ module EventSourcery
3
3
  module ErrorHandlers
4
4
  class ExponentialBackoffRetry
5
5
  include EventSourcery::EventProcessing::ErrorHandlers::ErrorHandler
6
- DEFAULT_RETRY_INVERAL = 1
7
- MAX_RETRY_INVERVAL = 64
6
+
7
+ # The starting value for the retry interval used with {with_error_handling}.
8
+ #
9
+ # @api private
10
+ DEFAULT_RETRY_INTERVAL = 1
11
+
12
+ # The maximum retry interval value to be used with {with_error_handling}.
13
+ #
14
+ # @api private
15
+ MAX_RETRY_INTERVAL = 64
8
16
 
9
17
  def initialize(processor_name:)
10
18
  @processor_name = processor_name
11
- @retry_interval = DEFAULT_RETRY_INVERAL
19
+ @retry_interval = DEFAULT_RETRY_INTERVAL
12
20
  end
13
21
 
22
+ # Will yield the block and attempt to retry in an exponential backoff.
14
23
  def with_error_handling
15
24
  yield
16
25
  rescue => error
@@ -25,13 +34,13 @@ module EventSourcery
25
34
  def update_retry_interval(error)
26
35
  if error.instance_of?(EventSourcery::EventProcessingError)
27
36
  if @error_event_uuid == error.event.uuid
28
- @retry_interval *= 2 if @retry_interval < MAX_RETRY_INVERVAL
37
+ @retry_interval *= 2 if @retry_interval < MAX_RETRY_INTERVAL
29
38
  else
30
39
  @error_event_uuid = error.event.uuid
31
- @retry_interval = DEFAULT_RETRY_INVERAL
40
+ @retry_interval = DEFAULT_RETRY_INTERVAL
32
41
  end
33
42
  else
34
- @retry_interval = DEFAULT_RETRY_INVERAL
43
+ @retry_interval = DEFAULT_RETRY_INTERVAL
35
44
  end
36
45
  end
37
46
  end
@@ -7,6 +7,7 @@ module EventSourcery
7
7
  @processor_name = processor_name
8
8
  end
9
9
 
10
+ # Will yield the block and exit the process if an error is raised.
10
11
  def with_error_handling
11
12
  yield
12
13
  rescue => error
@@ -8,7 +8,9 @@ module EventSourcery
8
8
  @event_source = event_source
9
9
  @subscription_master = subscription_master
10
10
  end
11
-
11
+
12
+ # This will start the Event Stream Processor which will subscribe
13
+ # to the event stream source.
12
14
  def start
13
15
  name_process
14
16
  error_handler.with_error_handling do
@@ -16,6 +18,9 @@ module EventSourcery
16
18
  subscribe_to_event_stream
17
19
  EventSourcery.logger.info("Stopping #{@event_processor.processor_name}")
18
20
  end
21
+ rescue Exception => e
22
+ EventSourcery.logger.error(e)
23
+ raise e
19
24
  end
20
25
 
21
26
  private
@@ -15,6 +15,7 @@ module EventSourcery
15
15
  @exit_status = true
16
16
  end
17
17
 
18
+ # Start each Event Stream Processor in a new child process.
18
19
  def start!
19
20
  with_logging do
20
21
  start_processes
@@ -73,6 +74,7 @@ module EventSourcery
73
74
  send_signal_to_remaining_processes(:KILL)
74
75
  end
75
76
 
77
+
76
78
  def send_signal_to_remaining_processes(signal)
77
79
  Process.kill(signal, *@pids) unless all_processes_terminated?
78
80
  rescue Errno::ESRCH
@@ -18,6 +18,12 @@ module EventSourcery
18
18
  end
19
19
 
20
20
  module ProcessHandler
21
+ # Handler that processes the given event.
22
+ #
23
+ # @raise [EventProcessingError] error raised due to processing isssues
24
+ # @raise [UnableToProcessEventError] raised if unable to process event type
25
+ #
26
+ # @param event [Event] the event to process
21
27
  def process(event)
22
28
  @_event = event
23
29
  handlers = self.class.event_handlers[event.type]
@@ -34,28 +40,46 @@ module EventSourcery
34
40
  end
35
41
  @_event = nil
36
42
  rescue
37
- raise EventProcessingError.new(event)
43
+ raise EventProcessingError.new(event: event, processor: self)
38
44
  end
39
45
  end
40
46
 
41
47
  module ClassMethods
48
+
49
+ # @attr_reader processes_event_types [Array] Process Event Types
50
+ # @attr_reader event_handlers [Hash] Hash of handler blocks keyed by event
42
51
  attr_reader :processes_event_types, :event_handlers
43
52
 
53
+ # Registers the event types to process.
54
+ #
55
+ # @param event_types a collection of event types to process
44
56
  def processes_events(*event_types)
45
57
  @processes_event_types = Array(@processes_event_types) | event_types.map(&:to_s)
46
58
  end
47
59
 
60
+ # Indicate that this class can process all event types. Note that you need to call this method if you
61
+ # intend to process all event types, without calling {ProcessHandler#process} for each event type.
48
62
  def processes_all_events
49
63
  define_singleton_method :processes? do |_|
50
64
  true
51
65
  end
52
66
  end
53
67
 
68
+ # Can this class process this event type.
69
+ # If you use process_all_events this will always return true
70
+ #
71
+ # @param event_type the event type to check
72
+ #
73
+ # @return [True, False]
54
74
  def processes?(event_type)
55
75
  processes_event_types &&
56
76
  processes_event_types.include?(event_type.to_s)
57
77
  end
58
78
 
79
+ # Set the name of the processor.
80
+ # Returns the class name if no name is given.
81
+ #
82
+ # @param name [String] the name of the processor to set
59
83
  def processor_name(name = nil)
60
84
  if name
61
85
  @processor_name = name
@@ -64,6 +88,10 @@ module EventSourcery
64
88
  end
65
89
  end
66
90
 
91
+ # Process the events for the given event types with the given block.
92
+ #
93
+ # @param event_classes the event type classes to process
94
+ # @param block the code block used to process
67
95
  def process(*event_classes, &block)
68
96
  event_classes.each do |event_class|
69
97
  processes_events event_class.type
@@ -72,30 +100,42 @@ module EventSourcery
72
100
  end
73
101
  end
74
102
 
103
+ # Calls processes_event_types method on the instance class
75
104
  def processes_event_types
76
105
  self.class.processes_event_types
77
106
  end
78
107
 
108
+ # Set up the event tracker
79
109
  def setup
80
110
  tracker.setup(processor_name)
81
111
  end
82
112
 
113
+ # Reset the event tracker
83
114
  def reset
84
115
  tracker.reset_last_processed_event_id(processor_name)
85
116
  end
86
117
 
118
+ # Return the last processed event id
119
+ #
120
+ # @return [Int] the id of the last processed event
87
121
  def last_processed_event_id
88
122
  tracker.last_processed_event_id(processor_name)
89
123
  end
90
124
 
125
+ # Calls processor_name method on the instance class
91
126
  def processor_name
92
127
  self.class.processor_name
93
128
  end
94
129
 
130
+ # Calls processes? method on the instance class
95
131
  def processes?(event_type)
96
132
  self.class.processes?(event_type)
97
133
  end
98
134
 
135
+ # Subscribe to the given event source.
136
+ #
137
+ # @param event_source the event source to subscribe to
138
+ # @param subscription_master [SignalHandlingSubscriptionMaster]
99
139
  def subscribe_to(event_source, subscription_master: EventStore::SignalHandlingSubscriptionMaster.new)
100
140
  setup
101
141
  event_source.subscribe(from_id: last_processed_event_id + 1,
@@ -105,6 +145,7 @@ module EventSourcery
105
145
  end
106
146
  end
107
147
 
148
+ # @attr_writer tracker the tracker for the class
108
149
  attr_accessor :tracker
109
150
 
110
151
  private