event_sourcery 0.14.0 → 0.15.0

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