sequent 4.0.0 → 4.1.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 +31 -25
- 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 +17 -13
- data/lib/sequent/core/aggregate_root.rb +16 -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 +57 -50
- 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 +25 -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 +5 -9
- data/lib/sequent/core/transactions/no_transactions.rb +2 -1
- data/lib/sequent/core/transactions/transactions.rb +2 -0
- data/lib/sequent/core/value_object.rb +8 -10
- data/lib/sequent/core/workflow.rb +7 -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 +2 -0
- data/lib/sequent/generator/template_project/Gemfile +1 -1
- 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 +11 -8
- 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 +81 -66
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e0c606e57bb36fdcaeeb102904dd450a0ff9fc2a84fdd5aff7f365a914c45d3
|
|
4
|
+
data.tar.gz: ea03e8a0c7ab524ff8c73fded97a726edf3dff240b95db62496496d5a6a6d580
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 10f78e12826549dcd0ea3677940b5b2cdbde26fb9ecdf2f5a1bca67d5dd8405447409417b6cd277bd554c3590a2a7a3814a0918ae79ceec2b6da9510d8d1fa3f
|
|
7
|
+
data.tar.gz: abde6abb5638e8ad3f4f2081eb1e9eada6588d4d74994a5a62a4ef761b9737277981a5c270e75297a5e508cc611031a2e779df43b3a70b3d5a9bb0b77c81ec34
|
data/bin/sequent
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
2
4
|
require_relative '../lib/sequent/generator'
|
|
3
5
|
|
|
4
6
|
command = ARGV[0].to_s.strip
|
|
@@ -8,8 +10,8 @@ abort('Please specify a command. i.e. `sequent new myapp`') if ARGV[1..-1].empty
|
|
|
8
10
|
args = ARGV[1..-1].map(&:to_s).map(&:strip)
|
|
9
11
|
|
|
10
12
|
def new_project(args)
|
|
11
|
-
|
|
12
|
-
name =
|
|
13
|
+
arguments = args.dup
|
|
14
|
+
name = arguments.shift
|
|
13
15
|
abort('Please specify a directory name. i.e. `sequent new myapp`') if name.empty?
|
|
14
16
|
|
|
15
17
|
Sequent::Generator::Project.new(name).execute
|
|
@@ -43,8 +45,8 @@ def new_project(args)
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def generate_aggregate(args)
|
|
46
|
-
|
|
47
|
-
aggregate_name =
|
|
48
|
+
arguments = args.dup
|
|
49
|
+
aggregate_name = arguments.shift
|
|
48
50
|
abort('Please specify an aggregate name. i.e. `sequent g aggregate user`') unless args_valid?(aggregate_name)
|
|
49
51
|
|
|
50
52
|
Sequent::Generator::Aggregate.new(aggregate_name).execute
|
|
@@ -52,41 +54,45 @@ def generate_aggregate(args)
|
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def generate_command(args)
|
|
55
|
-
|
|
56
|
-
aggregate_name =
|
|
57
|
-
command_name =
|
|
58
|
-
attrs =
|
|
57
|
+
arguments = args.dup
|
|
58
|
+
aggregate_name = arguments.shift
|
|
59
|
+
command_name = arguments.shift
|
|
60
|
+
attrs = arguments
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
unless args_valid?(aggregate_name, command_name)
|
|
63
|
+
abort('Please specify an aggregate name and command name. i.e. `sequent g command user AddUser`')
|
|
64
|
+
end
|
|
61
65
|
Sequent::Generator::Command.new(aggregate_name, command_name, attrs).execute
|
|
62
66
|
puts "#{command_name} command has been added to #{aggregate_name}"
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def generate_event(args)
|
|
66
|
-
|
|
67
|
-
aggregate_name =
|
|
68
|
-
event_name =
|
|
69
|
-
attrs =
|
|
70
|
-
|
|
71
|
-
abort('Please specify an aggregate name and event name. i.e. `sequent g event user AddUser`') unless args_valid?(
|
|
70
|
+
arguments = args.dup
|
|
71
|
+
aggregate_name = arguments.shift
|
|
72
|
+
event_name = arguments.shift
|
|
73
|
+
attrs = arguments
|
|
74
|
+
|
|
75
|
+
abort('Please specify an aggregate name and event name. i.e. `sequent g event user AddUser`') unless args_valid?(
|
|
76
|
+
aggregate_name, event_name
|
|
77
|
+
)
|
|
72
78
|
Sequent::Generator::Event.new(aggregate_name, event_name, attrs).execute
|
|
73
79
|
puts "#{event_name} event has been added to #{aggregate_name}"
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
def generate(args)
|
|
77
|
-
|
|
78
|
-
entity =
|
|
83
|
+
arguments = args.dup
|
|
84
|
+
entity = arguments.shift
|
|
79
85
|
abort('Please specify a command. i.e. `sequent g aggregate user`') if entity.empty?
|
|
80
86
|
|
|
81
87
|
case entity
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
when 'aggregate'
|
|
89
|
+
generate_aggregate(arguments)
|
|
90
|
+
when 'command'
|
|
91
|
+
generate_command(arguments)
|
|
92
|
+
when 'event'
|
|
93
|
+
generate_event(arguments)
|
|
94
|
+
else
|
|
95
|
+
abort("Unknown argument #{entity} for `generate`. Try `sequent g aggregate user`")
|
|
90
96
|
end
|
|
91
97
|
end
|
|
92
98
|
|
data/lib/notices.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'core/event_store'
|
|
2
4
|
require_relative 'core/command_service'
|
|
3
5
|
require_relative 'core/transactions/no_transactions'
|
|
@@ -7,7 +9,6 @@ require 'logger'
|
|
|
7
9
|
|
|
8
10
|
module Sequent
|
|
9
11
|
class Configuration
|
|
10
|
-
|
|
11
12
|
DEFAULT_VERSIONS_TABLE_NAME = 'sequent_versions'
|
|
12
13
|
DEFAULT_REPLAYED_IDS_TABLE_NAME = 'sequent_replayed_ids'
|
|
13
14
|
|
|
@@ -16,7 +17,7 @@ module Sequent
|
|
|
16
17
|
DEFAULT_DATABASE_SCHEMA_DIRECTORY = 'db'
|
|
17
18
|
|
|
18
19
|
DEFAULT_VIEW_SCHEMA_NAME = 'view_schema'
|
|
19
|
-
DEFAULT_EVENT_STORE_SCHEMA_NAME= 'sequent_schema'
|
|
20
|
+
DEFAULT_EVENT_STORE_SCHEMA_NAME = 'sequent_schema'
|
|
20
21
|
|
|
21
22
|
MIGRATIONS_CLASS_NAME = 'Sequent::Migrations::Projectors'
|
|
22
23
|
|
|
@@ -31,41 +32,31 @@ module Sequent
|
|
|
31
32
|
|
|
32
33
|
DEFAULT_ERROR_LOCALE_RESOLVER = -> { I18n.locale || :en }
|
|
33
34
|
|
|
34
|
-
attr_accessor :aggregate_repository
|
|
35
|
-
|
|
36
|
-
attr_accessor :event_store,
|
|
35
|
+
attr_accessor :aggregate_repository,
|
|
36
|
+
:event_store,
|
|
37
37
|
:command_service,
|
|
38
38
|
:event_record_class,
|
|
39
39
|
:stream_record_class,
|
|
40
40
|
:snapshot_event_class,
|
|
41
41
|
:transaction_provider,
|
|
42
|
-
:event_publisher
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
attr_accessor :disable_event_handlers
|
|
54
|
-
|
|
55
|
-
attr_accessor :logger
|
|
56
|
-
|
|
57
|
-
attr_accessor :error_locale_resolver
|
|
58
|
-
|
|
59
|
-
attr_accessor :migration_sql_files_directory,
|
|
42
|
+
:event_publisher,
|
|
43
|
+
:event_record_hooks_class,
|
|
44
|
+
:command_handlers,
|
|
45
|
+
:command_filters,
|
|
46
|
+
:event_handlers,
|
|
47
|
+
:uuid_generator,
|
|
48
|
+
:disable_event_handlers,
|
|
49
|
+
:logger,
|
|
50
|
+
:error_locale_resolver,
|
|
51
|
+
:migration_sql_files_directory,
|
|
60
52
|
:view_schema_name,
|
|
61
53
|
:offline_replay_persistor_class,
|
|
62
54
|
:online_replay_persistor_class,
|
|
63
55
|
:number_of_replay_processes,
|
|
64
56
|
:database_config_directory,
|
|
65
57
|
:database_schema_directory,
|
|
66
|
-
:event_store_schema_name
|
|
67
|
-
|
|
68
|
-
attr_accessor :strict_check_attributes_on_apply_events
|
|
58
|
+
:event_store_schema_name,
|
|
59
|
+
:strict_check_attributes_on_apply_events
|
|
69
60
|
|
|
70
61
|
attr_reader :migrations_class_name,
|
|
71
62
|
:versions_table_name,
|
|
@@ -114,19 +105,19 @@ module Sequent
|
|
|
114
105
|
self.database_schema_directory = DEFAULT_DATABASE_SCHEMA_DIRECTORY
|
|
115
106
|
self.strict_check_attributes_on_apply_events = DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS
|
|
116
107
|
|
|
117
|
-
self.logger = Logger.new(STDOUT).tap {|l| l.level = Logger::INFO }
|
|
108
|
+
self.logger = Logger.new(STDOUT).tap { |l| l.level = Logger::INFO }
|
|
118
109
|
self.error_locale_resolver = DEFAULT_ERROR_LOCALE_RESOLVER
|
|
119
110
|
end
|
|
120
111
|
|
|
121
112
|
def replayed_ids_table_name=(table_name)
|
|
122
|
-
fail ArgumentError
|
|
113
|
+
fail ArgumentError, 'table_name can not be nil' unless table_name
|
|
123
114
|
|
|
124
115
|
@replayed_ids_table_name = table_name
|
|
125
116
|
Sequent::Migrations::ViewSchema::ReplayedIds.table_name = table_name
|
|
126
117
|
end
|
|
127
118
|
|
|
128
119
|
def versions_table_name=(table_name)
|
|
129
|
-
fail ArgumentError
|
|
120
|
+
fail ArgumentError, 'table_name can not be nil' unless table_name
|
|
130
121
|
|
|
131
122
|
@versions_table_name = table_name
|
|
132
123
|
Sequent::Migrations::ViewSchema::Versions.table_name = table_name
|
|
@@ -134,9 +125,11 @@ module Sequent
|
|
|
134
125
|
|
|
135
126
|
def migrations_class_name=(class_name)
|
|
136
127
|
migration_class = Class.const_get(class_name)
|
|
137
|
-
|
|
128
|
+
unless migration_class <= Sequent::Migrations::Projectors
|
|
129
|
+
fail ArgumentError, "#{migration_class} must extend Sequent::Migrations::Projectors"
|
|
130
|
+
end
|
|
131
|
+
|
|
138
132
|
@migrations_class_name = class_name
|
|
139
133
|
end
|
|
140
|
-
|
|
141
134
|
end
|
|
142
135
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
5
|
# Repository for aggregates.
|
|
@@ -38,7 +40,7 @@ module Sequent
|
|
|
38
40
|
def add_aggregate(aggregate)
|
|
39
41
|
existing = aggregates[aggregate.id]
|
|
40
42
|
if existing && !existing.equal?(aggregate)
|
|
41
|
-
|
|
43
|
+
fail NonUniqueAggregateId.new(aggregate, aggregates[aggregate.id])
|
|
42
44
|
else
|
|
43
45
|
aggregates[aggregate.id] = aggregate
|
|
44
46
|
end
|
|
@@ -69,30 +71,30 @@ module Sequent
|
|
|
69
71
|
# +aggregate_ids+ The ids of the aggregates to be loaded
|
|
70
72
|
# +clazz+ Optional argument that checks if all aggregates are of type +clazz+
|
|
71
73
|
def load_aggregates(aggregate_ids, clazz = nil)
|
|
72
|
-
fail ArgumentError
|
|
74
|
+
fail ArgumentError, 'aggregate_ids is required' unless aggregate_ids
|
|
73
75
|
return [] if aggregate_ids.empty?
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
unique_ids = aggregate_ids.uniq
|
|
78
|
+
result = aggregates.values_at(*unique_ids).compact
|
|
79
|
+
query_ids = unique_ids - result.map(&:id)
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
result += Sequent.configuration.event_store.load_events_for_aggregates(query_ids).map do |stream, events|
|
|
80
82
|
aggregate_class = Class.const_get(stream.aggregate_type)
|
|
81
83
|
aggregate_class.load_from_history(stream, events)
|
|
82
84
|
end
|
|
83
85
|
|
|
84
|
-
if
|
|
85
|
-
missing_aggregate_ids =
|
|
86
|
-
|
|
86
|
+
if result.count != unique_ids.count
|
|
87
|
+
missing_aggregate_ids = unique_ids - result.map(&:id)
|
|
88
|
+
fail AggregateNotFound, missing_aggregate_ids
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
if clazz
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
result.each do |aggregate|
|
|
93
|
+
fail TypeError, "#{aggregate.class} is not a #{clazz}" unless aggregate.class <= clazz
|
|
92
94
|
end
|
|
93
95
|
end
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
result.map do |aggregate|
|
|
96
98
|
aggregates[aggregate.id] = aggregate
|
|
97
99
|
end
|
|
98
100
|
end
|
|
@@ -119,8 +121,9 @@ module Sequent
|
|
|
119
121
|
def commit(command)
|
|
120
122
|
updated_aggregates = aggregates.values.reject { |x| x.uncommitted_events.empty? }
|
|
121
123
|
return if updated_aggregates.empty?
|
|
124
|
+
|
|
122
125
|
streams_with_events = updated_aggregates.map do |aggregate|
|
|
123
|
-
[
|
|
126
|
+
[aggregate.event_stream, aggregate.uncommitted_events]
|
|
124
127
|
end
|
|
125
128
|
updated_aggregates.each(&:clear_events)
|
|
126
129
|
store_events command, streams_with_events
|
|
@@ -136,6 +139,7 @@ module Sequent
|
|
|
136
139
|
# A +HasUncommittedEvents+ is raised when there are uncommitted_events in the Unit of Work.
|
|
137
140
|
def clear!
|
|
138
141
|
fail HasUncommittedEvents if aggregates.values.any? { |x| !x.uncommitted_events.empty? }
|
|
142
|
+
|
|
139
143
|
clear
|
|
140
144
|
end
|
|
141
145
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'base64'
|
|
2
4
|
require_relative 'helpers/message_handler'
|
|
3
5
|
require_relative 'helpers/autoset_attributes'
|
|
4
6
|
require_relative 'stream_record'
|
|
7
|
+
require_relative 'aggregate_roots'
|
|
5
8
|
|
|
6
9
|
module Sequent
|
|
7
10
|
module Core
|
|
8
|
-
|
|
9
11
|
module SnapshotConfiguration
|
|
10
12
|
module ClassMethods
|
|
11
13
|
##
|
|
@@ -40,13 +42,20 @@ module Sequent
|
|
|
40
42
|
|
|
41
43
|
attr_reader :id, :uncommitted_events, :sequence_number, :event_stream
|
|
42
44
|
|
|
45
|
+
def self.inherited(subclass)
|
|
46
|
+
super
|
|
47
|
+
AggregateRoots << subclass
|
|
48
|
+
end
|
|
49
|
+
|
|
43
50
|
def self.load_from_history(stream, events)
|
|
44
51
|
first, *rest = events
|
|
45
52
|
if first.is_a? SnapshotEvent
|
|
53
|
+
# rubocop:disable Security/MarshalLoad
|
|
46
54
|
aggregate_root = Marshal.load(Base64.decode64(first.data))
|
|
55
|
+
# rubocop:enable Security/MarshalLoad
|
|
47
56
|
rest.each { |x| aggregate_root.apply_event(x) }
|
|
48
57
|
else
|
|
49
|
-
aggregate_root = allocate
|
|
58
|
+
aggregate_root = allocate # allocate without calling new
|
|
50
59
|
aggregate_root.load_from_history(stream, events)
|
|
51
60
|
end
|
|
52
61
|
aggregate_root
|
|
@@ -62,7 +71,8 @@ module Sequent
|
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def load_from_history(stream, events)
|
|
65
|
-
|
|
74
|
+
fail 'Empty history' if events.empty?
|
|
75
|
+
|
|
66
76
|
@id = events.first.aggregate_id
|
|
67
77
|
@uncommitted_events = []
|
|
68
78
|
@sequence_number = 1
|
|
@@ -100,7 +110,7 @@ module Sequent
|
|
|
100
110
|
# apply InvoiceSentEvent, send_date: DateTime.now
|
|
101
111
|
# end
|
|
102
112
|
#
|
|
103
|
-
def apply(event, params={})
|
|
113
|
+
def apply(event, params = {})
|
|
104
114
|
event = build_event(event, params) if event.is_a?(Class)
|
|
105
115
|
apply_event(event)
|
|
106
116
|
@uncommitted_events << event
|
|
@@ -123,12 +133,11 @@ module Sequent
|
|
|
123
133
|
if args.empty?
|
|
124
134
|
apply event_class
|
|
125
135
|
elsif self.class
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
.event_attribute_keys(event_class)
|
|
137
|
+
.any? { |k| instance_variable_get(:"@#{k}") != args[k.to_sym] }
|
|
128
138
|
apply event_class, args
|
|
129
139
|
end
|
|
130
140
|
end
|
|
131
|
-
|
|
132
141
|
end
|
|
133
142
|
end
|
|
134
143
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sequent
|
|
4
|
+
module Core
|
|
5
|
+
#
|
|
6
|
+
# Utility class containing all subclasses of AggregateRoot
|
|
7
|
+
#
|
|
8
|
+
class AggregateRoots
|
|
9
|
+
class << self
|
|
10
|
+
def aggregate_roots
|
|
11
|
+
@aggregate_roots ||= []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def all
|
|
15
|
+
aggregate_roots
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def <<(aggregate_root)
|
|
19
|
+
aggregate_roots << aggregate_root
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Sequent
|
|
2
4
|
module Core
|
|
3
|
-
|
|
4
5
|
##
|
|
5
6
|
# Take up to `limit` snapshots when needed. Throws `:done` when done.
|
|
6
7
|
#
|
|
7
|
-
class SnapshotCommand <
|
|
8
|
+
class SnapshotCommand < Sequent::Core::BaseCommand
|
|
8
9
|
attrs limit: Integer
|
|
9
10
|
end
|
|
10
11
|
|
|
@@ -14,9 +15,11 @@ module Sequent
|
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
class AggregateSnapshotter < BaseCommandHandler
|
|
17
|
-
|
|
18
18
|
on SnapshotCommand do |command|
|
|
19
|
-
aggregate_ids =
|
|
19
|
+
aggregate_ids = Sequent.configuration.event_store.aggregates_that_need_snapshots(
|
|
20
|
+
@last_aggregate_id,
|
|
21
|
+
command.limit,
|
|
22
|
+
)
|
|
20
23
|
aggregate_ids.each do |aggregate_id|
|
|
21
24
|
take_snapshot!(aggregate_id)
|
|
22
25
|
end
|
|
@@ -32,7 +35,7 @@ module Sequent
|
|
|
32
35
|
aggregate = repository.load_aggregate(aggregate_id)
|
|
33
36
|
Sequent.logger.info "Taking snapshot for aggregate #{aggregate}"
|
|
34
37
|
aggregate.take_snapshot!
|
|
35
|
-
rescue => e
|
|
38
|
+
rescue StandardError => e
|
|
36
39
|
Sequent.logger.error("Failed to take snapshot for aggregate #{aggregate_id}: #{e}, #{e.inspect}")
|
|
37
40
|
end
|
|
38
41
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'helpers/message_handler'
|
|
2
4
|
require_relative 'helpers/uuid_helper'
|
|
3
5
|
|
|
@@ -17,8 +19,8 @@ module Sequent
|
|
|
17
19
|
# end
|
|
18
20
|
# end
|
|
19
21
|
class BaseCommandHandler
|
|
20
|
-
include Sequent::Core::Helpers::
|
|
21
|
-
|
|
22
|
+
include Sequent::Core::Helpers::UuidHelper
|
|
23
|
+
include Sequent::Core::Helpers::MessageHandler
|
|
22
24
|
|
|
23
25
|
protected
|
|
24
26
|
|
data/lib/sequent/core/command.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'helpers/copyable'
|
|
2
4
|
require_relative 'helpers/attribute_support'
|
|
3
5
|
require_relative 'helpers/uuid_helper'
|
|
@@ -17,13 +19,13 @@ module Sequent
|
|
|
17
19
|
# BaseCommand uses `ActiveModel::Validations` for
|
|
18
20
|
# validations
|
|
19
21
|
class BaseCommand
|
|
20
|
-
include
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
include Sequent::Core::Helpers::Mergable
|
|
23
|
+
include Sequent::Core::Helpers::ParamSupport
|
|
24
|
+
include Sequent::Core::Helpers::EqualSupport
|
|
25
|
+
include Sequent::Core::Helpers::UuidHelper
|
|
26
|
+
include Sequent::Core::Helpers::AttributeSupport
|
|
27
|
+
include Sequent::Core::Helpers::Copyable
|
|
28
|
+
include ActiveModel::Validations
|
|
27
29
|
include ActiveModel::Validations::Callbacks
|
|
28
30
|
include Sequent::Core::Helpers::TypeConversionSupport
|
|
29
31
|
|
|
@@ -35,6 +37,7 @@ module Sequent
|
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def self.inherited(subclass)
|
|
40
|
+
super
|
|
38
41
|
Commands << subclass
|
|
39
42
|
end
|
|
40
43
|
end
|
|
@@ -44,7 +47,11 @@ module Sequent
|
|
|
44
47
|
included do
|
|
45
48
|
attrs sequence_number: Integer
|
|
46
49
|
validates_presence_of :sequence_number
|
|
47
|
-
validates_numericality_of :sequence_number,
|
|
50
|
+
validates_numericality_of :sequence_number,
|
|
51
|
+
only_integer: true,
|
|
52
|
+
allow_nil: true,
|
|
53
|
+
allow_blank: true,
|
|
54
|
+
greater_than: 0
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
|
|
@@ -85,7 +92,8 @@ module Sequent
|
|
|
85
92
|
attrs aggregate_id: String, user_id: String, event_aggregate_id: String, event_sequence_number: Integer
|
|
86
93
|
|
|
87
94
|
def initialize(args = {})
|
|
88
|
-
|
|
95
|
+
fail ArgumentError, 'Missing aggregate_id' if args[:aggregate_id].nil?
|
|
96
|
+
|
|
89
97
|
super
|
|
90
98
|
end
|
|
91
99
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
require_relative 'sequent_oj'
|
|
3
5
|
|
|
4
6
|
module Sequent
|
|
5
7
|
module Core
|
|
6
|
-
|
|
7
8
|
module SerializesCommand
|
|
8
9
|
def command
|
|
9
10
|
args = Sequent::Core::Oj.strict_load(command_json)
|
|
@@ -21,7 +22,10 @@ module Sequent
|
|
|
21
22
|
# this should be moved to a configurable CommandSerializer
|
|
22
23
|
self.organization_id = command.organization_id if serialize_attribute?(command, :organization_id)
|
|
23
24
|
self.event_aggregate_id = command.event_aggregate_id if serialize_attribute?(command, :event_aggregate_id)
|
|
24
|
-
self.event_sequence_number = command.event_sequence_number if serialize_attribute?(
|
|
25
|
+
self.event_sequence_number = command.event_sequence_number if serialize_attribute?(
|
|
26
|
+
command,
|
|
27
|
+
:event_sequence_number,
|
|
28
|
+
)
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
private
|
|
@@ -35,7 +39,7 @@ module Sequent
|
|
|
35
39
|
class CommandRecord < Sequent::ApplicationRecord
|
|
36
40
|
include SerializesCommand
|
|
37
41
|
|
|
38
|
-
self.table_name =
|
|
42
|
+
self.table_name = 'command_records'
|
|
39
43
|
|
|
40
44
|
has_many :event_records
|
|
41
45
|
|
|
@@ -58,6 +62,7 @@ module Sequent
|
|
|
58
62
|
|
|
59
63
|
def find_origin(record)
|
|
60
64
|
return find_origin(record.parent) if record.parent.present?
|
|
65
|
+
|
|
61
66
|
record
|
|
62
67
|
end
|
|
63
68
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'transactions/no_transactions'
|
|
2
4
|
require_relative 'current_event'
|
|
3
5
|
|
|
@@ -39,7 +41,7 @@ module Sequent
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def remove_event_handler(clazz)
|
|
42
|
-
warn
|
|
44
|
+
warn '[DEPRECATION] `remove_event_handler` is deprecated'
|
|
43
45
|
event_store.remove_event_handler(clazz)
|
|
44
46
|
end
|
|
45
47
|
|
|
@@ -47,17 +49,13 @@ module Sequent
|
|
|
47
49
|
|
|
48
50
|
def process_commands
|
|
49
51
|
Sequent::Util.skip_if_already_processing(:command_service_process_commands) do
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
process_command(command_queue.pop)
|
|
54
|
-
end
|
|
55
|
-
Sequent::Util.done_processing(:command_service_process_commands)
|
|
56
|
-
end
|
|
57
|
-
ensure
|
|
58
|
-
command_queue.clear
|
|
59
|
-
repository.clear
|
|
52
|
+
transaction_provider.transactional do
|
|
53
|
+
process_command(command_queue.pop) until command_queue.empty?
|
|
54
|
+
Sequent::Util.done_processing(:command_service_process_commands)
|
|
60
55
|
end
|
|
56
|
+
ensure
|
|
57
|
+
command_queue.clear
|
|
58
|
+
repository.clear
|
|
61
59
|
end
|
|
62
60
|
end
|
|
63
61
|
|
|
@@ -68,12 +66,12 @@ module Sequent
|
|
|
68
66
|
|
|
69
67
|
filters.each { |filter| filter.execute(command) }
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
raise CommandNotValid.new(command) unless command.valid?
|
|
73
|
-
end
|
|
69
|
+
fail CommandNotValid, command unless command.valid?
|
|
74
70
|
|
|
75
71
|
parsed_command = command.parse_attrs_to_correct_types
|
|
76
|
-
command_handlers.select
|
|
72
|
+
command_handlers.select do |h|
|
|
73
|
+
h.class.handles_message?(parsed_command)
|
|
74
|
+
end.each { |h| h.handle_message parsed_command }
|
|
77
75
|
repository.commit(parsed_command)
|
|
78
76
|
end
|
|
79
77
|
|
|
@@ -108,12 +106,14 @@ module Sequent
|
|
|
108
106
|
|
|
109
107
|
def initialize(command)
|
|
110
108
|
@command = command
|
|
111
|
-
msg = @command.respond_to?(:aggregate_id) ? " #{@command.aggregate_id}" :
|
|
112
|
-
super "Invalid command #{@command.class
|
|
109
|
+
msg = @command.respond_to?(:aggregate_id) ? " #{@command.aggregate_id}" : ''
|
|
110
|
+
super "Invalid command #{@command.class}#{msg}, errors: #{@command.validation_errors}"
|
|
113
111
|
end
|
|
114
112
|
|
|
115
113
|
def errors(prefix = nil)
|
|
116
|
-
|
|
114
|
+
I18n.with_locale(Sequent.configuration.error_locale_resolver.call) do
|
|
115
|
+
@command.validation_errors(prefix)
|
|
116
|
+
end
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def errors_with_command_prefix
|
data/lib/sequent/core/core.rb
CHANGED