sequent 3.4.0 → 3.5.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/lib/sequent/configuration.rb +4 -0
- data/lib/sequent/core/aggregate_repository.rb +5 -0
- data/lib/sequent/core/command.rb +13 -2
- data/lib/sequent/core/command_service.rb +26 -12
- data/lib/sequent/core/event_publisher.rb +4 -0
- data/lib/sequent/core/event_store.rb +10 -2
- data/lib/sequent/core/helpers/attribute_support.rb +19 -5
- data/lib/sequent/core/helpers/mergable.rb +1 -0
- data/lib/sequent/sequent.rb +4 -0
- data/lib/sequent/util/dry_run.rb +191 -0
- data/lib/sequent/util/util.rb +1 -0
- data/lib/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11621c1f62780395fc4954a6a5180aa027eb8a26a88d42da6419b20f9e312122
|
4
|
+
data.tar.gz: 855d52912a637416cfdaf66fad0bdf15d9ea4f4474e8896e7441dc8d56afa98b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c45c07e3e9ac5a4d52bb82dfb7bd76c7096115eb97f0f87477ee99b95eec513a68e67f422a2fb76d23626596c206a5c881f14afce281129e071b3c383ba36979
|
7
|
+
data.tar.gz: de31f973652d7bcc75f8dc6964039d139b464c59e2bd0ace6c4dbf000b017d708dd667fcf6cce90c925c7582361f0dd6a9d53f9abc18ed46c8ccae2c1461fcde
|
@@ -28,6 +28,8 @@ module Sequent
|
|
28
28
|
|
29
29
|
DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS = false
|
30
30
|
|
31
|
+
DEFAULT_ERROR_LOCALE_RESOLVER = -> { I18n.locale || :en }
|
32
|
+
|
31
33
|
attr_accessor :aggregate_repository
|
32
34
|
|
33
35
|
attr_accessor :event_store,
|
@@ -51,6 +53,7 @@ module Sequent
|
|
51
53
|
|
52
54
|
attr_accessor :logger
|
53
55
|
|
56
|
+
attr_accessor :error_locale_resolver
|
54
57
|
|
55
58
|
attr_accessor :migration_sql_files_directory,
|
56
59
|
:view_schema_name,
|
@@ -109,6 +112,7 @@ module Sequent
|
|
109
112
|
self.strict_check_attributes_on_apply_events = DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS
|
110
113
|
|
111
114
|
self.logger = Logger.new(STDOUT).tap {|l| l.level = Logger::INFO }
|
115
|
+
self.error_locale_resolver = DEFAULT_ERROR_LOCALE_RESOLVER
|
112
116
|
end
|
113
117
|
|
114
118
|
def replayed_ids_table_name=(table_name)
|
@@ -105,6 +105,11 @@ module Sequent
|
|
105
105
|
|
106
106
|
# Gets all uncommitted_events from the 'registered' aggregates
|
107
107
|
# and stores them in the event store.
|
108
|
+
#
|
109
|
+
# The events given to the EventStore are ordered in loading order
|
110
|
+
# of the different AggregateRoot's. So Events are stored
|
111
|
+
# (and therefore published) in order in which they are `apply`-ed per AggregateRoot.
|
112
|
+
#
|
108
113
|
# The command is 'attached' for traceability purpose so we can see
|
109
114
|
# which command resulted in which events.
|
110
115
|
#
|
data/lib/sequent/core/command.rb
CHANGED
@@ -7,7 +7,15 @@ require_relative 'helpers/mergable'
|
|
7
7
|
|
8
8
|
module Sequent
|
9
9
|
module Core
|
10
|
-
#
|
10
|
+
#
|
11
|
+
# Base class for all Command's.
|
12
|
+
#
|
13
|
+
# Commands form the API of your domain. They are
|
14
|
+
# simple data objects with descriptive names
|
15
|
+
# of what they want to achieve. E.g. `SendInvoice`.
|
16
|
+
#
|
17
|
+
# BaseCommand uses `ActiveModel::Validations` for
|
18
|
+
# validations
|
11
19
|
class BaseCommand
|
12
20
|
include ActiveModel::Validations,
|
13
21
|
Sequent::Core::Helpers::Copyable,
|
@@ -40,6 +48,9 @@ module Sequent
|
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
51
|
+
#
|
52
|
+
# Utility class containing all subclasses of BaseCommand
|
53
|
+
#
|
43
54
|
class Commands
|
44
55
|
class << self
|
45
56
|
def commands
|
@@ -60,7 +71,7 @@ module Sequent
|
|
60
71
|
end
|
61
72
|
end
|
62
73
|
|
63
|
-
# Most commonly used
|
74
|
+
# Most commonly used Command
|
64
75
|
# Command can be instantiated just by using:
|
65
76
|
#
|
66
77
|
# Command.new(aggregate_id: "1", user_id: "joe")
|
@@ -4,23 +4,29 @@ require_relative 'current_event'
|
|
4
4
|
module Sequent
|
5
5
|
module Core
|
6
6
|
#
|
7
|
-
# Single point in the application
|
8
|
-
#
|
7
|
+
# Single point in the application to get something done in Sequent.
|
8
|
+
# The CommandService handles all subclasses Sequent::Core::BaseCommand. Most common
|
9
|
+
# use is to subclass `Sequent::Command`.
|
9
10
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
11
|
+
# The CommandService is available via the shortcut method `Sequent.command_service`
|
12
|
+
#
|
13
|
+
# To use the CommandService please use:
|
14
|
+
#
|
15
|
+
# Sequent.command_service.execute_commands(...)
|
15
16
|
#
|
16
17
|
class CommandService
|
18
|
+
#
|
17
19
|
# Executes the given commands in a single transactional block as implemented by the +transaction_provider+
|
18
20
|
#
|
19
|
-
# For each
|
21
|
+
# For each Command:
|
22
|
+
#
|
23
|
+
# * Validate command
|
24
|
+
# * Call Sequent::CommandHandler's listening to the given Command
|
25
|
+
# * Store and publish Events
|
26
|
+
# * Any new Command's (from e.g. workflows) are queued for processing in the same transaction
|
27
|
+
#
|
28
|
+
# At the end the transaction is committed and the AggregateRepository's Unit of Work is cleared.
|
20
29
|
#
|
21
|
-
# * All filters are executed. Any exception raised will rollback the transaction and propagate up
|
22
|
-
# * If the command is valid all +command_handlers+ that +handles_message?+ is invoked
|
23
|
-
# * The +repository+ commits the command and all uncommitted_events resulting from the command
|
24
30
|
def execute_commands(*commands)
|
25
31
|
commands.each do |command|
|
26
32
|
if command.respond_to?(:event_aggregate_id) && CurrentEvent.current
|
@@ -33,6 +39,7 @@ module Sequent
|
|
33
39
|
end
|
34
40
|
|
35
41
|
def remove_event_handler(clazz)
|
42
|
+
warn "[DEPRECATION] `remove_event_handler` is deprecated"
|
36
43
|
event_store.remove_event_handler(clazz)
|
37
44
|
end
|
38
45
|
|
@@ -54,9 +61,16 @@ module Sequent
|
|
54
61
|
end
|
55
62
|
|
56
63
|
def process_command(command)
|
64
|
+
fail ArgumentError, 'command is required' if command.nil?
|
65
|
+
|
66
|
+
Sequent.logger.debug("[CommandService] Processing command #{command.class}")
|
67
|
+
|
57
68
|
filters.each { |filter| filter.execute(command) }
|
58
69
|
|
59
|
-
|
70
|
+
I18n.with_locale(Sequent.configuration.error_locale_resolver.call) do
|
71
|
+
raise CommandNotValid.new(command) unless command.valid?
|
72
|
+
end
|
73
|
+
|
60
74
|
parsed_command = command.parse_attrs_to_correct_types
|
61
75
|
command_handlers.select { |h| h.class.handles_message?(parsed_command) }.each { |h| h.handle_message parsed_command }
|
62
76
|
repository.commit(parsed_command)
|
@@ -46,6 +46,10 @@ module Sequent
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def process_event(event)
|
49
|
+
fail ArgumentError, 'event is required' if event.nil?
|
50
|
+
|
51
|
+
Sequent.logger.debug("[EventPublisher] Publishing event #{event.class}")
|
52
|
+
|
49
53
|
configuration.event_handlers.each do |handler|
|
50
54
|
begin
|
51
55
|
handler.handle_message event
|
@@ -33,10 +33,18 @@ module Sequent
|
|
33
33
|
# Stores the events in the EventStore and publishes the events
|
34
34
|
# to the registered event_handlers.
|
35
35
|
#
|
36
|
-
#
|
37
|
-
#
|
36
|
+
# The events are published according to the order in
|
37
|
+
# the tail of the given `streams_with_events` array pair.
|
38
|
+
#
|
39
|
+
# @param command The command that caused the Events
|
40
|
+
# @param streams_with_events is an enumerable of pairs from
|
41
|
+
# `StreamRecord` to arrays ordered uncommitted `Event`s.
|
38
42
|
#
|
39
43
|
def commit_events(command, streams_with_events)
|
44
|
+
fail ArgumentError, "command is required" if command.nil?
|
45
|
+
|
46
|
+
Sequent.logger.debug("[EventStore] Committing events for command #{command.class}")
|
47
|
+
|
40
48
|
store_events(command, streams_with_events)
|
41
49
|
publish_events(streams_with_events.flat_map { |_, events| events })
|
42
50
|
end
|
@@ -9,16 +9,30 @@ require_relative 'association_validator'
|
|
9
9
|
module Sequent
|
10
10
|
module Core
|
11
11
|
module Helpers
|
12
|
-
# Provides functionality for defining attributes with their types
|
12
|
+
# Provides functionality for defining attributes with their types.
|
13
13
|
#
|
14
|
-
# Since our Commands and ValueObjects are not backed by a database like e.g.
|
14
|
+
# Since our Commands and ValueObjects are not backed by a database like e.g. Rails
|
15
15
|
# we can not infer their types. We need the types to be able to parse from and to json.
|
16
|
-
# We could have stored te type information in the json, but we didn't.
|
17
|
-
#
|
18
16
|
# You typically do not need to include this module in your classes. If you extend from
|
19
|
-
# Sequent::
|
17
|
+
# Sequent::ValueObject, Sequent::Event or Sequent::Command you will
|
20
18
|
# get this functionality for free.
|
21
19
|
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# attrs name: String, age: Integer, born: Date
|
23
|
+
#
|
24
|
+
# Currently Sequent supports the following types:
|
25
|
+
#
|
26
|
+
# - String
|
27
|
+
# - Integer
|
28
|
+
# - Boolean
|
29
|
+
# - Date
|
30
|
+
# - DateTime
|
31
|
+
# - Subclasses of Sequent::ValueObject
|
32
|
+
# - Lists defined as `array(String)`
|
33
|
+
# - BigDecimal
|
34
|
+
# - Sequent::Secret
|
35
|
+
#
|
22
36
|
module AttributeSupport
|
23
37
|
class UnknownAttributeError < StandardError; end
|
24
38
|
|
@@ -8,6 +8,7 @@ module Sequent
|
|
8
8
|
module Mergable
|
9
9
|
|
10
10
|
def merge!(attrs = {})
|
11
|
+
warn "[DEPRECATION] `merge!` is deprecated. Please use `copy` instead. This method will no longer be included in the next version of Sequent. You can still use it but you will have to include the module `Sequent::Core::Helpers::Mergable` yourself."
|
11
12
|
attrs.each do |name, value|
|
12
13
|
self.send("#{name}=", value)
|
13
14
|
end
|
data/lib/sequent/sequent.rb
CHANGED
@@ -64,6 +64,10 @@ module Sequent
|
|
64
64
|
configuration.aggregate_repository
|
65
65
|
end
|
66
66
|
|
67
|
+
def self.dry_run(*commands)
|
68
|
+
Sequent::Util::DryRun.these_commands(commands)
|
69
|
+
end
|
70
|
+
|
67
71
|
# Shortcut classes for easy usage
|
68
72
|
Event = Sequent::Core::Event
|
69
73
|
Command = Sequent::Core::Command
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require_relative '../test/command_handler_helpers'
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Util
|
5
|
+
##
|
6
|
+
# Dry run provides the ability to inspect what would
|
7
|
+
# happen if the given commands would be executed
|
8
|
+
# without actually committing the results.
|
9
|
+
# You can inspect which commands are executed
|
10
|
+
# and what the resulting events would be
|
11
|
+
# with theSequent::Projector's and Sequent::Workflow's
|
12
|
+
# that would be invoked (without actually invoking them).
|
13
|
+
#
|
14
|
+
# Since the Workflow's are not actually invoked new commands
|
15
|
+
# resulting from this Workflow will of course not be in the result.
|
16
|
+
#
|
17
|
+
# Caution: Since the Sequent Configuration is shared between threads this method
|
18
|
+
# is not Thread safe.
|
19
|
+
#
|
20
|
+
# Example usage:
|
21
|
+
#
|
22
|
+
# result = Sequent.dry_run(create_foo_command, ping_foo_command)
|
23
|
+
#
|
24
|
+
# result.print(STDOUT)
|
25
|
+
#
|
26
|
+
module DryRun
|
27
|
+
EventInvokedHandler = Struct.new(:event, :handler)
|
28
|
+
|
29
|
+
##
|
30
|
+
# Proxies the given EventStore implements commit_events
|
31
|
+
# that instead of publish and store just publishes the events.
|
32
|
+
class EventStoreProxy
|
33
|
+
attr_reader :command_with_events, :event_store
|
34
|
+
|
35
|
+
delegate :load_events_for_aggregates,
|
36
|
+
:load_events,
|
37
|
+
:publish_events,
|
38
|
+
:stream_exists?,
|
39
|
+
to: :event_store
|
40
|
+
|
41
|
+
def initialize(result)
|
42
|
+
@event_store = Sequent::Test::CommandHandlerHelpers::FakeEventStore.new
|
43
|
+
@command_with_events = {}
|
44
|
+
@result = result
|
45
|
+
end
|
46
|
+
|
47
|
+
def commit_events(command, streams_with_events)
|
48
|
+
event_store.commit_events(command, streams_with_events)
|
49
|
+
|
50
|
+
new_events = streams_with_events.flat_map { |_, events| events }
|
51
|
+
@result.published_command_with_events(command, new_events)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Records which Projector's and Workflow's are executed
|
57
|
+
#
|
58
|
+
class RecordingEventPublisher < Sequent::Core::EventPublisher
|
59
|
+
attr_reader :projectors, :workflows
|
60
|
+
|
61
|
+
def initialize(result)
|
62
|
+
@result = result
|
63
|
+
end
|
64
|
+
|
65
|
+
def process_event(event)
|
66
|
+
Sequent.configuration.event_handlers.each do |handler|
|
67
|
+
next unless handler.class.handles_message?(event)
|
68
|
+
|
69
|
+
if handler.is_a?(Sequent::Workflow)
|
70
|
+
@result.invoked_workflow(EventInvokedHandler.new(event, handler.class))
|
71
|
+
elsif handler.is_a?(Sequent::Projector)
|
72
|
+
@result.invoked_projector(EventInvokedHandler.new(event, handler.class))
|
73
|
+
else
|
74
|
+
fail "Unrecognized event_handler #{handler.class} called for event #{event.class}"
|
75
|
+
end
|
76
|
+
rescue
|
77
|
+
raise PublishEventError.new(handler.class, event)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Contains the result of a dry run.
|
84
|
+
#
|
85
|
+
# @see #tree
|
86
|
+
# @see #print
|
87
|
+
#
|
88
|
+
class Result
|
89
|
+
EventCalledHandlers = Struct.new(:event, :projectors, :workflows)
|
90
|
+
|
91
|
+
def initialize
|
92
|
+
@command_with_events = {}
|
93
|
+
@event_invoked_projectors = []
|
94
|
+
@event_invoked_workflows = []
|
95
|
+
end
|
96
|
+
|
97
|
+
def invoked_projector(event_invoked_handler)
|
98
|
+
event_invoked_projectors << event_invoked_handler
|
99
|
+
end
|
100
|
+
|
101
|
+
def invoked_workflow(event_invoked_handler)
|
102
|
+
event_invoked_workflows << event_invoked_handler
|
103
|
+
end
|
104
|
+
|
105
|
+
def published_command_with_events(command, events)
|
106
|
+
command_with_events[command] = events
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Returns the command with events as a tree structure.
|
111
|
+
#
|
112
|
+
# {
|
113
|
+
# command => [
|
114
|
+
# EventCalledHandlers,
|
115
|
+
# EventCalledHandlers,
|
116
|
+
# EventCalledHandlers,
|
117
|
+
# ]
|
118
|
+
# }
|
119
|
+
#
|
120
|
+
# The EventCalledHandlers contains an Event with the
|
121
|
+
# lists of `Sequent::Projector`s and `Sequent::Workflow`s
|
122
|
+
# that were called.
|
123
|
+
#
|
124
|
+
def tree
|
125
|
+
command_with_events.reduce({}) do |memo, (command, events)|
|
126
|
+
events_to_handlers = events.map do |event|
|
127
|
+
for_current_event = ->(pair) { pair.event == event }
|
128
|
+
EventCalledHandlers.new(
|
129
|
+
event,
|
130
|
+
event_invoked_projectors.select(&for_current_event).map(&:handler),
|
131
|
+
event_invoked_workflows.select(&for_current_event).map(&:handler),
|
132
|
+
)
|
133
|
+
end
|
134
|
+
memo[command] = events_to_handlers
|
135
|
+
memo
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Prints the output from #tree to the given `io`
|
141
|
+
#
|
142
|
+
def print(io)
|
143
|
+
tree.each_with_index do |(command, event_called_handlerss), index|
|
144
|
+
io.puts "+++++++++++++++++++++++++++++++++++" if index == 0
|
145
|
+
io.puts "Command: #{command.class} resulted in #{event_called_handlerss.length} events"
|
146
|
+
event_called_handlerss.each_with_index do |event_called_handlers, i|
|
147
|
+
io.puts "" if i > 0
|
148
|
+
io.puts "-- Event #{event_called_handlers.event.class} was handled by:"
|
149
|
+
io.puts "-- Projectors: [#{event_called_handlers.projectors.join(', ')}]"
|
150
|
+
io.puts "-- Workflows: [#{event_called_handlers.workflows.join(', ')}]"
|
151
|
+
end
|
152
|
+
|
153
|
+
io.puts "+++++++++++++++++++++++++++++++++++"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
attr_reader :command_with_events, :event_invoked_projectors, :event_invoked_workflows
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Main method of the DryRun.
|
164
|
+
#
|
165
|
+
# Caution: Since the Sequent Configuration is changed and is shared between threads this method
|
166
|
+
# is not Thread safe.
|
167
|
+
#
|
168
|
+
# After invocation the sequent configuration is reset to the state it was before
|
169
|
+
# invoking this method.
|
170
|
+
#
|
171
|
+
# @param commands - the commands to dry run
|
172
|
+
# @return Result - the Result of the dry run. See Result.
|
173
|
+
#
|
174
|
+
def self.these_commands(commands)
|
175
|
+
current_event_store = Sequent.configuration.event_store
|
176
|
+
current_event_publisher = Sequent.configuration.event_publisher
|
177
|
+
result = Result.new
|
178
|
+
|
179
|
+
Sequent.configuration.event_store = EventStoreProxy.new(result)
|
180
|
+
Sequent.configuration.event_publisher = RecordingEventPublisher.new(result)
|
181
|
+
|
182
|
+
Sequent.command_service.execute_commands(*commands)
|
183
|
+
|
184
|
+
result
|
185
|
+
ensure
|
186
|
+
Sequent.configuration.event_store = current_event_store
|
187
|
+
Sequent.configuration.event_publisher = current_event_publisher
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
data/lib/sequent/util/util.rb
CHANGED
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lars Vonk
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2020-02-10 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activerecord
|
@@ -152,6 +152,20 @@ dependencies:
|
|
152
152
|
- - "~>"
|
153
153
|
- !ruby/object:Gem::Version
|
154
154
|
version: 2.6.5
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: i18n
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
type: :runtime
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
155
169
|
- !ruby/object:Gem::Dependency
|
156
170
|
name: rspec
|
157
171
|
requirement: !ruby/object:Gem::Requirement
|
@@ -366,6 +380,7 @@ files:
|
|
366
380
|
- lib/sequent/test/event_handler_helpers.rb
|
367
381
|
- lib/sequent/test/event_stream_helpers.rb
|
368
382
|
- lib/sequent/test/time_comparison.rb
|
383
|
+
- lib/sequent/util/dry_run.rb
|
369
384
|
- lib/sequent/util/printer.rb
|
370
385
|
- lib/sequent/util/skip_if_already_processing.rb
|
371
386
|
- lib/sequent/util/timer.rb
|