event_sourcery 1.0.0 → 1.0.3
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 +35 -2
- data/README.md +2 -2
- data/event_sourcery.gemspec +19 -10
- data/lib/event_sourcery/aggregate_root.rb +10 -3
- data/lib/event_sourcery/config.rb +6 -2
- data/lib/event_sourcery/errors.rb +3 -1
- data/lib/event_sourcery/event.rb +37 -19
- data/lib/event_sourcery/event_body_serializer.rb +5 -1
- data/lib/event_sourcery/event_processing/error_handlers/constant_retry.rb +5 -3
- data/lib/event_sourcery/event_processing/error_handlers/error_handler.rb +2 -0
- data/lib/event_sourcery/event_processing/error_handlers/exponential_backoff_retry.rb +8 -4
- data/lib/event_sourcery/event_processing/error_handlers/no_retry.rb +4 -2
- data/lib/event_sourcery/event_processing/esp_process.rb +3 -1
- data/lib/event_sourcery/event_processing/esp_runner.rb +7 -5
- data/lib/event_sourcery/event_processing/event_stream_processor.rb +8 -8
- data/lib/event_sourcery/event_processing/event_stream_processor_registry.rb +2 -0
- data/lib/event_sourcery/event_store/each_by_range.rb +6 -5
- data/lib/event_sourcery/event_store/event_builder.rb +2 -0
- data/lib/event_sourcery/event_store/event_sink.rb +2 -0
- data/lib/event_sourcery/event_store/event_source.rb +2 -0
- data/lib/event_sourcery/event_store/event_type_serializers/class_name.rb +2 -0
- data/lib/event_sourcery/event_store/event_type_serializers/legacy.rb +4 -2
- data/lib/event_sourcery/event_store/event_type_serializers/underscored.rb +17 -13
- data/lib/event_sourcery/event_store/poll_waiter.rb +7 -3
- data/lib/event_sourcery/event_store/signal_handling_subscription_master.rb +3 -1
- data/lib/event_sourcery/event_store/subscription.rb +7 -3
- data/lib/event_sourcery/memory/config.rb +2 -2
- data/lib/event_sourcery/memory/event_store.rb +23 -18
- data/lib/event_sourcery/memory/projector.rb +2 -1
- data/lib/event_sourcery/memory/tracker.rb +3 -1
- data/lib/event_sourcery/repository.rb +2 -0
- data/lib/event_sourcery/rspec/event_store_shared_examples.rb +28 -27
- data/lib/event_sourcery/version.rb +4 -2
- data/lib/event_sourcery.rb +2 -0
- metadata +46 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 415c43691713e1a778ed869039f81ab0af0b3d65b86a1c96805414e46bde2111
|
|
4
|
+
data.tar.gz: 623adde0b5dd6f1b850567c8485bf8204cbc5c38691ead0f9b3f5f947faa6fc0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6fdc34b508b5f3031eaabda5cce803d112e577e585f73716f0a29d3f7d6f5b3fb35f9fee4bd27e732ff5a05f7c0235313f3c08f6edc091a71ff0e253bb5f5f48
|
|
7
|
+
data.tar.gz: d3bd692067e4231ddbf4cbdc8a8de01dc4ce60963adc25166b052a3552e982563b76b4225606d1c18d7333e6c28dc97a9378faf9ae71a02da2f638c4012b7552
|
data/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,46 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
[Unreleased]: https://github.com/envato/event_sourcery/compare/v1.0.3...HEAD
|
|
11
|
+
|
|
12
|
+
## [1.0.3] - 2025-12-29
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Resolve issues as identified by RuboCop ([#245]).
|
|
17
|
+
|
|
18
|
+
[1.0.3]: https://github.com/envato/event_sourcery/compare/v1.0.2...v1.0.3
|
|
19
|
+
[#245]: https://github.com/envato/event_sourcery/pull/245
|
|
20
|
+
|
|
21
|
+
## [1.0.2] - 2025-12-28
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Minor fixups in gem metadata ([#240]).
|
|
26
|
+
- Resolve issues as identified by RuboCop ([#241], [#244]).
|
|
27
|
+
- Removed support for older Ruby versions: Ruby 2.6 or greater is now required ([#243]).
|
|
28
|
+
|
|
29
|
+
[1.0.2]: https://github.com/envato/event_sourcery/compare/v1.0.1...v1.0.2
|
|
30
|
+
[#240]: https://github.com/envato/event_sourcery/pull/240
|
|
31
|
+
[#241]: https://github.com/envato/event_sourcery/pull/241
|
|
32
|
+
[#243]: https://github.com/envato/event_sourcery/pull/243
|
|
33
|
+
[#244]: https://github.com/envato/event_sourcery/pull/244
|
|
34
|
+
|
|
35
|
+
## [1.0.1] - 2025-12-27
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Support Ruby 4.0, make `logger` dependency explicit ([#239])
|
|
40
|
+
|
|
41
|
+
[1.0.1]: https://github.com/envato/event_sourcery/compare/v1.0.0...v1.0.1
|
|
42
|
+
[#239]: https://github.com/envato/event_sourcery/pull/239
|
|
43
|
+
|
|
10
44
|
## [1.0.0] - 2023-11-29
|
|
11
45
|
|
|
12
46
|
### Removed
|
|
13
47
|
- Removed Event.persisted? as it was potentially misleading ([#235])
|
|
14
48
|
|
|
49
|
+
[1.0.0]: https://github.com/envato/event_sourcery/compare/v0.24.0...v1.0.0
|
|
15
50
|
[#235]: https://github.com/envato/event_sourcery/pull/235
|
|
16
51
|
|
|
17
52
|
## [0.24.0] - 2021-11-18
|
|
@@ -261,8 +296,6 @@ moving all Postgres related code into a separate gem.
|
|
|
261
296
|
- EventSourcery no longer depends on Virtus.
|
|
262
297
|
- `Command` and `CommandHandler` have been removed.
|
|
263
298
|
|
|
264
|
-
[Unreleased]: https://github.com/envato/event_sourcery/compare/v0.24.0...HEAD
|
|
265
|
-
[1.0.0]: https://github.com/envato/event_sourcery/compare/v0.24.0...v1.0.0
|
|
266
299
|
[0.24.0]: https://github.com/envato/event_sourcery/compare/v0.23.1...v0.24.0
|
|
267
300
|
[0.23.1]: https://github.com/envato/event_sourcery/compare/v0.23.0...v0.23.1
|
|
268
301
|
[0.23.0]: https://github.com/envato/event_sourcery/compare/v0.22.0...v0.23.0
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# EventSourcery
|
|
2
2
|
|
|
3
|
-
[](https://github.com/envato/event_sourcery/actions/workflows/test.yml)
|
|
4
4
|
|
|
5
5
|
A framework for building event sourced, CQRS applications.
|
|
6
6
|
|
|
@@ -34,7 +34,7 @@ A framework for building event sourced, CQRS applications.
|
|
|
34
34
|
|
|
35
35
|
## Development Status
|
|
36
36
|
|
|
37
|
-
[](https://github.com/envato/event_sourcery/actions/workflows/test.yml)
|
|
38
38
|
|
|
39
39
|
Event Sourcery is in production use at [Envato](http://envato.com).
|
|
40
40
|
|
data/event_sourcery.gemspec
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'event_sourcery/version'
|
|
5
6
|
|
|
@@ -12,22 +13,30 @@ Gem::Specification.new do |spec|
|
|
|
12
13
|
spec.summary = 'Event Sourcing Library'
|
|
13
14
|
spec.description = ''
|
|
14
15
|
spec.homepage = 'https://github.com/envato/event_sourcery'
|
|
16
|
+
spec.license = 'MIT'
|
|
15
17
|
spec.metadata = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
'allowed_push_host' => 'https://rubygems.org',
|
|
19
|
+
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
|
20
|
+
'changelog_uri' => "#{spec.homepage}/blob/HEAD/CHANGELOG.md",
|
|
21
|
+
'documentation_uri' => "https://www.rubydoc.info/gems/event_sourcery/#{spec.version}",
|
|
22
|
+
'source_code_uri' => "#{spec.homepage}/tree/v#{spec.version}"
|
|
23
|
+
}
|
|
20
24
|
|
|
21
|
-
spec.files
|
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
+
f.match(%r{^(\.|Gemfile|Rakefile|bin/|script/|spec/)})
|
|
27
|
+
end
|
|
22
28
|
spec.bindir = 'exe'
|
|
23
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
24
30
|
spec.require_paths = ['lib']
|
|
25
31
|
|
|
26
|
-
spec.required_ruby_version = '>= 2.
|
|
32
|
+
spec.required_ruby_version = '>= 2.6.0'
|
|
33
|
+
|
|
34
|
+
spec.add_runtime_dependency 'logger'
|
|
27
35
|
|
|
36
|
+
spec.add_development_dependency 'benchmark-ips'
|
|
28
37
|
spec.add_development_dependency 'bundler'
|
|
38
|
+
spec.add_development_dependency 'pry'
|
|
29
39
|
spec.add_development_dependency 'rake', '~> 13'
|
|
30
40
|
spec.add_development_dependency 'rspec'
|
|
31
|
-
spec.add_development_dependency '
|
|
32
|
-
spec.add_development_dependency 'benchmark-ips'
|
|
41
|
+
spec.add_development_dependency 'rubocop', '~> 1'
|
|
33
42
|
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
#
|
|
3
5
|
# EventSourcery::AggregateRoot provides a foundation for writing your own aggregate root classes.
|
|
4
6
|
# You can use it by including it in your classes, as show in the example code.
|
|
5
7
|
#
|
|
6
|
-
# Excerpt from {https://github.com/envato/event_sourcery/blob/HEAD/docs/core-concepts.md EventSourcery Core Concepts}
|
|
8
|
+
# Excerpt from {https://github.com/envato/event_sourcery/blob/HEAD/docs/core-concepts.md EventSourcery Core Concepts}
|
|
9
|
+
# on Aggregates follows:
|
|
10
|
+
#
|
|
7
11
|
# === Aggregates and Command Handling
|
|
8
12
|
#
|
|
9
13
|
# An aggregate is a cluster of domain objects that can be treated as a single unit.
|
|
@@ -18,7 +22,8 @@ module EventSourcery
|
|
|
18
22
|
# A typical EventSourcery application will have one or more aggregate roots with multiple commands.
|
|
19
23
|
#
|
|
20
24
|
# The following partial example is taken from the EventSourceryTodoApp.
|
|
21
|
-
# Refer a more complete example
|
|
25
|
+
# Refer a more complete example
|
|
26
|
+
# {https://github.com/envato/event_sourcery_todo_app/blob/HEAD/app/aggregates/todo.rb here}.
|
|
22
27
|
#
|
|
23
28
|
# @example
|
|
24
29
|
# module EventSourceryTodoApp
|
|
@@ -85,7 +90,9 @@ module EventSourcery
|
|
|
85
90
|
#
|
|
86
91
|
# @param id [String] ID (a UUID represented as a string) of the aggregate instance to be loaded
|
|
87
92
|
# @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
|
|
93
|
+
# @param on_unknown_event [Proc] Optional. The proc to be run if an unknown
|
|
94
|
+
# event type (for which no event handler is registered using
|
|
95
|
+
# {ClassMethods#apply}) is to be loaded.
|
|
89
96
|
def initialize(id, events, on_unknown_event: EventSourcery.config.on_unknown_event)
|
|
90
97
|
@id = id.to_str
|
|
91
98
|
@version = 0
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'logger'
|
|
2
4
|
|
|
3
5
|
module EventSourcery
|
|
@@ -60,7 +62,7 @@ module EventSourcery
|
|
|
60
62
|
# Logger instance used by EventSourcery.
|
|
61
63
|
# By default EventSourcery will log to STDOUT with a log level of Logger::INFO
|
|
62
64
|
def logger
|
|
63
|
-
@logger ||= ::Logger.new(
|
|
65
|
+
@logger ||= ::Logger.new($stdout).tap do |logger|
|
|
64
66
|
logger.level = Logger::INFO
|
|
65
67
|
end
|
|
66
68
|
end
|
|
@@ -76,7 +78,9 @@ module EventSourcery
|
|
|
76
78
|
# ({EventStore::EventBuilder}). By default {EventBodySerializer} will be used.
|
|
77
79
|
# Provide a custom serializer here to change how the event body is serialized.
|
|
78
80
|
def event_body_serializer
|
|
79
|
-
@event_body_serializer ||=
|
|
81
|
+
@event_body_serializer ||=
|
|
82
|
+
EventBodySerializer
|
|
83
|
+
.new
|
|
80
84
|
.add(Hash, EventBodySerializer::HashSerializer)
|
|
81
85
|
.add(Array, EventBodySerializer::ArraySerializer)
|
|
82
86
|
.add(Time, &:iso8601)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
Error = Class.new(StandardError)
|
|
3
5
|
UnableToLockProcessorError = Class.new(Error)
|
|
@@ -18,7 +20,7 @@ module EventSourcery
|
|
|
18
20
|
parts << "#<#{processor.class} @@processor_name=#{processor.processor_name.inspect}>"
|
|
19
21
|
parts << "#<#{event.class} @id=#{event.id.inspect}, @uuid=#{event.uuid.inspect}, @type=#{event.type.inspect}>"
|
|
20
22
|
parts << "#<#{cause.class}: #{cause.message}>"
|
|
21
|
-
parts.join("\n")
|
|
23
|
+
"#{parts.join("\n")}\n"
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
data/lib/event_sourcery/event.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
# Represents an Event
|
|
3
5
|
class Event
|
|
@@ -7,9 +9,9 @@ module EventSourcery
|
|
|
7
9
|
#
|
|
8
10
|
# Will return `nil` if called on an instance of {EventSourcery::Event}.
|
|
9
11
|
def self.type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
return if self == Event
|
|
13
|
+
|
|
14
|
+
EventSourcery.config.event_type_serializer.serialize(self)
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
# Use this method to add "upcasting" to your events.
|
|
@@ -61,19 +63,35 @@ module EventSourcery
|
|
|
61
63
|
# @return [Time] Created at timestamp (in UTC) for the event.
|
|
62
64
|
|
|
63
65
|
# @!attribute [r] correlation_id
|
|
64
|
-
# @return [String] UUID attached to the event that allows reference to a
|
|
66
|
+
# @return [String] UUID attached to the event that allows reference to a
|
|
67
|
+
# particular transaction or event chain. This value is often supplied as
|
|
68
|
+
# part of a command issued by clients.
|
|
65
69
|
|
|
66
70
|
# @!attribute [r] causation_id
|
|
67
71
|
# @return [String] UUID of the event that caused this event.
|
|
68
72
|
|
|
69
73
|
#
|
|
70
|
-
# @param id [Integer] Optional. Unique identifier at the persistent layer.
|
|
71
|
-
#
|
|
72
|
-
#
|
|
74
|
+
# @param id [Integer] Optional. Unique identifier at the persistent layer.
|
|
75
|
+
# By default this will be set by the underlying persistence layer when
|
|
76
|
+
# persisting the event.
|
|
77
|
+
#
|
|
78
|
+
# @param uuid [String] UUID as a string. Optional. Unique identifier for
|
|
79
|
+
# this event. A random UUID will be generated by default.
|
|
80
|
+
#
|
|
81
|
+
# @param aggregate_id [String] UUID as a string. Aggregate instance UUID to
|
|
82
|
+
# which this event belongs to.
|
|
83
|
+
#
|
|
73
84
|
# @param type [Class] Optional. Event type. {Event.type} will be used by default.
|
|
74
|
-
#
|
|
85
|
+
#
|
|
86
|
+
# @param version [String] Optional. Event's aggregate version. Used by some
|
|
87
|
+
# event stores to guard against concurrency errors.
|
|
88
|
+
#
|
|
75
89
|
# @param created_at [Time] Optional. Created at timestamp (in UTC) for the event.
|
|
76
|
-
#
|
|
90
|
+
#
|
|
91
|
+
# @param correlation_id [String] Optional. UUID attached to the event that
|
|
92
|
+
# allows reference to a particular transaction or event chain. This value
|
|
93
|
+
# is often supplied as part of a command issued by clients.
|
|
94
|
+
#
|
|
77
95
|
# @param causation_id [String] Optional. UUID of the event that caused this event.
|
|
78
96
|
def initialize(id: nil,
|
|
79
97
|
uuid: SecureRandom.uuid,
|
|
@@ -85,8 +103,8 @@ module EventSourcery
|
|
|
85
103
|
correlation_id: nil,
|
|
86
104
|
causation_id: nil)
|
|
87
105
|
@id = id
|
|
88
|
-
@uuid = uuid
|
|
89
|
-
@aggregate_id = aggregate_id
|
|
106
|
+
@uuid = uuid&.downcase
|
|
107
|
+
@aggregate_id = aggregate_id&.to_str
|
|
90
108
|
@type = self.class.type || type.to_s
|
|
91
109
|
@body = body ? EventSourcery::EventBodySerializer.serialize(body) : {}
|
|
92
110
|
@version = version ? Integer(version) : nil
|
|
@@ -143,15 +161,15 @@ module EventSourcery
|
|
|
143
161
|
# @return Hash
|
|
144
162
|
def to_h
|
|
145
163
|
{
|
|
146
|
-
id:
|
|
147
|
-
uuid:
|
|
148
|
-
aggregate_id:
|
|
149
|
-
type:
|
|
150
|
-
body:
|
|
151
|
-
version:
|
|
152
|
-
created_at:
|
|
164
|
+
id: id,
|
|
165
|
+
uuid: uuid,
|
|
166
|
+
aggregate_id: aggregate_id,
|
|
167
|
+
type: type,
|
|
168
|
+
body: body,
|
|
169
|
+
version: version,
|
|
170
|
+
created_at: created_at,
|
|
153
171
|
correlation_id: correlation_id,
|
|
154
|
-
causation_id:
|
|
172
|
+
causation_id: causation_id
|
|
155
173
|
}
|
|
156
174
|
end
|
|
157
175
|
end
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
# EventBodySerializer is used for serializing event bodies when persisting events.
|
|
3
5
|
class EventBodySerializer
|
|
4
6
|
# Serialize the given event body, with the default or the provided, serializer
|
|
5
7
|
#
|
|
6
8
|
# @param event_body event body to be serialized
|
|
7
|
-
# @param serializer Optional. Serializer to be used. By default
|
|
9
|
+
# @param serializer Optional. Serializer to be used. By default
|
|
10
|
+
# {Config#event_body_serializer EventSourcery.config.event_body_serializer}
|
|
11
|
+
# will be used.
|
|
8
12
|
def self.serialize(event_body,
|
|
9
13
|
serializer: EventSourcery.config.event_body_serializer)
|
|
10
14
|
serializer.serialize(event_body)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventProcessing
|
|
3
5
|
module ErrorHandlers
|
|
@@ -9,7 +11,7 @@ module EventSourcery
|
|
|
9
11
|
#
|
|
10
12
|
# @api private
|
|
11
13
|
DEFAULT_RETRY_INTERVAL = 1
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
def initialize(processor_name:)
|
|
14
16
|
@processor_name = processor_name
|
|
15
17
|
@retry_interval = DEFAULT_RETRY_INTERVAL
|
|
@@ -18,8 +20,8 @@ module EventSourcery
|
|
|
18
20
|
# Will yield the block and attempt to retry after a defined retry interval {DEFAULT_RETRY_INTERVAL}.
|
|
19
21
|
def with_error_handling
|
|
20
22
|
yield
|
|
21
|
-
rescue =>
|
|
22
|
-
report_error(
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
report_error(e)
|
|
23
25
|
sleep(@retry_interval)
|
|
24
26
|
|
|
25
27
|
retry
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventProcessing
|
|
3
5
|
module ErrorHandlers
|
|
@@ -23,11 +25,13 @@ module EventSourcery
|
|
|
23
25
|
# Will yield the block and attempt to retry in an exponential backoff.
|
|
24
26
|
def with_error_handling
|
|
25
27
|
yield
|
|
26
|
-
rescue =>
|
|
27
|
-
report_error(
|
|
28
|
-
update_retry_interval(
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
report_error(e)
|
|
30
|
+
update_retry_interval(e)
|
|
29
31
|
sleep(@retry_interval)
|
|
30
|
-
EventSourcery.logger.info
|
|
32
|
+
EventSourcery.logger.info do
|
|
33
|
+
"Retrying #{@processor_name} with error: #{e.message} at interval=#{@retry_interval}"
|
|
34
|
+
end
|
|
31
35
|
retry
|
|
32
36
|
end
|
|
33
37
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventProcessing
|
|
3
5
|
module ErrorHandlers
|
|
@@ -10,8 +12,8 @@ module EventSourcery
|
|
|
10
12
|
# Will yield the block and exit the process if an error is raised.
|
|
11
13
|
def with_error_handling
|
|
12
14
|
yield
|
|
13
|
-
rescue =>
|
|
14
|
-
report_error(
|
|
15
|
+
rescue StandardError => e
|
|
16
|
+
report_error(e)
|
|
15
17
|
Process.exit(false)
|
|
16
18
|
end
|
|
17
19
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventProcessing
|
|
3
5
|
# NOTE: databases should be disconnected before running this
|
|
@@ -30,9 +32,7 @@ module EventSourcery
|
|
|
30
32
|
record_terminated_processes
|
|
31
33
|
end
|
|
32
34
|
terminate_remaining_processes
|
|
33
|
-
until all_processes_terminated? || waited_long_enough?
|
|
34
|
-
record_terminated_processes
|
|
35
|
-
end
|
|
35
|
+
record_terminated_processes until all_processes_terminated? || waited_long_enough?
|
|
36
36
|
kill_remaining_processes
|
|
37
37
|
record_terminated_processes until all_processes_terminated?
|
|
38
38
|
end
|
|
@@ -43,7 +43,7 @@ module EventSourcery
|
|
|
43
43
|
process = ESPProcess.new(
|
|
44
44
|
event_processor: event_processor,
|
|
45
45
|
event_source: @event_source,
|
|
46
|
-
after_fork: @after_fork
|
|
46
|
+
after_fork: @after_fork
|
|
47
47
|
)
|
|
48
48
|
pid = Process.fork { process.start }
|
|
49
49
|
@pids[pid] = event_processor
|
|
@@ -72,7 +72,7 @@ module EventSourcery
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def listen_for_shutdown_signals
|
|
75
|
-
%i
|
|
75
|
+
%i[TERM INT].each do |signal|
|
|
76
76
|
Signal.trap(signal) { shutdown }
|
|
77
77
|
end
|
|
78
78
|
end
|
|
@@ -81,6 +81,7 @@ module EventSourcery
|
|
|
81
81
|
loop do
|
|
82
82
|
yield
|
|
83
83
|
break if shutdown_requested?
|
|
84
|
+
|
|
84
85
|
sleep(1)
|
|
85
86
|
end
|
|
86
87
|
end
|
|
@@ -109,6 +110,7 @@ module EventSourcery
|
|
|
109
110
|
logger.info("ESPRunner: Process #{event_processor&.processor_name || pid} " \
|
|
110
111
|
"terminated with exit status: #{status.exitstatus.inspect}")
|
|
111
112
|
next unless event_processor
|
|
113
|
+
|
|
112
114
|
@exit_status &&= !!status.success?
|
|
113
115
|
@after_subprocess_termination&.call(processor: event_processor, runner: self, exit_status: status.exitstatus)
|
|
114
116
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventProcessing
|
|
3
5
|
module EventStreamProcessor
|
|
@@ -31,13 +33,12 @@ module EventSourcery
|
|
|
31
33
|
instance_exec(event, &handler)
|
|
32
34
|
end
|
|
33
35
|
@_event = nil
|
|
34
|
-
rescue
|
|
36
|
+
rescue StandardError
|
|
35
37
|
raise EventProcessingError.new(event: event, processor: self)
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
module ClassMethods
|
|
40
|
-
|
|
41
42
|
# @attr_reader processes_event_types [Array] Process Event Types
|
|
42
43
|
# @attr_reader event_handlers [Hash] Hash of handler blocks keyed by event
|
|
43
44
|
# @attr_reader all_event_handler [Proc] An event handler
|
|
@@ -49,8 +50,7 @@ module EventSourcery
|
|
|
49
50
|
#
|
|
50
51
|
# @return [True, False]
|
|
51
52
|
def processes?(event_type)
|
|
52
|
-
processes_event_types
|
|
53
|
-
processes_event_types.include?(event_type.to_s)
|
|
53
|
+
processes_event_types&.include?(event_type.to_s)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Set the name of the processor.
|
|
@@ -75,9 +75,9 @@ module EventSourcery
|
|
|
75
75
|
if event_classes.empty?
|
|
76
76
|
if @all_event_handler
|
|
77
77
|
raise MultipleCatchAllHandlersDefined, 'Attemping to define multiple catch all event handlers.'
|
|
78
|
-
else
|
|
79
|
-
@all_event_handler = block
|
|
80
78
|
end
|
|
79
|
+
|
|
80
|
+
@all_event_handler = block
|
|
81
81
|
else
|
|
82
82
|
@processes_event_types ||= []
|
|
83
83
|
event_classes.each do |event_class|
|
|
@@ -127,8 +127,8 @@ module EventSourcery
|
|
|
127
127
|
def subscribe_to(event_source, subscription_master: EventStore::SignalHandlingSubscriptionMaster.new)
|
|
128
128
|
setup
|
|
129
129
|
event_source.subscribe(from_id: last_processed_event_id + 1,
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
event_types: processes_event_types,
|
|
131
|
+
subscription_master: subscription_master) do |events|
|
|
132
132
|
process_events(events, subscription_master)
|
|
133
133
|
end
|
|
134
134
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventStore
|
|
3
5
|
module EachByRange
|
|
@@ -5,7 +7,7 @@ module EventSourcery
|
|
|
5
7
|
caught_up = false
|
|
6
8
|
no_events_left = false
|
|
7
9
|
event_id = from_event_id
|
|
8
|
-
|
|
10
|
+
loop do
|
|
9
11
|
events = get_next_from(event_id, event_types: event_types)
|
|
10
12
|
no_events_left = true if events.empty?
|
|
11
13
|
events.each do |event|
|
|
@@ -15,10 +17,9 @@ module EventSourcery
|
|
|
15
17
|
break
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
|
-
unless no_events_left
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
end while !caught_up && !no_events_left
|
|
20
|
+
event_id = events.last.id + 1 unless no_events_left
|
|
21
|
+
break if caught_up || no_events_left
|
|
22
|
+
end
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventStore
|
|
3
5
|
module EventTypeSerializers
|
|
4
6
|
# To support legacy implementations. Type is provided when initializing
|
|
5
7
|
# the event, not derived from the class constant
|
|
6
8
|
class Legacy
|
|
7
|
-
def serialize(
|
|
9
|
+
def serialize(_event_class)
|
|
8
10
|
nil
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def deserialize(
|
|
13
|
+
def deserialize(_event_type)
|
|
12
14
|
Event
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventStore
|
|
3
5
|
module EventTypeSerializers
|
|
@@ -13,32 +15,34 @@ module EventSourcery
|
|
|
13
15
|
def underscore(camel_cased_word)
|
|
14
16
|
word = camel_cased_word.to_s.dup
|
|
15
17
|
word.gsub!(/::/, '/')
|
|
16
|
-
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
|
17
|
-
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
|
18
|
-
word.tr!(
|
|
18
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
|
19
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
20
|
+
word.tr!('-', '_')
|
|
19
21
|
word.downcase!
|
|
20
22
|
word
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def camelize(term, uppercase_first_letter = true)
|
|
24
26
|
string = term.to_s
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
string =
|
|
28
|
+
if uppercase_first_letter
|
|
29
|
+
string.sub(/^[a-z\d]*/) { capitalize(::Regexp.last_match(0)) }
|
|
30
|
+
else
|
|
31
|
+
string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { ::Regexp.last_match(0).downcase }
|
|
32
|
+
end
|
|
33
|
+
string.gsub(%r{(?:_|(/))([a-z\d]*)}i) do
|
|
34
|
+
"#{::Regexp.last_match(1)}#{capitalize(::Regexp.last_match(2))}"
|
|
35
|
+
end.gsub('/', '::')
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
private
|
|
34
39
|
|
|
35
40
|
def capitalize(lower_case_and_underscored_word)
|
|
36
41
|
result = lower_case_and_underscored_word.to_s.dup
|
|
37
|
-
result.gsub!(/_id$/,
|
|
42
|
+
result.gsub!(/_id$/, '')
|
|
38
43
|
result.gsub!(/_/, ' ')
|
|
39
|
-
result.gsub(/([a-z\d]*)/i)
|
|
40
|
-
|
|
41
|
-
}.gsub(/^\w/) { $&.upcase }
|
|
44
|
+
result.gsub!(/([a-z\d]*)/i, &:downcase)
|
|
45
|
+
result.gsub(/^\w/) { ::Regexp.last_match(0).upcase }
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventStore
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
+
# This class provides a basic poll waiter implementation that calls the
|
|
6
|
+
# provided block and sleeps for the specified interval, to be used by a
|
|
7
|
+
# {Subscription}.
|
|
5
8
|
class PollWaiter
|
|
6
9
|
#
|
|
7
10
|
# @param interval [Float] Optional. Will default to `0.5`
|
|
@@ -9,7 +12,8 @@ module EventSourcery
|
|
|
9
12
|
@interval = interval
|
|
10
13
|
end
|
|
11
14
|
|
|
12
|
-
# Start polling. Call the provided block and sleep. Repeat until `:stop`
|
|
15
|
+
# Start polling. Call the provided block and sleep. Repeat until `:stop`
|
|
16
|
+
# is thrown (usually via a subscription master).
|
|
13
17
|
#
|
|
14
18
|
# @param block [Proc] code block to be called when polling
|
|
15
19
|
#
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventStore
|
|
3
5
|
# Manages shutdown signals and facilitate graceful shutdowns of subscriptions.
|
|
@@ -20,7 +22,7 @@ module EventSourcery
|
|
|
20
22
|
private
|
|
21
23
|
|
|
22
24
|
def setup_graceful_shutdown
|
|
23
|
-
%i
|
|
25
|
+
%i[TERM INT].each do |signal|
|
|
24
26
|
Signal.trap(signal) do
|
|
25
27
|
@shutdown_requested = true
|
|
26
28
|
wakeup_main_thread
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module EventStore
|
|
3
|
-
|
|
4
5
|
# This allows Event Stream Processors (ESPs) to subscribe to an event store, and be notified when new events are
|
|
5
6
|
# added.
|
|
6
7
|
class Subscription
|
|
@@ -10,14 +11,16 @@ module EventSourcery
|
|
|
10
11
|
# @param from_event_id [Integer] Start reading events from this event ID
|
|
11
12
|
# @param event_types [Array] Optional. If specified, only subscribe to given event types.
|
|
12
13
|
# @param on_new_events [Proc] Code block to be executed when new events are received
|
|
13
|
-
# @param subscription_master A subscription master instance (such as
|
|
14
|
+
# @param subscription_master A subscription master instance (such as
|
|
15
|
+
# {EventStore::SignalHandlingSubscriptionMaster}) which orchestrates a
|
|
16
|
+
# graceful shutdown of the subscription, if one is requested.
|
|
14
17
|
# @param events_table_name [Symbol] Optional. Defaults to `:events`
|
|
15
18
|
def initialize(event_store:,
|
|
16
19
|
poll_waiter:,
|
|
17
20
|
from_event_id:,
|
|
18
|
-
event_types: nil,
|
|
19
21
|
on_new_events:,
|
|
20
22
|
subscription_master:,
|
|
23
|
+
event_types: nil,
|
|
21
24
|
events_table_name: :events,
|
|
22
25
|
batch_size: EventSourcery.config.subscription_batch_size)
|
|
23
26
|
@event_store = event_store
|
|
@@ -51,6 +54,7 @@ module EventSourcery
|
|
|
51
54
|
@subscription_master.shutdown_if_requested
|
|
52
55
|
events = @event_store.get_next_from(@current_event_id + 1, event_types: @event_types, limit: batch_size)
|
|
53
56
|
break if events.empty?
|
|
57
|
+
|
|
54
58
|
EventSourcery.logger.debug { "New events in subscription: #{events.inspect}" }
|
|
55
59
|
@on_new_events.call(events)
|
|
56
60
|
@current_event_id = events.last.id
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Memory
|
|
3
5
|
class Config
|
|
@@ -19,7 +21,6 @@ module EventSourcery
|
|
|
19
21
|
def event_sink
|
|
20
22
|
@event_sink ||= ::EventSourcery::EventStore::EventSink.new(event_store)
|
|
21
23
|
end
|
|
22
|
-
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def self.configure
|
|
@@ -29,6 +30,5 @@ module EventSourcery
|
|
|
29
30
|
def self.config
|
|
30
31
|
@config ||= Config.new
|
|
31
32
|
end
|
|
32
|
-
|
|
33
33
|
end
|
|
34
34
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Memory
|
|
3
5
|
# In-memory event store.
|
|
@@ -18,16 +20,17 @@ module EventSourcery
|
|
|
18
20
|
# Store given events to the in-memory store
|
|
19
21
|
#
|
|
20
22
|
# @param event_or_events Event(s) to be stored
|
|
21
|
-
# @param expected_version [Optional] Expected version for the aggregate.
|
|
23
|
+
# @param expected_version [Optional] Expected version for the aggregate.
|
|
24
|
+
# This is the version the caller of this method expect the aggregate to
|
|
25
|
+
# be in. If it's different from the expected version a
|
|
26
|
+
# {EventSourcery::ConcurrencyError} will be raised. Defaults to nil.
|
|
22
27
|
# @raise EventSourcery::ConcurrencyError
|
|
23
28
|
# @return Boolean
|
|
24
29
|
def sink(event_or_events, expected_version: nil)
|
|
25
30
|
events = Array(event_or_events)
|
|
26
31
|
ensure_one_aggregate(events)
|
|
27
32
|
|
|
28
|
-
if expected_version && version_for(events.first.aggregate_id) != expected_version
|
|
29
|
-
raise ConcurrencyError
|
|
30
|
-
end
|
|
33
|
+
raise ConcurrencyError if expected_version && version_for(events.first.aggregate_id) != expected_version
|
|
31
34
|
|
|
32
35
|
events.each do |event|
|
|
33
36
|
@events << @event_builder.build(
|
|
@@ -39,7 +42,7 @@ module EventSourcery
|
|
|
39
42
|
created_at: event.created_at || Time.now.utc,
|
|
40
43
|
uuid: event.uuid,
|
|
41
44
|
correlation_id: event.correlation_id,
|
|
42
|
-
causation_id: event.causation_id
|
|
45
|
+
causation_id: event.causation_id
|
|
43
46
|
)
|
|
44
47
|
end
|
|
45
48
|
|
|
@@ -55,11 +58,12 @@ module EventSourcery
|
|
|
55
58
|
# @param limit [Integer] Optional. Number of events to retrieve (starting from the given event ID).
|
|
56
59
|
# @return Array
|
|
57
60
|
def get_next_from(id, event_types: nil, limit: 1000)
|
|
58
|
-
events =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
events =
|
|
62
|
+
if event_types.nil?
|
|
63
|
+
@events
|
|
64
|
+
else
|
|
65
|
+
@events.select { |e| event_types.include?(e.type) }
|
|
66
|
+
end
|
|
63
67
|
|
|
64
68
|
events.select { |event| event.id >= id }.first(limit)
|
|
65
69
|
end
|
|
@@ -69,11 +73,12 @@ module EventSourcery
|
|
|
69
73
|
# @param event_types [Array] Optional. If supplied, only retrieve events of given type(s).
|
|
70
74
|
# @return Integer
|
|
71
75
|
def latest_event_id(event_types: nil)
|
|
72
|
-
events =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
events =
|
|
77
|
+
if event_types.nil?
|
|
78
|
+
@events
|
|
79
|
+
else
|
|
80
|
+
@events.select { |e| event_types.include?(e.type) }
|
|
81
|
+
end
|
|
77
82
|
|
|
78
83
|
events.empty? ? 0 : events.last.id
|
|
79
84
|
end
|
|
@@ -108,9 +113,9 @@ module EventSourcery
|
|
|
108
113
|
# @param events [Array] Collection of events
|
|
109
114
|
# @raise AtomicWriteToMultipleAggregatesNotSupported
|
|
110
115
|
def ensure_one_aggregate(events)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
return if events.map(&:aggregate_id).uniq.one?
|
|
117
|
+
|
|
118
|
+
raise AtomicWriteToMultipleAggregatesNotSupported
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
# Adds a listener or listeners to the memory store.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Memory
|
|
3
5
|
# Being able to know where you're at when reading an event stream
|
|
@@ -26,7 +28,7 @@ module EventSourcery
|
|
|
26
28
|
@state[processor_name.to_s] = event_id
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
alias
|
|
31
|
+
alias reset_last_processed_event_id setup
|
|
30
32
|
|
|
31
33
|
# Find the last processed event id for a given processor name.
|
|
32
34
|
#
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
TestEvent2 = Class.new(EventSourcery::Event)
|
|
4
|
+
UserSignedUp = Class.new(EventSourcery::Event)
|
|
5
|
+
ItemRejected = Class.new(EventSourcery::Event)
|
|
6
|
+
Type1 = Class.new(EventSourcery::Event)
|
|
7
|
+
Type2 = Class.new(EventSourcery::Event)
|
|
8
|
+
BillingDetailsProvided = Class.new(EventSourcery::Event)
|
|
8
9
|
|
|
10
|
+
RSpec.shared_examples 'an event store' do
|
|
9
11
|
let(:aggregate_id) { SecureRandom.uuid }
|
|
10
12
|
|
|
11
13
|
describe '#sink' do
|
|
@@ -51,22 +53,22 @@ RSpec.shared_examples 'an event store' do
|
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
it 'writes multiple events' do
|
|
54
|
-
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: {e: 1}),
|
|
55
|
-
ItemAdded.new(aggregate_id: aggregate_id, body: {e: 2}),
|
|
56
|
-
ItemAdded.new(aggregate_id: aggregate_id, body: {e: 3})])
|
|
56
|
+
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: { e: 1 }),
|
|
57
|
+
ItemAdded.new(aggregate_id: aggregate_id, body: { e: 2 }),
|
|
58
|
+
ItemAdded.new(aggregate_id: aggregate_id, body: { e: 3 })])
|
|
57
59
|
events = event_store.get_next_from(1)
|
|
58
60
|
expect(events.count).to eq 3
|
|
59
61
|
expect(events.map(&:id)).to eq [1, 2, 3]
|
|
60
|
-
expect(events.map(&:body)).to eq [{'e' => 1}, {'e' => 2}, {'e' => 3}]
|
|
62
|
+
expect(events.map(&:body)).to eq [{ 'e' => 1 }, { 'e' => 2 }, { 'e' => 3 }]
|
|
61
63
|
expect(events.map(&:version)).to eq [1, 2, 3]
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
it 'sets the correct aggregates version' do
|
|
65
|
-
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: {e: 1}),
|
|
66
|
-
ItemAdded.new(aggregate_id: aggregate_id, body: {e: 2})])
|
|
67
|
+
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: { e: 1 }),
|
|
68
|
+
ItemAdded.new(aggregate_id: aggregate_id, body: { e: 2 })])
|
|
67
69
|
# this will throw a unique constrain error if the aggregate version was not set correctly ^
|
|
68
|
-
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: {e: 1}),
|
|
69
|
-
ItemAdded.new(aggregate_id: aggregate_id, body: {e: 2})])
|
|
70
|
+
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, body: { e: 1 }),
|
|
71
|
+
ItemAdded.new(aggregate_id: aggregate_id, body: { e: 2 })])
|
|
70
72
|
events = event_store.get_next_from(1)
|
|
71
73
|
expect(events.count).to eq 4
|
|
72
74
|
expect(events.map(&:id)).to eq [1, 2, 3, 4]
|
|
@@ -104,16 +106,17 @@ RSpec.shared_examples 'an event store' do
|
|
|
104
106
|
|
|
105
107
|
it 'correctly inserts created at times when inserting multiple events atomically' do
|
|
106
108
|
time = Time.parse('2016-10-14T00:00:00.646191Z')
|
|
107
|
-
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, created_at: nil),
|
|
109
|
+
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id, created_at: nil),
|
|
110
|
+
ItemAdded.new(aggregate_id: aggregate_id, created_at: time)])
|
|
108
111
|
created_ats = event_store.get_next_from(0).map(&:created_at)
|
|
109
112
|
expect(created_ats.map(&:class)).to eq [Time, Time]
|
|
110
113
|
expect(created_ats.last).to eq time
|
|
111
114
|
end
|
|
112
115
|
|
|
113
116
|
it 'raises an error if the events given are for more than one aggregate' do
|
|
114
|
-
expect
|
|
117
|
+
expect do
|
|
115
118
|
event_store.sink([ItemAdded.new(aggregate_id: aggregate_id), ItemAdded.new(aggregate_id: SecureRandom.uuid)])
|
|
116
|
-
|
|
119
|
+
end.to raise_error(EventSourcery::AtomicWriteToMultipleAggregatesNotSupported)
|
|
117
120
|
end
|
|
118
121
|
end
|
|
119
122
|
|
|
@@ -168,7 +171,7 @@ RSpec.shared_examples 'an event store' do
|
|
|
168
171
|
|
|
169
172
|
expect(event_store.latest_event_id(event_types: ['type1'])).to eq 2
|
|
170
173
|
expect(event_store.latest_event_id(event_types: ['type2'])).to eq 3
|
|
171
|
-
expect(event_store.latest_event_id(event_types: [
|
|
174
|
+
expect(event_store.latest_event_id(event_types: %w[type1 type2])).to eq 3
|
|
172
175
|
end
|
|
173
176
|
end
|
|
174
177
|
end
|
|
@@ -207,7 +210,7 @@ RSpec.shared_examples 'an event store' do
|
|
|
207
210
|
|
|
208
211
|
describe '#each_by_range' do
|
|
209
212
|
before do
|
|
210
|
-
|
|
213
|
+
21.times do
|
|
211
214
|
event_store.sink(ItemAdded.new(aggregate_id: aggregate_id, body: {}))
|
|
212
215
|
end
|
|
213
216
|
end
|
|
@@ -255,7 +258,7 @@ RSpec.shared_examples 'an event store' do
|
|
|
255
258
|
def save_event(expected_version: nil)
|
|
256
259
|
event_store.sink(
|
|
257
260
|
BillingDetailsProvided.new(aggregate_id: aggregate_id, body: { my_event: 'data' }),
|
|
258
|
-
expected_version: expected_version
|
|
261
|
+
expected_version: expected_version
|
|
259
262
|
)
|
|
260
263
|
end
|
|
261
264
|
|
|
@@ -278,9 +281,8 @@ RSpec.shared_examples 'an event store' do
|
|
|
278
281
|
|
|
279
282
|
context 'and the expected version is incorrect - 1' do
|
|
280
283
|
it 'raises a ConcurrencyError' do
|
|
281
|
-
expect {
|
|
282
|
-
|
|
283
|
-
}.to raise_error(EventSourcery::ConcurrencyError)
|
|
284
|
+
expect { save_event(expected_version: 1) }
|
|
285
|
+
.to raise_error(EventSourcery::ConcurrencyError)
|
|
284
286
|
end
|
|
285
287
|
end
|
|
286
288
|
|
|
@@ -299,9 +301,8 @@ RSpec.shared_examples 'an event store' do
|
|
|
299
301
|
|
|
300
302
|
context 'with an incorrect expected version - 0' do
|
|
301
303
|
it 'raises a ConcurrencyError' do
|
|
302
|
-
expect {
|
|
303
|
-
|
|
304
|
-
}.to raise_error(EventSourcery::ConcurrencyError)
|
|
304
|
+
expect { save_event(expected_version: 0) }
|
|
305
|
+
.to raise_error(EventSourcery::ConcurrencyError)
|
|
305
306
|
end
|
|
306
307
|
end
|
|
307
308
|
|
data/lib/event_sourcery.rb
CHANGED
metadata
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: event_sourcery
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Envato
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: logger
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
18
|
version: '0'
|
|
20
|
-
type: :
|
|
19
|
+
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
@@ -25,21 +24,21 @@ dependencies:
|
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: benchmark-ips
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
|
-
- - "
|
|
30
|
+
- - ">="
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
32
|
+
version: '0'
|
|
34
33
|
type: :development
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
|
-
- - "
|
|
37
|
+
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
39
|
+
version: '0'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: bundler
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
@@ -67,7 +66,21 @@ dependencies:
|
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: '0'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec
|
|
71
84
|
requirement: !ruby/object:Gem::Requirement
|
|
72
85
|
requirements:
|
|
73
86
|
- - ">="
|
|
@@ -80,6 +93,20 @@ dependencies:
|
|
|
80
93
|
- - ">="
|
|
81
94
|
- !ruby/object:Gem::Version
|
|
82
95
|
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1'
|
|
83
110
|
description: ''
|
|
84
111
|
email:
|
|
85
112
|
- rubygems@envato.com
|
|
@@ -124,12 +151,14 @@ files:
|
|
|
124
151
|
- lib/event_sourcery/rspec/event_store_shared_examples.rb
|
|
125
152
|
- lib/event_sourcery/version.rb
|
|
126
153
|
homepage: https://github.com/envato/event_sourcery
|
|
127
|
-
licenses:
|
|
154
|
+
licenses:
|
|
155
|
+
- MIT
|
|
128
156
|
metadata:
|
|
157
|
+
allowed_push_host: https://rubygems.org
|
|
129
158
|
bug_tracker_uri: https://github.com/envato/event_sourcery/issues
|
|
130
159
|
changelog_uri: https://github.com/envato/event_sourcery/blob/HEAD/CHANGELOG.md
|
|
131
|
-
|
|
132
|
-
|
|
160
|
+
documentation_uri: https://www.rubydoc.info/gems/event_sourcery/1.0.3
|
|
161
|
+
source_code_uri: https://github.com/envato/event_sourcery/tree/v1.0.3
|
|
133
162
|
rdoc_options: []
|
|
134
163
|
require_paths:
|
|
135
164
|
- lib
|
|
@@ -137,15 +166,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
137
166
|
requirements:
|
|
138
167
|
- - ">="
|
|
139
168
|
- !ruby/object:Gem::Version
|
|
140
|
-
version: 2.
|
|
169
|
+
version: 2.6.0
|
|
141
170
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
171
|
requirements:
|
|
143
172
|
- - ">="
|
|
144
173
|
- !ruby/object:Gem::Version
|
|
145
174
|
version: '0'
|
|
146
175
|
requirements: []
|
|
147
|
-
rubygems_version:
|
|
148
|
-
signing_key:
|
|
176
|
+
rubygems_version: 4.0.3
|
|
149
177
|
specification_version: 4
|
|
150
178
|
summary: Event Sourcing Library
|
|
151
179
|
test_files: []
|