sequent 4.0.0 → 4.3.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/bin/sequent +33 -26
- data/lib/notices.rb +2 -0
- data/lib/sequent/application_record.rb +2 -0
- data/lib/sequent/configuration.rb +24 -31
- data/lib/sequent/core/aggregate_repository.rb +48 -13
- data/lib/sequent/core/aggregate_root.rb +36 -7
- data/lib/sequent/core/aggregate_roots.rb +24 -0
- data/lib/sequent/core/aggregate_snapshotter.rb +8 -5
- data/lib/sequent/core/base_command_handler.rb +4 -2
- data/lib/sequent/core/command.rb +17 -9
- data/lib/sequent/core/command_record.rb +8 -3
- data/lib/sequent/core/command_service.rb +18 -18
- data/lib/sequent/core/core.rb +2 -0
- data/lib/sequent/core/current_event.rb +2 -0
- data/lib/sequent/core/event.rb +16 -11
- data/lib/sequent/core/event_publisher.rb +16 -15
- data/lib/sequent/core/event_record.rb +7 -7
- data/lib/sequent/core/event_store.rb +89 -51
- data/lib/sequent/core/ext/ext.rb +9 -1
- data/lib/sequent/core/helpers/array_with_type.rb +4 -1
- data/lib/sequent/core/helpers/association_validator.rb +9 -7
- data/lib/sequent/core/helpers/attribute_support.rb +45 -28
- data/lib/sequent/core/helpers/autoset_attributes.rb +4 -4
- data/lib/sequent/core/helpers/boolean_validator.rb +6 -1
- data/lib/sequent/core/helpers/copyable.rb +2 -2
- data/lib/sequent/core/helpers/date_time_validator.rb +4 -1
- data/lib/sequent/core/helpers/date_validator.rb +6 -1
- data/lib/sequent/core/helpers/default_validators.rb +12 -10
- data/lib/sequent/core/helpers/equal_support.rb +8 -6
- data/lib/sequent/core/helpers/helpers.rb +2 -0
- data/lib/sequent/core/helpers/mergable.rb +6 -5
- data/lib/sequent/core/helpers/message_handler.rb +3 -1
- data/lib/sequent/core/helpers/param_support.rb +19 -15
- data/lib/sequent/core/helpers/secret.rb +14 -12
- data/lib/sequent/core/helpers/string_support.rb +5 -4
- data/lib/sequent/core/helpers/string_to_value_parsers.rb +7 -2
- data/lib/sequent/core/helpers/string_validator.rb +6 -1
- data/lib/sequent/core/helpers/type_conversion_support.rb +5 -3
- data/lib/sequent/core/helpers/uuid_helper.rb +5 -2
- data/lib/sequent/core/helpers/value_validators.rb +23 -9
- data/lib/sequent/core/persistors/active_record_persistor.rb +19 -9
- data/lib/sequent/core/persistors/persistor.rb +16 -14
- data/lib/sequent/core/persistors/persistors.rb +2 -0
- data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +70 -47
- data/lib/sequent/core/projector.rb +53 -22
- data/lib/sequent/core/random_uuid_generator.rb +2 -0
- data/lib/sequent/core/sequent_oj.rb +2 -0
- data/lib/sequent/core/stream_record.rb +9 -3
- data/lib/sequent/core/transactions/active_record_transaction_provider.rb +30 -9
- data/lib/sequent/core/transactions/no_transactions.rb +2 -1
- data/lib/sequent/core/transactions/read_only_active_record_transaction_provider.rb +46 -0
- data/lib/sequent/core/transactions/transactions.rb +3 -0
- data/lib/sequent/core/value_object.rb +8 -10
- data/lib/sequent/core/workflow.rb +35 -5
- data/lib/sequent/generator/aggregate.rb +16 -10
- data/lib/sequent/generator/command.rb +26 -19
- data/lib/sequent/generator/event.rb +19 -17
- data/lib/sequent/generator/generator.rb +2 -0
- data/lib/sequent/generator/project.rb +9 -0
- data/lib/sequent/generator/template_project/Gemfile +1 -1
- data/lib/sequent/generator/template_project/ruby-version +1 -0
- data/lib/sequent/generator.rb +2 -0
- data/lib/sequent/migrations/executor.rb +22 -13
- data/lib/sequent/migrations/functions.rb +5 -6
- data/lib/sequent/migrations/migrate_events.rb +12 -9
- data/lib/sequent/migrations/migrations.rb +2 -1
- data/lib/sequent/migrations/planner.rb +33 -23
- data/lib/sequent/migrations/projectors.rb +4 -3
- data/lib/sequent/migrations/sql.rb +2 -0
- data/lib/sequent/migrations/view_schema.rb +84 -45
- data/lib/sequent/rake/migration_tasks.rb +58 -22
- data/lib/sequent/rake/tasks.rb +5 -2
- data/lib/sequent/sequent.rb +2 -0
- data/lib/sequent/support/database.rb +30 -15
- data/lib/sequent/support/view_projection.rb +6 -3
- data/lib/sequent/support/view_schema.rb +2 -0
- data/lib/sequent/support.rb +2 -0
- data/lib/sequent/test/command_handler_helpers.rb +35 -17
- data/lib/sequent/test/event_handler_helpers.rb +10 -4
- data/lib/sequent/test/event_stream_helpers.rb +7 -3
- data/lib/sequent/test/time_comparison.rb +12 -5
- data/lib/sequent/test.rb +2 -0
- data/lib/sequent/util/dry_run.rb +28 -20
- data/lib/sequent/util/printer.rb +6 -5
- data/lib/sequent/util/skip_if_already_processing.rb +3 -1
- data/lib/sequent/util/timer.rb +2 -0
- data/lib/sequent/util/util.rb +2 -0
- data/lib/sequent.rb +2 -0
- data/lib/version.rb +3 -1
- metadata +84 -67
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
require 'rake'
|
|
3
5
|
require 'rake/tasklib'
|
|
@@ -12,13 +14,14 @@ module Sequent
|
|
|
12
14
|
|
|
13
15
|
def register_tasks!
|
|
14
16
|
namespace :sequent do
|
|
15
|
-
desc
|
|
17
|
+
desc <<~EOS
|
|
18
|
+
Rake task that runs before all sequent rake tasks. Hook applications can use to for instance run other rake tasks.
|
|
19
|
+
EOS
|
|
16
20
|
task :init
|
|
17
21
|
|
|
18
22
|
namespace :db do
|
|
19
|
-
|
|
20
23
|
desc 'Creates the database and initializes the event_store schema for the current env'
|
|
21
|
-
task :
|
|
24
|
+
task create: ['sequent:init'] do
|
|
22
25
|
ensure_rack_env_set!
|
|
23
26
|
|
|
24
27
|
db_config = Sequent::Support::Database.read_config(@env)
|
|
@@ -31,14 +34,18 @@ module Sequent
|
|
|
31
34
|
task :drop, [:production] => ['sequent:init'] do |_t, args|
|
|
32
35
|
ensure_rack_env_set!
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
if @env == 'production' && args[:production] != 'yes_drop_production'
|
|
38
|
+
fail <<~OES
|
|
39
|
+
Wont drop db in production unless you whitelist the environment as follows: rake sequent:db:drop[yes_drop_production]
|
|
40
|
+
OES
|
|
41
|
+
end
|
|
35
42
|
|
|
36
43
|
db_config = Sequent::Support::Database.read_config(@env)
|
|
37
44
|
Sequent::Support::Database.drop!(db_config)
|
|
38
45
|
end
|
|
39
46
|
|
|
40
47
|
desc 'Creates the view schema for the current env'
|
|
41
|
-
task :
|
|
48
|
+
task create_view_schema: ['sequent:init'] do
|
|
42
49
|
ensure_rack_env_set!
|
|
43
50
|
|
|
44
51
|
db_config = Sequent::Support::Database.read_config(@env)
|
|
@@ -47,33 +54,41 @@ module Sequent
|
|
|
47
54
|
end
|
|
48
55
|
|
|
49
56
|
desc 'Creates the event_store schema for the current env'
|
|
50
|
-
task :
|
|
57
|
+
task create_event_store: ['sequent:init'] do
|
|
51
58
|
ensure_rack_env_set!
|
|
52
59
|
db_config = Sequent::Support::Database.read_config(@env)
|
|
53
60
|
create_event_store(db_config)
|
|
54
61
|
end
|
|
55
62
|
|
|
63
|
+
# rubocop:disable Lint/NestedMethodDefinition
|
|
56
64
|
def create_event_store(db_config)
|
|
57
65
|
event_store_schema = Sequent.configuration.event_store_schema_name
|
|
58
66
|
sequent_schema = File.join(Sequent.configuration.database_schema_directory, "#{event_store_schema}.rb")
|
|
59
|
-
|
|
67
|
+
unless File.exist?(sequent_schema)
|
|
68
|
+
fail "File #{sequent_schema} does not exist. Check your Sequent configuration."
|
|
69
|
+
end
|
|
60
70
|
|
|
61
71
|
Sequent::Support::Database.establish_connection(db_config)
|
|
62
|
-
|
|
72
|
+
if Sequent::Support::Database.schema_exists?(event_store_schema)
|
|
73
|
+
fail "Schema #{event_store_schema} already exists in the database"
|
|
74
|
+
end
|
|
63
75
|
|
|
64
76
|
Sequent::Support::Database.create_schema(event_store_schema)
|
|
65
77
|
Sequent::Support::Database.with_schema_search_path(event_store_schema, db_config, @env) do
|
|
66
78
|
load(sequent_schema)
|
|
67
79
|
end
|
|
68
80
|
end
|
|
81
|
+
# rubocop:enable Lint/NestedMethodDefinition
|
|
69
82
|
end
|
|
70
83
|
|
|
71
84
|
namespace :migrate do
|
|
72
|
-
desc
|
|
85
|
+
desc <<~EOS
|
|
86
|
+
Rake task that runs before all migrate rake tasks. Hook applications can use to for instance run other rake tasks.
|
|
87
|
+
EOS
|
|
73
88
|
task :init
|
|
74
89
|
|
|
75
90
|
desc 'Prints the current version in the database'
|
|
76
|
-
task :
|
|
91
|
+
task current_version: ['sequent:init', :init] do
|
|
77
92
|
ensure_rack_env_set!
|
|
78
93
|
|
|
79
94
|
Sequent::Support::Database.connect!(@env)
|
|
@@ -81,8 +96,10 @@ module Sequent
|
|
|
81
96
|
puts "Current version in the database is: #{Sequent::Migrations::ViewSchema::Versions.maximum(:version)}"
|
|
82
97
|
end
|
|
83
98
|
|
|
84
|
-
desc
|
|
85
|
-
|
|
99
|
+
desc <<~EOS
|
|
100
|
+
Migrates the Projectors while the app is running. Call +sequent:migrate:offline+ after this successfully completed.
|
|
101
|
+
EOS
|
|
102
|
+
task online: ['sequent:init', :init] do
|
|
86
103
|
ensure_rack_env_set!
|
|
87
104
|
|
|
88
105
|
db_config = Sequent::Support::Database.read_config(@env)
|
|
@@ -91,8 +108,10 @@ module Sequent
|
|
|
91
108
|
view_schema.migrate_online
|
|
92
109
|
end
|
|
93
110
|
|
|
94
|
-
desc
|
|
95
|
-
|
|
111
|
+
desc <<~EOS
|
|
112
|
+
Migrates the events inserted while +online+ was running. It is expected +sequent:migrate:online+ ran first.
|
|
113
|
+
EOS
|
|
114
|
+
task offline: ['sequent:init', :init] do
|
|
96
115
|
ensure_rack_env_set!
|
|
97
116
|
|
|
98
117
|
db_config = Sequent::Support::Database.read_config(@env)
|
|
@@ -103,21 +122,35 @@ module Sequent
|
|
|
103
122
|
end
|
|
104
123
|
|
|
105
124
|
namespace :snapshots do
|
|
106
|
-
desc
|
|
125
|
+
desc <<~EOS
|
|
126
|
+
Rake task that runs before all snapshots rake tasks. Hook applications can use to for instance run other rake tasks.
|
|
127
|
+
EOS
|
|
107
128
|
task :init
|
|
108
129
|
|
|
109
|
-
task :set_snapshot_threshold, [
|
|
130
|
+
task :set_snapshot_threshold, %i[aggregate_type threshold] => ['sequent:init', :init] do |_t, args|
|
|
110
131
|
aggregate_type = args['aggregate_type']
|
|
111
132
|
threshold = args['threshold']
|
|
112
133
|
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
unless aggregate_type
|
|
135
|
+
fail ArgumentError,
|
|
136
|
+
'usage rake sequent:snapshots:set_snapshot_threshold[AggregegateType,threshold]'
|
|
137
|
+
end
|
|
138
|
+
unless threshold
|
|
139
|
+
fail ArgumentError,
|
|
140
|
+
'usage rake sequent:snapshots:set_snapshot_threshold[AggregegateType,threshold]'
|
|
141
|
+
end
|
|
115
142
|
|
|
116
|
-
execute
|
|
143
|
+
execute <<~EOS
|
|
144
|
+
UPDATE #{Sequent.configuration.stream_record_class} SET snapshot_threshold = #{threshold.to_i} WHERE aggregate_type = '#{aggregate_type}'
|
|
145
|
+
EOS
|
|
117
146
|
end
|
|
118
147
|
|
|
119
|
-
task :
|
|
120
|
-
result = Sequent::ApplicationRecord
|
|
148
|
+
task delete_all: ['sequent:init', :init] do
|
|
149
|
+
result = Sequent::ApplicationRecord
|
|
150
|
+
.connection
|
|
151
|
+
.execute(<<~EOS)
|
|
152
|
+
DELETE FROM #{Sequent.configuration.event_record_class.table_name} WHERE event_type = 'Sequent::Core::SnapshotEvent'
|
|
153
|
+
EOS
|
|
121
154
|
Sequent.logger.info "Deleted #{result.cmd_tuples} aggregate snapshots from the event store"
|
|
122
155
|
end
|
|
123
156
|
end
|
|
@@ -125,9 +158,12 @@ module Sequent
|
|
|
125
158
|
end
|
|
126
159
|
|
|
127
160
|
private
|
|
161
|
+
|
|
162
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
|
128
163
|
def ensure_rack_env_set!
|
|
129
|
-
@env ||= ENV['RACK_ENV'] || fail(
|
|
164
|
+
@env ||= ENV['RACK_ENV'] || fail('RACK_ENV not set')
|
|
130
165
|
end
|
|
166
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
|
131
167
|
end
|
|
132
168
|
end
|
|
133
169
|
end
|
data/lib/sequent/rake/tasks.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
require 'rake'
|
|
3
5
|
require 'rake/tasklib'
|
|
@@ -11,12 +13,13 @@ module Sequent
|
|
|
11
13
|
|
|
12
14
|
DEFAULT_OPTIONS = {
|
|
13
15
|
migrations_path: 'db/migrate',
|
|
14
|
-
event_store_schema: 'public'
|
|
15
|
-
}
|
|
16
|
+
event_store_schema: 'public',
|
|
17
|
+
}.freeze
|
|
16
18
|
|
|
17
19
|
attr_reader :options
|
|
18
20
|
|
|
19
21
|
def initialize(options)
|
|
22
|
+
super()
|
|
20
23
|
@options = DEFAULT_OPTIONS.merge(options)
|
|
21
24
|
end
|
|
22
25
|
|
data/lib/sequent/sequent.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_support/hash_with_indifferent_access'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
@@ -16,20 +18,25 @@ module Sequent
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def self.read_config(env)
|
|
19
|
-
fail ArgumentError
|
|
21
|
+
fail ArgumentError, 'env is mandatory' unless env
|
|
20
22
|
|
|
21
23
|
database_yml = File.join(Sequent.configuration.database_config_directory, 'database.yml')
|
|
22
|
-
YAML.
|
|
24
|
+
config = YAML.safe_load(ERB.new(File.read(database_yml)).result, aliases: true)[env]
|
|
25
|
+
if Gem.loaded_specs['activerecord'].version >= Gem::Version.create('6.1.0')
|
|
26
|
+
ActiveRecord::Base.configurations.resolve(config).configuration_hash.with_indifferent_access
|
|
27
|
+
else
|
|
28
|
+
ActiveRecord::Base.resolve_config_for_connection(config)
|
|
29
|
+
end
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
def self.create!(db_config)
|
|
26
|
-
ActiveRecord::Base.establish_connection(db_config.merge(
|
|
27
|
-
ActiveRecord::Base.connection.create_database(db_config[
|
|
33
|
+
ActiveRecord::Base.establish_connection(db_config.merge(database: 'postgres'))
|
|
34
|
+
ActiveRecord::Base.connection.create_database(db_config[:database])
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
def self.drop!(db_config)
|
|
31
|
-
ActiveRecord::Base.establish_connection(db_config.merge(
|
|
32
|
-
ActiveRecord::Base.connection.drop_database(db_config[
|
|
38
|
+
ActiveRecord::Base.establish_connection(db_config.merge(database: 'postgres'))
|
|
39
|
+
ActiveRecord::Base.connection.drop_database(db_config[:database])
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
def self.establish_connection(db_config)
|
|
@@ -46,9 +53,8 @@ module Sequent
|
|
|
46
53
|
|
|
47
54
|
def self.create_schema(schema)
|
|
48
55
|
sql = "CREATE SCHEMA IF NOT EXISTS #{schema}"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
56
|
+
user = configuration_hash[:username]
|
|
57
|
+
sql += %( AUTHORIZATION "#{user}") if user
|
|
52
58
|
execute_sql(sql)
|
|
53
59
|
end
|
|
54
60
|
|
|
@@ -57,32 +63,41 @@ module Sequent
|
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def self.with_schema_search_path(search_path, db_config, env = ENV['RACK_ENV'])
|
|
60
|
-
fail ArgumentError
|
|
66
|
+
fail ArgumentError, 'env is required' unless env
|
|
61
67
|
|
|
62
68
|
disconnect!
|
|
63
|
-
original_search_paths = db_config[
|
|
69
|
+
original_search_paths = db_config[:schema_search_path].dup
|
|
64
70
|
|
|
65
71
|
if ActiveRecord::VERSION::MAJOR < 6
|
|
66
|
-
ActiveRecord::Base.configurations[env.to_s] =
|
|
72
|
+
ActiveRecord::Base.configurations[env.to_s] =
|
|
73
|
+
ActiveSupport::HashWithIndifferentAccess.new(db_config).stringify_keys
|
|
67
74
|
end
|
|
68
75
|
|
|
69
|
-
db_config[
|
|
76
|
+
db_config[:schema_search_path] = search_path
|
|
70
77
|
|
|
71
78
|
ActiveRecord::Base.establish_connection db_config
|
|
72
79
|
|
|
73
80
|
yield
|
|
74
81
|
ensure
|
|
75
82
|
disconnect!
|
|
76
|
-
db_config[
|
|
83
|
+
db_config[:schema_search_path] = original_search_paths
|
|
77
84
|
establish_connection(db_config)
|
|
78
85
|
end
|
|
79
86
|
|
|
80
87
|
def self.schema_exists?(schema)
|
|
81
88
|
ActiveRecord::Base.connection.execute(
|
|
82
|
-
"SELECT schema_name FROM information_schema.schemata WHERE schema_name like '#{schema}'"
|
|
89
|
+
"SELECT schema_name FROM information_schema.schemata WHERE schema_name like '#{schema}'",
|
|
83
90
|
).count == 1
|
|
84
91
|
end
|
|
85
92
|
|
|
93
|
+
def self.configuration_hash
|
|
94
|
+
if Gem.loaded_specs['activesupport'].version >= Gem::Version.create('6.1.0')
|
|
95
|
+
ActiveRecord::Base.connection_db_config.configuration_hash
|
|
96
|
+
else
|
|
97
|
+
ActiveRecord::Base.connection_config
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
86
101
|
def schema_exists?(schema)
|
|
87
102
|
self.class.schema_exists?(schema)
|
|
88
103
|
end
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'postgresql_cursor'
|
|
2
4
|
|
|
3
5
|
module Sequent
|
|
4
6
|
module Support
|
|
5
7
|
class ViewProjection
|
|
6
8
|
attr_reader :name, :version, :schema_definition
|
|
9
|
+
|
|
7
10
|
def initialize(options)
|
|
8
11
|
@name = options.fetch(:name)
|
|
9
12
|
@version = options.fetch(:version)
|
|
@@ -20,7 +23,7 @@ module Sequent
|
|
|
20
23
|
ordering = Events::ORDERED_BY_STREAM
|
|
21
24
|
event_store.replay_events_from_cursor(
|
|
22
25
|
block_size: 10_000,
|
|
23
|
-
get_events: ->
|
|
26
|
+
get_events: -> { ordering[event_store] },
|
|
24
27
|
)
|
|
25
28
|
end
|
|
26
29
|
end
|
|
@@ -42,13 +45,13 @@ module Sequent
|
|
|
42
45
|
module Events
|
|
43
46
|
extend ActiveRecord::ConnectionAdapters::Quoting
|
|
44
47
|
|
|
45
|
-
ORDERED_BY_STREAM =
|
|
48
|
+
ORDERED_BY_STREAM = ->(_event_store) do
|
|
46
49
|
event_records = quote_table_name(Sequent.configuration.event_record_class.table_name)
|
|
47
50
|
stream_records = quote_table_name(Sequent.configuration.stream_record_class.table_name)
|
|
48
51
|
snapshot_event_type = quote(Sequent.configuration.snapshot_event_class)
|
|
49
52
|
|
|
50
53
|
Sequent.configuration.event_record_class
|
|
51
|
-
.select(
|
|
54
|
+
.select('event_type, event_json')
|
|
52
55
|
.joins("INNER JOIN #{stream_records} ON #{event_records}.stream_record_id = #{stream_records}.id")
|
|
53
56
|
.where("event_type <> #{snapshot_event_type}")
|
|
54
57
|
.order!("#{stream_records}.id, #{event_records}.sequence_number")
|
data/lib/sequent/support.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thread_safe'
|
|
2
4
|
require 'sequent/core/event_store'
|
|
3
5
|
|
|
@@ -36,7 +38,6 @@ module Sequent
|
|
|
36
38
|
#
|
|
37
39
|
# end
|
|
38
40
|
module CommandHandlerHelpers
|
|
39
|
-
|
|
40
41
|
class FakeEventStore
|
|
41
42
|
extend Forwardable
|
|
42
43
|
|
|
@@ -89,7 +90,7 @@ module Sequent
|
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
def stream_exists?(aggregate_id)
|
|
92
|
-
@event_streams.
|
|
93
|
+
@event_streams.key?(aggregate_id)
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def events_exists?(aggregate_id)
|
|
@@ -99,15 +100,21 @@ module Sequent
|
|
|
99
100
|
private
|
|
100
101
|
|
|
101
102
|
def to_event_streams(events)
|
|
102
|
-
# Specs use a simple list of given events.
|
|
103
|
+
# Specs use a simple list of given events.
|
|
104
|
+
# We need a mapping from StreamRecord to the associated events for the event store.
|
|
103
105
|
streams_by_aggregate_id = {}
|
|
104
106
|
events.map do |event|
|
|
105
107
|
event_stream = streams_by_aggregate_id.fetch(event.aggregate_id) do |aggregate_id|
|
|
106
108
|
streams_by_aggregate_id[aggregate_id] =
|
|
107
109
|
find_event_stream(aggregate_id) ||
|
|
108
110
|
begin
|
|
109
|
-
aggregate_type =
|
|
110
|
-
|
|
111
|
+
aggregate_type = aggregate_type_for_event(event)
|
|
112
|
+
unless aggregate_type
|
|
113
|
+
fail <<~EOS
|
|
114
|
+
Cannot find aggregate type associated with creation event #{event}, did you include an event handler in your aggregate for this event?
|
|
115
|
+
EOS
|
|
116
|
+
end
|
|
117
|
+
|
|
111
118
|
Sequent::Core::EventStream.new(aggregate_type: aggregate_type.name, aggregate_id: aggregate_id)
|
|
112
119
|
end
|
|
113
120
|
end
|
|
@@ -115,10 +122,10 @@ module Sequent
|
|
|
115
122
|
end
|
|
116
123
|
end
|
|
117
124
|
|
|
118
|
-
def
|
|
125
|
+
def aggregate_type_for_event(event)
|
|
119
126
|
@event_to_aggregate_type ||= ThreadSafe::Cache.new
|
|
120
127
|
@event_to_aggregate_type.fetch_or_store(event.class) do |klass|
|
|
121
|
-
Sequent::Core::
|
|
128
|
+
Sequent::Core::AggregateRoots.all.find { |x| x.message_mapping.key?(klass) }
|
|
122
129
|
end
|
|
123
130
|
end
|
|
124
131
|
|
|
@@ -133,30 +140,41 @@ module Sequent
|
|
|
133
140
|
end
|
|
134
141
|
end
|
|
135
142
|
|
|
136
|
-
def given_events
|
|
143
|
+
def given_events(*events)
|
|
137
144
|
Sequent.configuration.event_store.given_events(events.flatten(1))
|
|
138
145
|
end
|
|
139
146
|
|
|
140
|
-
def when_command
|
|
147
|
+
def when_command(command)
|
|
141
148
|
Sequent.configuration.command_service.execute_commands command
|
|
142
149
|
end
|
|
143
150
|
|
|
144
151
|
def then_events(*expected_events)
|
|
145
|
-
expected_classes = expected_events.flatten(1).map { |event| event.
|
|
152
|
+
expected_classes = expected_events.flatten(1).map { |event| event.instance_of?(Class) ? event : event.class }
|
|
146
153
|
expect(Sequent.configuration.event_store.stored_events.map(&:class)).to eq(expected_classes)
|
|
147
154
|
|
|
148
|
-
Sequent
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
Sequent
|
|
156
|
+
.configuration
|
|
157
|
+
.event_store
|
|
158
|
+
.stored_events
|
|
159
|
+
.zip(expected_events.flatten(1))
|
|
160
|
+
.each_with_index do |(actual, expected), index|
|
|
161
|
+
next if expected.instance_of?(Class)
|
|
162
|
+
|
|
163
|
+
actual_hash = Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(actual.payload))
|
|
164
|
+
expected_hash = Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(expected.payload))
|
|
165
|
+
next unless expected
|
|
166
|
+
|
|
167
|
+
# rubocop:disable Layout/LineLength
|
|
168
|
+
expect(actual_hash)
|
|
169
|
+
.to eq(expected_hash),
|
|
170
|
+
"#{index + 1}th Event of type #{actual.class} not equal\nexpected: #{expected_hash.inspect}\n got: #{actual_hash.inspect}"
|
|
171
|
+
# rubocop:enable Layout/LineLength
|
|
172
|
+
end
|
|
154
173
|
end
|
|
155
174
|
|
|
156
175
|
def then_no_events
|
|
157
176
|
then_events
|
|
158
177
|
end
|
|
159
|
-
|
|
160
178
|
end
|
|
161
179
|
end
|
|
162
180
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Test
|
|
3
5
|
##
|
|
@@ -26,7 +28,6 @@ module Sequent
|
|
|
26
28
|
# end
|
|
27
29
|
# end
|
|
28
30
|
module WorkflowHelpers
|
|
29
|
-
|
|
30
31
|
class FakeTransactionProvider
|
|
31
32
|
def initialize
|
|
32
33
|
@after_commit_blocks = []
|
|
@@ -55,12 +56,17 @@ module Sequent
|
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def then_events(*expected_events)
|
|
58
|
-
expected_classes = expected_events.flatten(1).map { |event| event.
|
|
59
|
+
expected_classes = expected_events.flatten(1).map { |event| event.instance_of?(Class) ? event : event.class }
|
|
59
60
|
expect(Sequent.configuration.event_store.stored_events.map(&:class)).to eq(expected_classes)
|
|
60
61
|
|
|
61
62
|
Sequent.configuration.event_store.stored_events.zip(expected_events.flatten(1)).each do |actual, expected|
|
|
62
|
-
next if expected.
|
|
63
|
-
|
|
63
|
+
next if expected.instance_of?(Class)
|
|
64
|
+
|
|
65
|
+
next unless expected
|
|
66
|
+
|
|
67
|
+
expect(
|
|
68
|
+
Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(actual.payload)),
|
|
69
|
+
).to eq(Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(expected.payload)))
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Test
|
|
3
5
|
##
|
|
@@ -41,12 +43,14 @@ module Sequent
|
|
|
41
43
|
@events = []
|
|
42
44
|
end
|
|
43
45
|
|
|
46
|
+
# rubocop:disable Style/MissingRespondToMissing
|
|
44
47
|
def method_missing(name, *args, &block)
|
|
45
48
|
args = prepare_arguments(args)
|
|
46
49
|
@events << FactoryBot.build(name, *args, &block)
|
|
47
50
|
end
|
|
51
|
+
# rubocop:enable Style/MissingRespondToMissing
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
private
|
|
50
54
|
|
|
51
55
|
def prepare_arguments(args)
|
|
52
56
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
@@ -68,10 +72,10 @@ module Sequent
|
|
|
68
72
|
given_events(*event_stream(aggregate_id: aggregate_id, &block))
|
|
69
73
|
end
|
|
70
74
|
|
|
71
|
-
def self.included(
|
|
75
|
+
def self.included(_spec)
|
|
72
76
|
require 'factory_bot'
|
|
73
77
|
rescue LoadError
|
|
74
|
-
raise ArgumentError,
|
|
78
|
+
raise ArgumentError, 'Factory bot is required to use the event stream helpers'
|
|
75
79
|
end
|
|
76
80
|
end
|
|
77
81
|
end
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Test
|
|
3
5
|
module DateTimePatches
|
|
4
6
|
module Normalize
|
|
5
7
|
def normalize
|
|
6
|
-
in_time_zone(
|
|
8
|
+
in_time_zone('UTC')
|
|
7
9
|
end
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
module Compare
|
|
11
|
-
|
|
13
|
+
# rubocop:disable Style/Alias
|
|
14
|
+
alias :'___<=>' :'<=>'
|
|
15
|
+
# rubocop:enable Style/Alias
|
|
12
16
|
|
|
13
17
|
# omit nsec in datetime comparisons
|
|
14
18
|
def <=>(other)
|
|
15
|
-
if other
|
|
19
|
+
if other&.is_a?(DateTimePatches::Normalize)
|
|
16
20
|
result = normalize.iso8601 <=> other.normalize.iso8601
|
|
17
21
|
return result unless result == 0
|
|
22
|
+
|
|
18
23
|
# use usec here, which *truncates* the nsec (ie. like Postgres)
|
|
19
24
|
return normalize.usec <=> other.normalize.usec
|
|
20
25
|
end
|
|
@@ -35,6 +40,8 @@ class DateTime
|
|
|
35
40
|
prepend Sequent::Test::DateTimePatches::Compare
|
|
36
41
|
end
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
module ActiveSupport
|
|
44
|
+
class TimeWithZone
|
|
45
|
+
prepend Sequent::Test::DateTimePatches::Normalize
|
|
46
|
+
end
|
|
40
47
|
end
|