sequent 3.3.1 → 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 +6 -0
- data/lib/sequent/application_record.rb +2 -0
- data/lib/sequent/configuration.rb +29 -29
- data/lib/sequent/core/aggregate_repository.rb +24 -14
- 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 +30 -11
- data/lib/sequent/core/command_record.rb +12 -4
- data/lib/sequent/core/command_service.rb +41 -25
- 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 +20 -15
- data/lib/sequent/core/event_record.rb +7 -7
- data/lib/sequent/core/event_store.rb +75 -49
- 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 +64 -33
- 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 -4
- 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 +7 -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 +6 -0
- data/lib/sequent/generator/project.rb +3 -1
- data/lib/sequent/generator/template_project/Gemfile +1 -1
- data/lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb +1 -1
- data/lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb +1 -1
- data/lib/sequent/generator.rb +3 -4
- data/lib/sequent/migrations/executor.rb +30 -9
- 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 +93 -44
- data/lib/sequent/rake/migration_tasks.rb +59 -23
- data/lib/sequent/rake/tasks.rb +5 -2
- data/lib/sequent/sequent.rb +6 -1
- data/lib/sequent/support/database.rb +39 -17
- 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 +39 -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 +194 -0
- data/lib/sequent/util/printer.rb +6 -5
- data/lib/sequent/util/skip_if_already_processing.rb +21 -5
- data/lib/sequent/util/timer.rb +2 -0
- data/lib/sequent/util/util.rb +3 -0
- data/lib/sequent.rb +4 -0
- data/lib/version.rb +3 -1
- metadata +110 -59
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
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is for any notices such as deprecation warnings, which should appear
|
4
|
+
# in the logs during app boot. Adding such warnings in other places causes
|
5
|
+
# lots of noise with duplicated messages, whereas this file is only
|
6
|
+
# run once.
|
@@ -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,15 +9,15 @@ 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
|
|
14
15
|
DEFAULT_MIGRATION_SQL_FILES_DIRECTORY = 'db/tables'
|
15
16
|
DEFAULT_DATABASE_CONFIG_DIRECTORY = 'db'
|
17
|
+
DEFAULT_DATABASE_SCHEMA_DIRECTORY = 'db'
|
16
18
|
|
17
19
|
DEFAULT_VIEW_SCHEMA_NAME = 'view_schema'
|
18
|
-
DEFAULT_EVENT_STORE_SCHEMA_NAME= 'sequent_schema'
|
20
|
+
DEFAULT_EVENT_STORE_SCHEMA_NAME = 'sequent_schema'
|
19
21
|
|
20
22
|
MIGRATIONS_CLASS_NAME = 'Sequent::Migrations::Projectors'
|
21
23
|
|
@@ -28,39 +30,33 @@ module Sequent
|
|
28
30
|
|
29
31
|
DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS = false
|
30
32
|
|
31
|
-
|
33
|
+
DEFAULT_ERROR_LOCALE_RESOLVER = -> { I18n.locale || :en }
|
32
34
|
|
33
|
-
attr_accessor :
|
35
|
+
attr_accessor :aggregate_repository,
|
36
|
+
:event_store,
|
34
37
|
:command_service,
|
35
38
|
:event_record_class,
|
36
39
|
:stream_record_class,
|
37
40
|
:snapshot_event_class,
|
38
41
|
:transaction_provider,
|
39
|
-
:event_publisher
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
attr_accessor :disable_event_handlers
|
51
|
-
|
52
|
-
attr_accessor :logger
|
53
|
-
|
54
|
-
|
55
|
-
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,
|
56
52
|
:view_schema_name,
|
57
53
|
:offline_replay_persistor_class,
|
58
54
|
:online_replay_persistor_class,
|
59
55
|
:number_of_replay_processes,
|
60
56
|
:database_config_directory,
|
61
|
-
:
|
62
|
-
|
63
|
-
|
57
|
+
:database_schema_directory,
|
58
|
+
:event_store_schema_name,
|
59
|
+
:strict_check_attributes_on_apply_events
|
64
60
|
|
65
61
|
attr_reader :migrations_class_name,
|
66
62
|
:versions_table_name,
|
@@ -106,20 +102,22 @@ module Sequent
|
|
106
102
|
self.offline_replay_persistor_class = DEFAULT_OFFLINE_REPLAY_PERSISTOR_CLASS
|
107
103
|
self.online_replay_persistor_class = DEFAULT_ONLINE_REPLAY_PERSISTOR_CLASS
|
108
104
|
self.database_config_directory = DEFAULT_DATABASE_CONFIG_DIRECTORY
|
105
|
+
self.database_schema_directory = DEFAULT_DATABASE_SCHEMA_DIRECTORY
|
109
106
|
self.strict_check_attributes_on_apply_events = DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS
|
110
107
|
|
111
|
-
self.logger = Logger.new(STDOUT).tap {|l| l.level = Logger::INFO }
|
108
|
+
self.logger = Logger.new(STDOUT).tap { |l| l.level = Logger::INFO }
|
109
|
+
self.error_locale_resolver = DEFAULT_ERROR_LOCALE_RESOLVER
|
112
110
|
end
|
113
111
|
|
114
112
|
def replayed_ids_table_name=(table_name)
|
115
|
-
fail ArgumentError
|
113
|
+
fail ArgumentError, 'table_name can not be nil' unless table_name
|
116
114
|
|
117
115
|
@replayed_ids_table_name = table_name
|
118
116
|
Sequent::Migrations::ViewSchema::ReplayedIds.table_name = table_name
|
119
117
|
end
|
120
118
|
|
121
119
|
def versions_table_name=(table_name)
|
122
|
-
fail ArgumentError
|
120
|
+
fail ArgumentError, 'table_name can not be nil' unless table_name
|
123
121
|
|
124
122
|
@versions_table_name = table_name
|
125
123
|
Sequent::Migrations::ViewSchema::Versions.table_name = table_name
|
@@ -127,9 +125,11 @@ module Sequent
|
|
127
125
|
|
128
126
|
def migrations_class_name=(class_name)
|
129
127
|
migration_class = Class.const_get(class_name)
|
130
|
-
|
128
|
+
unless migration_class <= Sequent::Migrations::Projectors
|
129
|
+
fail ArgumentError, "#{migration_class} must extend Sequent::Migrations::Projectors"
|
130
|
+
end
|
131
|
+
|
131
132
|
@migrations_class_name = class_name
|
132
133
|
end
|
133
|
-
|
134
134
|
end
|
135
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
|
@@ -100,11 +102,17 @@ module Sequent
|
|
100
102
|
##
|
101
103
|
# Returns whether the event store has an aggregate with the given id
|
102
104
|
def contains_aggregate?(aggregate_id)
|
103
|
-
Sequent.configuration.event_store.stream_exists?(aggregate_id)
|
105
|
+
Sequent.configuration.event_store.stream_exists?(aggregate_id) &&
|
106
|
+
Sequent.configuration.event_store.events_exists?(aggregate_id)
|
104
107
|
end
|
105
108
|
|
106
109
|
# Gets all uncommitted_events from the 'registered' aggregates
|
107
110
|
# and stores them in the event store.
|
111
|
+
#
|
112
|
+
# The events given to the EventStore are ordered in loading order
|
113
|
+
# of the different AggregateRoot's. So Events are stored
|
114
|
+
# (and therefore published) in order in which they are `apply`-ed per AggregateRoot.
|
115
|
+
#
|
108
116
|
# The command is 'attached' for traceability purpose so we can see
|
109
117
|
# which command resulted in which events.
|
110
118
|
#
|
@@ -113,8 +121,9 @@ module Sequent
|
|
113
121
|
def commit(command)
|
114
122
|
updated_aggregates = aggregates.values.reject { |x| x.uncommitted_events.empty? }
|
115
123
|
return if updated_aggregates.empty?
|
124
|
+
|
116
125
|
streams_with_events = updated_aggregates.map do |aggregate|
|
117
|
-
[
|
126
|
+
[aggregate.event_stream, aggregate.uncommitted_events]
|
118
127
|
end
|
119
128
|
updated_aggregates.each(&:clear_events)
|
120
129
|
store_events command, streams_with_events
|
@@ -130,6 +139,7 @@ module Sequent
|
|
130
139
|
# A +HasUncommittedEvents+ is raised when there are uncommitted_events in the Unit of Work.
|
131
140
|
def clear!
|
132
141
|
fail HasUncommittedEvents if aggregates.values.any? { |x| !x.uncommitted_events.empty? }
|
142
|
+
|
133
143
|
clear
|
134
144
|
end
|
135
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'
|
@@ -7,15 +9,23 @@ require_relative 'helpers/mergable'
|
|
7
9
|
|
8
10
|
module Sequent
|
9
11
|
module Core
|
10
|
-
#
|
12
|
+
#
|
13
|
+
# Base class for all Command's.
|
14
|
+
#
|
15
|
+
# Commands form the API of your domain. They are
|
16
|
+
# simple data objects with descriptive names
|
17
|
+
# of what they want to achieve. E.g. `SendInvoice`.
|
18
|
+
#
|
19
|
+
# BaseCommand uses `ActiveModel::Validations` for
|
20
|
+
# validations
|
11
21
|
class BaseCommand
|
12
|
-
include
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
19
29
|
include ActiveModel::Validations::Callbacks
|
20
30
|
include Sequent::Core::Helpers::TypeConversionSupport
|
21
31
|
|
@@ -27,6 +37,7 @@ module Sequent
|
|
27
37
|
end
|
28
38
|
|
29
39
|
def self.inherited(subclass)
|
40
|
+
super
|
30
41
|
Commands << subclass
|
31
42
|
end
|
32
43
|
end
|
@@ -36,10 +47,17 @@ module Sequent
|
|
36
47
|
included do
|
37
48
|
attrs sequence_number: Integer
|
38
49
|
validates_presence_of :sequence_number
|
39
|
-
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
|
40
55
|
end
|
41
56
|
end
|
42
57
|
|
58
|
+
#
|
59
|
+
# Utility class containing all subclasses of BaseCommand
|
60
|
+
#
|
43
61
|
class Commands
|
44
62
|
class << self
|
45
63
|
def commands
|
@@ -60,7 +78,7 @@ module Sequent
|
|
60
78
|
end
|
61
79
|
end
|
62
80
|
|
63
|
-
# Most commonly used
|
81
|
+
# Most commonly used Command
|
64
82
|
# Command can be instantiated just by using:
|
65
83
|
#
|
66
84
|
# Command.new(aggregate_id: "1", user_id: "joe")
|
@@ -74,7 +92,8 @@ module Sequent
|
|
74
92
|
attrs aggregate_id: String, user_id: String, event_aggregate_id: String, event_sequence_number: Integer
|
75
93
|
|
76
94
|
def initialize(args = {})
|
77
|
-
|
95
|
+
fail ArgumentError, 'Missing aggregate_id' if args[:aggregate_id].nil?
|
96
|
+
|
78
97
|
super
|
79
98
|
end
|
80
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,14 +39,17 @@ 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
|
|
42
46
|
validates_presence_of :command_type, :command_json
|
43
47
|
|
44
48
|
def parent
|
45
|
-
EventRecord
|
49
|
+
EventRecord
|
50
|
+
.where(aggregate_id: event_aggregate_id, sequence_number: event_sequence_number)
|
51
|
+
.where('event_type != ?', Sequent::Core::SnapshotEvent.name)
|
52
|
+
.first
|
46
53
|
end
|
47
54
|
|
48
55
|
def children
|
@@ -55,6 +62,7 @@ module Sequent
|
|
55
62
|
|
56
63
|
def find_origin(record)
|
57
64
|
return find_origin(record.parent) if record.parent.present?
|
65
|
+
|
58
66
|
record
|
59
67
|
end
|
60
68
|
end
|