yes-core 1.0.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/lib/yes/core/active_job_serializers/command_group_serializer.rb +29 -0
- data/lib/yes/core/active_job_serializers/dry_struct_serializer.rb +57 -0
- data/lib/yes/core/aggregate/draftable.rb +205 -0
- data/lib/yes/core/aggregate/dsl/attribute_data.rb +37 -0
- data/lib/yes/core/aggregate/dsl/attribute_definer.rb +54 -0
- data/lib/yes/core/aggregate/dsl/attribute_definers/aggregate.rb +36 -0
- data/lib/yes/core/aggregate/dsl/attribute_definers/standard.rb +36 -0
- data/lib/yes/core/aggregate/dsl/class_name_convention.rb +80 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/authorizer.rb +132 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/base.rb +80 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/authorizer.rb +30 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/authorizer_factory.rb +34 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/base.rb +38 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/cerbos_authorizer.rb +114 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/command.rb +70 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/event.rb +88 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/guard_evaluator.rb +84 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/simple_authorizer.rb +50 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command/state_updater.rb +46 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/read_model.rb +75 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/read_model_filter.rb +88 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/read_model_serializer.rb +76 -0
- data/lib/yes/core/aggregate/dsl/command_data.rb +54 -0
- data/lib/yes/core/aggregate/dsl/command_definer.rb +263 -0
- data/lib/yes/core/aggregate/dsl/command_shortcut_expander.rb +233 -0
- data/lib/yes/core/aggregate/dsl/constant_resolver.rb +67 -0
- data/lib/yes/core/aggregate/dsl/method_definers/attribute/accessor.rb +28 -0
- data/lib/yes/core/aggregate/dsl/method_definers/attribute/aggregate_accessor.rb +36 -0
- data/lib/yes/core/aggregate/dsl/method_definers/attribute/base.rb +42 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command/base.rb +42 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command/can_command.rb +41 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command/command.rb +50 -0
- data/lib/yes/core/aggregate/has_authorizer.rb +86 -0
- data/lib/yes/core/aggregate/has_read_model.rb +169 -0
- data/lib/yes/core/aggregate/read_model_rebuilder.rb +40 -0
- data/lib/yes/core/aggregate/shared_read_model_rebuilder.rb +158 -0
- data/lib/yes/core/aggregate.rb +404 -0
- data/lib/yes/core/authentication_error.rb +8 -0
- data/lib/yes/core/authorization/cerbos_client_provider.rb +27 -0
- data/lib/yes/core/authorization/command_authorizer.rb +40 -0
- data/lib/yes/core/authorization/command_cerbos_authorizer.rb +182 -0
- data/lib/yes/core/authorization/read_model_authorizer.rb +22 -0
- data/lib/yes/core/authorization/read_models_authorizer.rb +49 -0
- data/lib/yes/core/authorization/read_request_authorizer.rb +32 -0
- data/lib/yes/core/authorization/read_request_cerbos_authorizer.rb +112 -0
- data/lib/yes/core/command.rb +35 -0
- data/lib/yes/core/command_handling/aggregate_tracker.rb +33 -0
- data/lib/yes/core/command_handling/command_executor.rb +171 -0
- data/lib/yes/core/command_handling/command_handler.rb +124 -0
- data/lib/yes/core/command_handling/event_publisher.rb +189 -0
- data/lib/yes/core/command_handling/guard_evaluator.rb +165 -0
- data/lib/yes/core/command_handling/guard_runner.rb +76 -0
- data/lib/yes/core/command_handling/payload_proxy.rb +159 -0
- data/lib/yes/core/command_handling/read_model_recovery_service.rb +264 -0
- data/lib/yes/core/command_handling/read_model_revision_guard.rb +198 -0
- data/lib/yes/core/command_handling/read_model_updater.rb +103 -0
- data/lib/yes/core/command_handling/state_updater.rb +113 -0
- data/lib/yes/core/commands/bus.rb +46 -0
- data/lib/yes/core/commands/group.rb +135 -0
- data/lib/yes/core/commands/group_response.rb +13 -0
- data/lib/yes/core/commands/helper.rb +126 -0
- data/lib/yes/core/commands/notifier.rb +65 -0
- data/lib/yes/core/commands/processor.rb +137 -0
- data/lib/yes/core/commands/response.rb +63 -0
- data/lib/yes/core/commands/stateless/group_handler.rb +186 -0
- data/lib/yes/core/commands/stateless/group_response.rb +15 -0
- data/lib/yes/core/commands/stateless/handler.rb +292 -0
- data/lib/yes/core/commands/stateless/handler_helpers.rb +321 -0
- data/lib/yes/core/commands/stateless/response.rb +14 -0
- data/lib/yes/core/commands/stateless/subject.rb +41 -0
- data/lib/yes/core/commands/validator.rb +28 -0
- data/lib/yes/core/configuration.rb +432 -0
- data/lib/yes/core/data_decryptor.rb +59 -0
- data/lib/yes/core/data_encryptor.rb +60 -0
- data/lib/yes/core/encryption_metadata.rb +33 -0
- data/lib/yes/core/error.rb +14 -0
- data/lib/yes/core/error_messages.rb +37 -0
- data/lib/yes/core/event.rb +222 -0
- data/lib/yes/core/event_class_resolver.rb +40 -0
- data/lib/yes/core/generators/read_models/add_pending_update_tracking_generator.rb +43 -0
- data/lib/yes/core/generators/read_models/templates/add_pending_update_tracking.rb.erb +122 -0
- data/lib/yes/core/generators/read_models/templates/migration.rb.erb +9 -0
- data/lib/yes/core/generators/read_models/update_generator.rb +147 -0
- data/lib/yes/core/jobs/read_model_recovery_job.rb +219 -0
- data/lib/yes/core/middlewares/encryptor.rb +48 -0
- data/lib/yes/core/middlewares/timestamp.rb +29 -0
- data/lib/yes/core/middlewares/with_indifferent_access.rb +22 -0
- data/lib/yes/core/models/application_record.rb +9 -0
- data/lib/yes/core/open_telemetry/otl_span.rb +110 -0
- data/lib/yes/core/open_telemetry/trackable.rb +101 -0
- data/lib/yes/core/payload_store/base.rb +33 -0
- data/lib/yes/core/payload_store/errors.rb +13 -0
- data/lib/yes/core/payload_store/lookup.rb +44 -0
- data/lib/yes/core/process_managers/access_token_client.rb +107 -0
- data/lib/yes/core/process_managers/base.rb +40 -0
- data/lib/yes/core/process_managers/command_runner.rb +109 -0
- data/lib/yes/core/process_managers/service_client.rb +57 -0
- data/lib/yes/core/process_managers/state.rb +118 -0
- data/lib/yes/core/railtie.rb +58 -0
- data/lib/yes/core/read_model/builder.rb +267 -0
- data/lib/yes/core/read_model/event_handler.rb +64 -0
- data/lib/yes/core/read_model/filter.rb +118 -0
- data/lib/yes/core/read_model/filter_query_builder.rb +104 -0
- data/lib/yes/core/serializer.rb +21 -0
- data/lib/yes/core/subscriptions.rb +94 -0
- data/lib/yes/core/test_support/event_helpers.rb +27 -0
- data/lib/yes/core/test_support/jwt_helpers.rb +30 -0
- data/lib/yes/core/test_support/subscriptions_helper.rb +88 -0
- data/lib/yes/core/test_support/test_helper.rb +27 -0
- data/lib/yes/core/test_support.rb +5 -0
- data/lib/yes/core/transaction_details.rb +90 -0
- data/lib/yes/core/type_lookup.rb +88 -0
- data/lib/yes/core/types.rb +110 -0
- data/lib/yes/core/utils/aggregate_shortcuts.rb +164 -0
- data/lib/yes/core/utils/caller_utils.rb +37 -0
- data/lib/yes/core/utils/command_utils.rb +226 -0
- data/lib/yes/core/utils/error_notifier.rb +101 -0
- data/lib/yes/core/utils/event_name_resolver.rb +67 -0
- data/lib/yes/core/utils/exponential_retrier.rb +180 -0
- data/lib/yes/core/utils/hash_utils.rb +63 -0
- data/lib/yes/core/version.rb +7 -0
- data/lib/yes/core.rb +85 -0
- data/lib/yes.rb +0 -0
- metadata +324 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
class Aggregate
|
|
6
|
+
# Provides read model functionality for aggregates
|
|
7
|
+
#
|
|
8
|
+
# @example Include in an aggregate
|
|
9
|
+
# class UserAggregate < Yes::Core::Aggregate
|
|
10
|
+
# include Yes::Core::Concerns::HasReadModel
|
|
11
|
+
# end
|
|
12
|
+
module HasReadModel
|
|
13
|
+
extend ActiveSupport::Concern
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
class << self
|
|
17
|
+
attr_accessor :_read_model_name, :_read_model_public, :_read_model_class, :_read_model_filter_class,
|
|
18
|
+
:_read_model_serializer_class, :_read_model_enabled
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Default to enabled
|
|
22
|
+
self._read_model_enabled = true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class_methods do
|
|
26
|
+
# Returns the read model class associated with the current aggregate.
|
|
27
|
+
# The class is resolved using ReadModelClassResolver, which either uses an explicitly set
|
|
28
|
+
# class name or derives it from the current namespace.
|
|
29
|
+
#
|
|
30
|
+
# @return [Class] The read model class
|
|
31
|
+
# @raise [NameError] If the read model class cannot be found
|
|
32
|
+
def read_model_class
|
|
33
|
+
_read_model_class
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Sets up all read model related classes for the aggregate
|
|
37
|
+
#
|
|
38
|
+
# @return [void]
|
|
39
|
+
# @note This method initializes three components:
|
|
40
|
+
# - The read model class itself
|
|
41
|
+
# - The read model filter class for querying
|
|
42
|
+
# - The read model serializer class for data transformation
|
|
43
|
+
def setup_read_model_classes
|
|
44
|
+
setup_read_model
|
|
45
|
+
setup_read_model_filter
|
|
46
|
+
setup_read_model_serializer
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [String] The name of the read model
|
|
50
|
+
def read_model_name
|
|
51
|
+
self._read_model_name ||= "#{context}_#{aggregate}".underscore
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Sets the read model configuration for this aggregate
|
|
55
|
+
# @param name [String, false] The name for the read model, or false to disable read models
|
|
56
|
+
# @param public [Boolean] Whether the read model should be public via read API
|
|
57
|
+
def read_model(name, public: true)
|
|
58
|
+
if name == false
|
|
59
|
+
self._read_model_enabled = false
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
self._read_model_name = name.to_s.underscore
|
|
64
|
+
self._read_model_public = public
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Boolean] Whether the read model is public
|
|
68
|
+
def read_model_public?
|
|
69
|
+
_read_model_public.nil? || _read_model_public
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Boolean] Whether the read model is enabled
|
|
73
|
+
def read_model_enabled?
|
|
74
|
+
_read_model_enabled != false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def setup_read_model
|
|
80
|
+
self._read_model_class = resolve_read_model_class
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def setup_read_model_filter
|
|
84
|
+
self._read_model_filter_class = resolve_read_model_filter_class
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def setup_read_model_serializer
|
|
88
|
+
self._read_model_serializer_class = resolve_read_model_serializer_class
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def resolve_read_model_class
|
|
92
|
+
Yes::Core::Aggregate::Dsl::ClassResolvers::ReadModel.new(read_model_name, context, aggregate).call
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def resolve_read_model_filter_class
|
|
96
|
+
Yes::Core::Aggregate::Dsl::ClassResolvers::ReadModelFilter.new(read_model_name, context, aggregate).call
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def resolve_read_model_serializer_class
|
|
100
|
+
Yes::Core::Aggregate::Dsl::ClassResolvers::ReadModelSerializer.new(
|
|
101
|
+
read_model_name, context, aggregate, attributes.keys
|
|
102
|
+
).call
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Updates or creates a read model with the given attributes
|
|
107
|
+
#
|
|
108
|
+
# @param attributes [Hash] The attributes to update the read model with
|
|
109
|
+
# @return [Boolean] Returns true if the record is saved successfully
|
|
110
|
+
# @raise [ActiveRecord::RecordInvalid] If the record is invalid
|
|
111
|
+
def update_read_model(attributes)
|
|
112
|
+
locale = attributes.delete(:locale) || I18n.locale
|
|
113
|
+
I18n.with_locale(locale) do
|
|
114
|
+
read_model.update!(attributes)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Retrieves or creates a read model instance for this aggregate
|
|
119
|
+
#
|
|
120
|
+
# @return [ApplicationRecord, nil] The read model instance associated with this aggregate's ID, or nil if disabled
|
|
121
|
+
# @example
|
|
122
|
+
# user_aggregate = UserAggregate.new(1)
|
|
123
|
+
# user_aggregate.read_model #=> #<User id: 1>
|
|
124
|
+
def read_model
|
|
125
|
+
return nil unless self.class.read_model_enabled?
|
|
126
|
+
|
|
127
|
+
@read_model ||= self.class.read_model_class.find_or_create_by(id:)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Removes the read model instance for this aggregate
|
|
131
|
+
def remove_read_model
|
|
132
|
+
read_model.destroy
|
|
133
|
+
@read_model = nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Rebuilds the read model by processing all events
|
|
137
|
+
# @param remove [Boolean] Whether to remove the read model before rebuilding
|
|
138
|
+
# @return [void]
|
|
139
|
+
def rebuild_read_model(remove: true)
|
|
140
|
+
Yes::Core::Aggregate::ReadModelRebuilder.new(self).call(remove:)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def revision_column
|
|
144
|
+
return :revision unless self.class.read_model_enabled?
|
|
145
|
+
|
|
146
|
+
aggregate_revision_column = "#{self.class.context.underscore}_#{self.class.aggregate.underscore}_revision"
|
|
147
|
+
return aggregate_revision_column.to_sym if read_model.class.column_names.include?(aggregate_revision_column)
|
|
148
|
+
|
|
149
|
+
:revision
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns the current revision number from the read model
|
|
153
|
+
# @return [Integer, nil] The revision number stored in the read model, or -1 if read models are disabled
|
|
154
|
+
def revision
|
|
155
|
+
return -1 unless self.class.read_model_enabled?
|
|
156
|
+
|
|
157
|
+
read_model.send(revision_column)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Initializes the read model's revision column with the current event stream revision
|
|
161
|
+
# @return [Boolean] True if the update was successful
|
|
162
|
+
# @note This method bypasses validations and callbacks by using update_column
|
|
163
|
+
def init_revision_from_stream
|
|
164
|
+
read_model.update_column(revision_column, event_revision)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
class Aggregate
|
|
6
|
+
# Handles rebuilding of read models for aggregates
|
|
7
|
+
#
|
|
8
|
+
# @example Using the rebuilder
|
|
9
|
+
# rebuilder = ReadModelRebuilder.new(aggregate)
|
|
10
|
+
# rebuilder.call
|
|
11
|
+
class ReadModelRebuilder
|
|
12
|
+
attr_reader :aggregate
|
|
13
|
+
private :aggregate
|
|
14
|
+
|
|
15
|
+
delegate :events, :remove_read_model, :read_model, :revision_column, to: :aggregate
|
|
16
|
+
|
|
17
|
+
# @param aggregate [Yes::Core::Aggregate] The aggregate whose read model needs rebuilding
|
|
18
|
+
def initialize(aggregate)
|
|
19
|
+
@aggregate = aggregate
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Rebuilds the read model by processing all events
|
|
23
|
+
# @param remove [Boolean] Whether to remove the read model before rebuilding
|
|
24
|
+
# @return [void]
|
|
25
|
+
def call(remove: true)
|
|
26
|
+
remove ? remove_read_model : read_model.update(revision_column => -1)
|
|
27
|
+
events.each { |events_page| events_page.each { |event| process_event(event) } }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# @param event [Object] The event to process for read model rebuilding
|
|
33
|
+
# @return [void]
|
|
34
|
+
def process_event(event)
|
|
35
|
+
CommandHandling::ReadModelUpdater.new(aggregate).call(event, nil, resolve_payload: true)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yes
|
|
4
|
+
module Core
|
|
5
|
+
class Aggregate
|
|
6
|
+
# Handles rebuilding of read models that are shared by multiple aggregates
|
|
7
|
+
#
|
|
8
|
+
# @example Using the shared rebuilder
|
|
9
|
+
# rebuilder = SharedReadModelRebuilder.new(SharedUserReadModel)
|
|
10
|
+
# rebuilder.call
|
|
11
|
+
class SharedReadModelRebuilder
|
|
12
|
+
# Value object to hold event data with aggregate information
|
|
13
|
+
EventWithAggregate = Struct.new(:event, :aggregate, keyword_init: true) do
|
|
14
|
+
# @return [Time] The creation timestamp of the event
|
|
15
|
+
delegate :created_at, to: :event
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param read_model_class [Class] The Active Record read model class to rebuild
|
|
19
|
+
# @param ids [Array<String>] Array of IDs to rebuild
|
|
20
|
+
# @example
|
|
21
|
+
# rebuilder = SharedReadModelRebuilder.new(SharedUserProfile, ['user-1', 'user-2'])
|
|
22
|
+
# rebuilder.call
|
|
23
|
+
def initialize(read_model_class, ids)
|
|
24
|
+
@read_model_class = read_model_class
|
|
25
|
+
@ids = ids
|
|
26
|
+
@aggregate_types = find_aggregates_using_read_model
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Rebuilds the shared read model by processing all events from all aggregates
|
|
30
|
+
# @return [void]
|
|
31
|
+
def call
|
|
32
|
+
ids.each do |id|
|
|
33
|
+
rebuild_read_model_for_id(id)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# @return [Class] The read model class being rebuilt
|
|
40
|
+
# @return [Array<String>] The IDs to rebuild
|
|
41
|
+
# @return [Array<Array<String>>] The aggregate types that use this read model
|
|
42
|
+
attr_reader :read_model_class, :ids, :aggregate_types
|
|
43
|
+
|
|
44
|
+
# Finds all aggregates that use the given read model class
|
|
45
|
+
# @return [Array<Array<String>>] Array of [context_name, aggregate_name] pairs
|
|
46
|
+
def find_aggregates_using_read_model
|
|
47
|
+
aggregates = []
|
|
48
|
+
|
|
49
|
+
Yes::Core.configuration.list_all_registered_classes.each do |key, classes|
|
|
50
|
+
next unless classes[:read_model] == read_model_class
|
|
51
|
+
|
|
52
|
+
context_name, aggregate_name = key
|
|
53
|
+
aggregates << [context_name, aggregate_name]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
aggregates
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Rebuilds the read model for a single ID
|
|
60
|
+
# @param id [String] The ID to rebuild
|
|
61
|
+
# @return [void]
|
|
62
|
+
def rebuild_read_model_for_id(id)
|
|
63
|
+
remove_read_model_for_id(id)
|
|
64
|
+
|
|
65
|
+
aggregates = instantiate_aggregates_for_id(id)
|
|
66
|
+
events_with_aggregates = collect_events_from_aggregates(aggregates)
|
|
67
|
+
sorted_events = events_with_aggregates.sort_by(&:created_at)
|
|
68
|
+
|
|
69
|
+
sorted_events.each do |event_with_aggregate|
|
|
70
|
+
process_event_with_aggregate(event_with_aggregate)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Removes the read model instance for a specific ID
|
|
75
|
+
# @param id [String] The ID to remove
|
|
76
|
+
# @return [void]
|
|
77
|
+
def remove_read_model_for_id(id)
|
|
78
|
+
read_model_class.find_by(id: id)&.destroy
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Instantiates all aggregate instances for a given ID
|
|
82
|
+
# @param id [String] The ID to instantiate aggregates for
|
|
83
|
+
# @return [Array<Aggregate>] The instantiated aggregates
|
|
84
|
+
def instantiate_aggregates_for_id(id)
|
|
85
|
+
aggregate_types.map do |context_name, aggregate_name|
|
|
86
|
+
aggregate_class = build_aggregate_class(context_name, aggregate_name)
|
|
87
|
+
aggregate_class.new(id)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Collects all events from the given aggregates
|
|
92
|
+
# @param aggregates [Array<Aggregate>] The aggregates to collect events from
|
|
93
|
+
# @return [Array<EventWithAggregate>] All events with their aggregate references
|
|
94
|
+
def collect_events_from_aggregates(aggregates)
|
|
95
|
+
events_with_aggregates = []
|
|
96
|
+
|
|
97
|
+
aggregates.each do |aggregate|
|
|
98
|
+
aggregate.events.each do |events_page|
|
|
99
|
+
events_page.each do |event|
|
|
100
|
+
events_with_aggregates << EventWithAggregate.new(
|
|
101
|
+
event: event,
|
|
102
|
+
aggregate: aggregate
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
events_with_aggregates
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Builds the aggregate class from context and aggregate names
|
|
112
|
+
# @param context_name [String] The context name
|
|
113
|
+
# @param aggregate_name [String] The aggregate name
|
|
114
|
+
# @return [Class] The aggregate class
|
|
115
|
+
def build_aggregate_class(context_name, aggregate_name)
|
|
116
|
+
"#{context_name}::#{aggregate_name}::Aggregate".constantize
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Processes a single event with its aggregate information
|
|
120
|
+
# @param event_with_aggregate [EventWithAggregate] The event with aggregate data
|
|
121
|
+
# @return [void]
|
|
122
|
+
def process_event_with_aggregate(event_with_aggregate)
|
|
123
|
+
aggregate = event_with_aggregate.aggregate
|
|
124
|
+
command_utilities = aggregate.send(:command_utilities)
|
|
125
|
+
|
|
126
|
+
command_name = command_utilities.command_name_from_event(
|
|
127
|
+
event_with_aggregate.event,
|
|
128
|
+
aggregate.class
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
payload = event_with_aggregate.event.data
|
|
132
|
+
locale = payload.delete(:locale)
|
|
133
|
+
|
|
134
|
+
state_updater = command_utilities.fetch_state_updater_class(command_name).new(
|
|
135
|
+
payload: payload,
|
|
136
|
+
aggregate: aggregate
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Determine which revision column to use
|
|
140
|
+
context_revision_column =
|
|
141
|
+
"#{aggregate.class.context.underscore}_#{aggregate.class.aggregate.underscore}_revision"
|
|
142
|
+
revision_column = if aggregate.read_model.class.column_names.include?(context_revision_column)
|
|
143
|
+
context_revision_column.to_sym
|
|
144
|
+
else
|
|
145
|
+
:revision
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
aggregate.update_read_model(
|
|
149
|
+
state_updater.call.merge(
|
|
150
|
+
revision_column => event_with_aggregate.event.stream_revision,
|
|
151
|
+
locale: locale
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|