sequent 4.0.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: feea2aeb3dba28570a36615392e4ecc1037bfdfd1c2d667bcc787113e4c7fdba
|
4
|
+
data.tar.gz: 79dc60b21109885a56f3f68cf97185afee5692167f54db1934eaa89dc9d030de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb74d9c6cbb946bab9b00cb2ea73eab80caed4737d12ce2115a6c97348ca0e9452a14280b3d0de9ecba237c201fdd8b2914382751569224c89cae92128d5d3f5
|
7
|
+
data.tar.gz: 5fe084735eb4df8855b8102e68d027ca828a8dd7540d577b6e0a97616bbd22815432e684f20d48d04ad111db962fe935af4bff50afd8ebc7242f5c15f01b97a7
|
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
|
@@ -18,10 +20,11 @@ def new_project(args)
|
|
18
20
|
Success!
|
19
21
|
|
20
22
|
Your brand spanking new sequent app is waiting for you in:
|
21
|
-
#{File.expand_path(name,
|
23
|
+
#{File.expand_path(name, Dir.pwd)}
|
22
24
|
|
23
25
|
To finish setting up your app:
|
24
26
|
cd #{name}
|
27
|
+
bundle install
|
25
28
|
bundle exec rake sequent:db:create
|
26
29
|
bundle exec rake sequent:db:create_view_schema
|
27
30
|
bundle exec rake sequent:migrate:online
|
@@ -43,8 +46,8 @@ def new_project(args)
|
|
43
46
|
end
|
44
47
|
|
45
48
|
def generate_aggregate(args)
|
46
|
-
|
47
|
-
aggregate_name =
|
49
|
+
arguments = args.dup
|
50
|
+
aggregate_name = arguments.shift
|
48
51
|
abort('Please specify an aggregate name. i.e. `sequent g aggregate user`') unless args_valid?(aggregate_name)
|
49
52
|
|
50
53
|
Sequent::Generator::Aggregate.new(aggregate_name).execute
|
@@ -52,41 +55,45 @@ def generate_aggregate(args)
|
|
52
55
|
end
|
53
56
|
|
54
57
|
def generate_command(args)
|
55
|
-
|
56
|
-
aggregate_name =
|
57
|
-
command_name =
|
58
|
-
attrs =
|
58
|
+
arguments = args.dup
|
59
|
+
aggregate_name = arguments.shift
|
60
|
+
command_name = arguments.shift
|
61
|
+
attrs = arguments
|
59
62
|
|
60
|
-
|
63
|
+
unless args_valid?(aggregate_name, command_name)
|
64
|
+
abort('Please specify an aggregate name and command name. i.e. `sequent g command user AddUser`')
|
65
|
+
end
|
61
66
|
Sequent::Generator::Command.new(aggregate_name, command_name, attrs).execute
|
62
67
|
puts "#{command_name} command has been added to #{aggregate_name}"
|
63
68
|
end
|
64
69
|
|
65
70
|
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?(
|
71
|
+
arguments = args.dup
|
72
|
+
aggregate_name = arguments.shift
|
73
|
+
event_name = arguments.shift
|
74
|
+
attrs = arguments
|
75
|
+
|
76
|
+
abort('Please specify an aggregate name and event name. i.e. `sequent g event user AddUser`') unless args_valid?(
|
77
|
+
aggregate_name, event_name
|
78
|
+
)
|
72
79
|
Sequent::Generator::Event.new(aggregate_name, event_name, attrs).execute
|
73
80
|
puts "#{event_name} event has been added to #{aggregate_name}"
|
74
81
|
end
|
75
82
|
|
76
83
|
def generate(args)
|
77
|
-
|
78
|
-
entity =
|
84
|
+
arguments = args.dup
|
85
|
+
entity = arguments.shift
|
79
86
|
abort('Please specify a command. i.e. `sequent g aggregate user`') if entity.empty?
|
80
87
|
|
81
88
|
case entity
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
when 'aggregate'
|
90
|
+
generate_aggregate(arguments)
|
91
|
+
when 'command'
|
92
|
+
generate_command(arguments)
|
93
|
+
when 'event'
|
94
|
+
generate_event(arguments)
|
95
|
+
else
|
96
|
+
abort("Unknown argument #{entity} for `generate`. Try `sequent g aggregate user`")
|
90
97
|
end
|
91
98
|
end
|
92
99
|
|
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
|
@@ -55,6 +57,37 @@ module Sequent
|
|
55
57
|
load_aggregates([aggregate_id], clazz)[0]
|
56
58
|
end
|
57
59
|
|
60
|
+
# Optimised for loading lots of events and ignore snapshot events. To get the correct historical state of an
|
61
|
+
# AggregateRoot it is necessary to be able to ignore snapshots. For a nested AggregateRoot, there will not be a
|
62
|
+
# sequence number known, so a load_until timestamp can be used instead.
|
63
|
+
#
|
64
|
+
# +aggregate_id+ The id of the aggregate to be loaded
|
65
|
+
#
|
66
|
+
# +clazz+ Optional argument that checks if aggregate is of type +clazz+
|
67
|
+
#
|
68
|
+
# +load_until+ Optional argument that defines up until what point in time the AggregateRoot will be rebuilt.
|
69
|
+
def load_aggregate_for_snapshotting(aggregate_id, clazz = nil, load_until: nil)
|
70
|
+
fail ArgumentError, 'aggregate_id is required' if aggregate_id.blank?
|
71
|
+
|
72
|
+
stream = Sequent
|
73
|
+
.configuration
|
74
|
+
.event_store
|
75
|
+
.find_event_stream(aggregate_id)
|
76
|
+
aggregate = Class.const_get(stream.aggregate_type).stream_from_history(stream)
|
77
|
+
|
78
|
+
Sequent
|
79
|
+
.configuration
|
80
|
+
.event_store
|
81
|
+
.stream_events_for_aggregate(aggregate_id, load_until: load_until) do |event_stream|
|
82
|
+
aggregate.stream_from_history(event_stream)
|
83
|
+
end
|
84
|
+
|
85
|
+
if clazz
|
86
|
+
fail TypeError, "#{aggregate.class} is not a #{clazz}" unless aggregate.class <= clazz
|
87
|
+
end
|
88
|
+
aggregate
|
89
|
+
end
|
90
|
+
|
58
91
|
##
|
59
92
|
# Loads multiple aggregates at once.
|
60
93
|
# Returns the ones in the current Unit Of Work otherwise loads it from history.
|
@@ -69,30 +102,30 @@ module Sequent
|
|
69
102
|
# +aggregate_ids+ The ids of the aggregates to be loaded
|
70
103
|
# +clazz+ Optional argument that checks if all aggregates are of type +clazz+
|
71
104
|
def load_aggregates(aggregate_ids, clazz = nil)
|
72
|
-
fail ArgumentError
|
105
|
+
fail ArgumentError, 'aggregate_ids is required' unless aggregate_ids
|
73
106
|
return [] if aggregate_ids.empty?
|
74
107
|
|
75
|
-
|
76
|
-
|
77
|
-
|
108
|
+
unique_ids = aggregate_ids.uniq
|
109
|
+
result = aggregates.values_at(*unique_ids).compact
|
110
|
+
query_ids = unique_ids - result.map(&:id)
|
78
111
|
|
79
|
-
|
112
|
+
result += Sequent.configuration.event_store.load_events_for_aggregates(query_ids).map do |stream, events|
|
80
113
|
aggregate_class = Class.const_get(stream.aggregate_type)
|
81
114
|
aggregate_class.load_from_history(stream, events)
|
82
115
|
end
|
83
116
|
|
84
|
-
if
|
85
|
-
missing_aggregate_ids =
|
86
|
-
|
117
|
+
if result.count != unique_ids.count
|
118
|
+
missing_aggregate_ids = unique_ids - result.map(&:id)
|
119
|
+
fail AggregateNotFound, missing_aggregate_ids
|
87
120
|
end
|
88
121
|
|
89
122
|
if clazz
|
90
|
-
|
91
|
-
|
123
|
+
result.each do |aggregate|
|
124
|
+
fail TypeError, "#{aggregate.class} is not a #{clazz}" unless aggregate.class <= clazz
|
92
125
|
end
|
93
126
|
end
|
94
127
|
|
95
|
-
|
128
|
+
result.map do |aggregate|
|
96
129
|
aggregates[aggregate.id] = aggregate
|
97
130
|
end
|
98
131
|
end
|
@@ -119,8 +152,9 @@ module Sequent
|
|
119
152
|
def commit(command)
|
120
153
|
updated_aggregates = aggregates.values.reject { |x| x.uncommitted_events.empty? }
|
121
154
|
return if updated_aggregates.empty?
|
155
|
+
|
122
156
|
streams_with_events = updated_aggregates.map do |aggregate|
|
123
|
-
[
|
157
|
+
[aggregate.event_stream, aggregate.uncommitted_events]
|
124
158
|
end
|
125
159
|
updated_aggregates.each(&:clear_events)
|
126
160
|
store_events command, streams_with_events
|
@@ -136,6 +170,7 @@ module Sequent
|
|
136
170
|
# A +HasUncommittedEvents+ is raised when there are uncommitted_events in the Unit of Work.
|
137
171
|
def clear!
|
138
172
|
fail HasUncommittedEvents if aggregates.values.any? { |x| !x.uncommitted_events.empty? }
|
173
|
+
|
139
174
|
clear
|
140
175
|
end
|
141
176
|
|
@@ -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
|
@@ -70,6 +80,26 @@ module Sequent
|
|
70
80
|
events.each { |event| apply_event(event) }
|
71
81
|
end
|
72
82
|
|
83
|
+
def initialize_for_streaming(stream)
|
84
|
+
@uncommitted_events = []
|
85
|
+
@sequence_number = 1
|
86
|
+
@event_stream = stream
|
87
|
+
end
|
88
|
+
|
89
|
+
def stream_from_history(stream_events)
|
90
|
+
_stream, event = stream_events
|
91
|
+
fail 'Empty history' if event.blank?
|
92
|
+
|
93
|
+
@id ||= event.aggregate_id
|
94
|
+
apply_event(event)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.stream_from_history(stream)
|
98
|
+
aggregate_root = allocate
|
99
|
+
aggregate_root.initialize_for_streaming(stream)
|
100
|
+
aggregate_root
|
101
|
+
end
|
102
|
+
|
73
103
|
def to_s
|
74
104
|
"#{self.class.name}: #{@id}"
|
75
105
|
end
|
@@ -100,7 +130,7 @@ module Sequent
|
|
100
130
|
# apply InvoiceSentEvent, send_date: DateTime.now
|
101
131
|
# end
|
102
132
|
#
|
103
|
-
def apply(event, params={})
|
133
|
+
def apply(event, params = {})
|
104
134
|
event = build_event(event, params) if event.is_a?(Class)
|
105
135
|
apply_event(event)
|
106
136
|
@uncommitted_events << event
|
@@ -123,12 +153,11 @@ module Sequent
|
|
123
153
|
if args.empty?
|
124
154
|
apply event_class
|
125
155
|
elsif self.class
|
126
|
-
|
127
|
-
|
156
|
+
.event_attribute_keys(event_class)
|
157
|
+
.any? { |k| instance_variable_get(:"@#{k}") != args[k.to_sym] }
|
128
158
|
apply event_class, args
|
129
159
|
end
|
130
160
|
end
|
131
|
-
|
132
161
|
end
|
133
162
|
end
|
134
163
|
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
|