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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +3 -1
- data/lib/event_sourcery/aggregate_root.rb +80 -1
- data/lib/event_sourcery/config.rb +32 -5
- data/lib/event_sourcery/errors.rb +8 -3
- data/lib/event_sourcery/event.rb +61 -0
- data/lib/event_sourcery/event_body_serializer.rb +16 -0
- data/lib/event_sourcery/event_processing/error_handlers/constant_retry.rb +9 -2
- data/lib/event_sourcery/event_processing/error_handlers/error_handler.rb +4 -1
- data/lib/event_sourcery/event_processing/error_handlers/exponential_backoff_retry.rb +15 -6
- data/lib/event_sourcery/event_processing/error_handlers/no_retry.rb +1 -0
- data/lib/event_sourcery/event_processing/esp_process.rb +6 -1
- data/lib/event_sourcery/event_processing/esp_runner.rb +2 -0
- data/lib/event_sourcery/event_processing/event_stream_processor.rb +42 -1
- data/lib/event_sourcery/event_processing/event_stream_processor_registry.rb +16 -0
- data/lib/event_sourcery/event_store/poll_waiter.rb +10 -0
- data/lib/event_sourcery/event_store/signal_handling_subscription_master.rb +7 -0
- data/lib/event_sourcery/event_store/subscription.rb +15 -0
- data/lib/event_sourcery/memory/config.rb +36 -0
- data/lib/event_sourcery/memory/event_store.rb +59 -0
- data/lib/event_sourcery/memory/projector.rb +25 -0
- data/lib/event_sourcery/memory/tracker.rb +19 -0
- data/lib/event_sourcery/repository.rb +28 -0
- data/lib/event_sourcery/rspec/event_store_shared_examples.rb +59 -74
- data/lib/event_sourcery/version.rb +2 -1
- data/lib/event_sourcery.rb +28 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1952be35c93c960587ad23886643f74d4825e18
|
4
|
+
data.tar.gz: 58fbfc8343e6c673b6f003a7d478e05dbdd11fef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
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
|
-
|
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
|
data/lib/event_sourcery/event.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
7
|
-
|
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 =
|
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 <
|
37
|
+
@retry_interval *= 2 if @retry_interval < MAX_RETRY_INTERVAL
|
29
38
|
else
|
30
39
|
@error_event_uuid = error.event.uuid
|
31
|
-
@retry_interval =
|
40
|
+
@retry_interval = DEFAULT_RETRY_INTERVAL
|
32
41
|
end
|
33
42
|
else
|
34
|
-
@retry_interval =
|
43
|
+
@retry_interval = DEFAULT_RETRY_INTERVAL
|
35
44
|
end
|
36
45
|
end
|
37
46
|
end
|
@@ -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
|