sequent 6.0.1 → 7.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/db/sequent_schema.rb +5 -0
  3. data/lib/sequent/configuration.rb +64 -13
  4. data/lib/sequent/core/aggregate_repository.rb +2 -2
  5. data/lib/sequent/core/aggregate_snapshotter.rb +4 -0
  6. data/lib/sequent/core/base_command_handler.rb +5 -0
  7. data/lib/sequent/core/core.rb +1 -1
  8. data/lib/sequent/core/event.rb +2 -2
  9. data/lib/sequent/core/event_record.rb +1 -0
  10. data/lib/sequent/core/event_store.rb +20 -16
  11. data/lib/sequent/core/helpers/attribute_support.rb +7 -7
  12. data/lib/sequent/core/helpers/message_handler.rb +10 -11
  13. data/lib/sequent/core/helpers/message_router.rb +13 -7
  14. data/lib/sequent/core/persistors/active_record_persistor.rb +4 -0
  15. data/lib/sequent/core/persistors/persistor.rb +5 -0
  16. data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +140 -133
  17. data/lib/sequent/core/projector.rb +4 -0
  18. data/lib/sequent/core/transactions/active_record_transaction_provider.rb +2 -1
  19. data/lib/sequent/core/workflow.rb +4 -0
  20. data/lib/sequent/dry_run/dry_run.rb +4 -0
  21. data/lib/sequent/dry_run/read_only_replay_optimized_postgres_persistor.rb +26 -0
  22. data/lib/sequent/dry_run/view_schema.rb +36 -0
  23. data/lib/sequent/generator/template_project/db/sequent_schema.rb +1 -0
  24. data/lib/sequent/migrations/errors.rb +12 -0
  25. data/lib/sequent/migrations/migrations.rb +0 -1
  26. data/lib/sequent/migrations/planner.rb +11 -7
  27. data/lib/sequent/migrations/versions.rb +82 -0
  28. data/lib/sequent/migrations/view_schema.rb +101 -58
  29. data/lib/sequent/rake/migration_tasks.rb +89 -6
  30. data/lib/sequent/sequent.rb +4 -11
  31. data/lib/sequent/support/database.rb +3 -11
  32. data/lib/sequent/support.rb +0 -2
  33. data/lib/sequent/util/util.rb +1 -0
  34. data/lib/sequent/util/web/clear_cache.rb +19 -0
  35. data/lib/sequent.rb +1 -0
  36. data/lib/version.rb +1 -1
  37. metadata +20 -30
  38. data/lib/sequent/core/helpers/message_dispatcher.rb +0 -20
  39. data/lib/sequent/migrations/migrate_events.rb +0 -67
  40. data/lib/sequent/support/view_projection.rb +0 -61
  41. data/lib/sequent/support/view_schema.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e420a9289f17d8552364afd49977cdb8cdbc0b37f39445a0a7ce310f9c1a180f
4
- data.tar.gz: 116144ee36558de52bc381b8e99e13d44aad3bedda6d531b380d93b3d7b96445
3
+ metadata.gz: 31a7eb29122b5706014155cacea33cc9064991eca03762d6c9eb0a300e4d3fe5
4
+ data.tar.gz: 4724c03d49b69fd01d111a53961936c3b1a6adba91bacb9af0abd97c4337d49a
5
5
  SHA512:
6
- metadata.gz: 434eb99e05dda79479a8c8a6529a0d2b3bfa2fc3e02c7143ad314f27a2140d33c6a01f1d3d1a147892b1dafefcd899bed8bde9cbe3b0c76520f3561b5dfa2b05
7
- data.tar.gz: 6fd8a9df8cac32aaad084fd2b472caab478c13840da563b8d3a8e8c9e1d86c32bfc3d43a7e8d7031386a36a127c9be6648d3baaa241dcfb45d92f3d7721a9d4d
6
+ metadata.gz: 6c13866f01dde089ae6cd95ca17fbbfd594209751300f429b0ef4f3680d19f1a468bf2675b24536b6e09150f80471afdab5a663be79fd45692fe758c47dd8f26
7
+ data.tar.gz: 96d19ae22452eb86ed09fda426e6cb56aad6b47be5589004a8ba0e24623bd8316297d8b29829210fafb57e75b251797007bb9b610d27850809ce4b1c661adcde
data/db/sequent_schema.rb CHANGED
@@ -8,8 +8,12 @@ ActiveRecord::Schema.define do
8
8
  t.text "event_json", :null => false
9
9
  t.integer "command_record_id", :null => false
10
10
  t.integer "stream_record_id", :null => false
11
+ t.bigint "xact_id"
11
12
  end
12
13
 
14
+ execute %Q{
15
+ ALTER TABLE event_records ALTER COLUMN xact_id SET DEFAULT pg_current_xact_id()::text::bigint
16
+ }
13
17
  execute %Q{
14
18
  CREATE UNIQUE INDEX unique_event_per_aggregate ON event_records (
15
19
  aggregate_id,
@@ -24,6 +28,7 @@ CREATE INDEX snapshot_events ON event_records (aggregate_id, sequence_number DES
24
28
  add_index "event_records", ["command_record_id"], :name => "index_event_records_on_command_record_id"
25
29
  add_index "event_records", ["event_type"], :name => "index_event_records_on_event_type"
26
30
  add_index "event_records", ["created_at"], :name => "index_event_records_on_created_at"
31
+ add_index "event_records", ["xact_id"], :name => "index_event_records_on_xact_id"
27
32
 
28
33
  create_table "command_records", :force => true do |t|
29
34
  t.string "user_id"
@@ -10,7 +10,6 @@ require 'logger'
10
10
  module Sequent
11
11
  class Configuration
12
12
  DEFAULT_VERSIONS_TABLE_NAME = 'sequent_versions'
13
- DEFAULT_REPLAYED_IDS_TABLE_NAME = 'sequent_replayed_ids'
14
13
 
15
14
  DEFAULT_MIGRATION_SQL_FILES_DIRECTORY = 'db/tables'
16
15
  DEFAULT_DATABASE_CONFIG_DIRECTORY = 'db'
@@ -36,6 +35,7 @@ module Sequent
36
35
 
37
36
  attr_accessor :aggregate_repository,
38
37
  :event_store,
38
+ :event_store_cache_event_types,
39
39
  :command_service,
40
40
  :event_record_class,
41
41
  :stream_record_class,
@@ -63,20 +63,23 @@ module Sequent
63
63
  :enable_multiple_database_support,
64
64
  :primary_database_role,
65
65
  :primary_database_key,
66
- :time_precision
66
+ :time_precision,
67
+ :enable_autoregistration
67
68
 
68
69
  attr_reader :migrations_class_name,
69
- :versions_table_name,
70
- :replayed_ids_table_name
70
+ :versions_table_name
71
71
 
72
72
  def self.instance
73
73
  @instance ||= new
74
74
  end
75
75
 
76
+ # Create a new instance of Configuration
76
77
  def self.reset
77
78
  @instance = new
78
79
  end
79
80
 
81
+ # Restore the given Configuration
82
+ # @param configuration [Sequent::Configuration]
80
83
  def self.restore(configuration)
81
84
  @instance = configuration
82
85
  end
@@ -88,6 +91,7 @@ module Sequent
88
91
  self.command_middleware = Sequent::Core::Middleware::Chain.new
89
92
 
90
93
  self.aggregate_repository = Sequent::Core::AggregateRepository.new
94
+ self.event_store_cache_event_types = true
91
95
  self.event_store = Sequent::Core::EventStore.new
92
96
  self.command_service = Sequent::Core::CommandService.new
93
97
  self.event_record_class = Sequent::Core::EventRecord
@@ -98,7 +102,6 @@ module Sequent
98
102
  self.event_publisher = Sequent::Core::EventPublisher.new
99
103
  self.disable_event_handlers = false
100
104
  self.versions_table_name = DEFAULT_VERSIONS_TABLE_NAME
101
- self.replayed_ids_table_name = DEFAULT_REPLAYED_IDS_TABLE_NAME
102
105
  self.migration_sql_files_directory = DEFAULT_MIGRATION_SQL_FILES_DIRECTORY
103
106
  self.view_schema_name = DEFAULT_VIEW_SCHEMA_NAME
104
107
  self.event_store_schema_name = DEFAULT_EVENT_STORE_SCHEMA_NAME
@@ -121,24 +124,19 @@ module Sequent
121
124
  self.primary_database_key = :primary
122
125
 
123
126
  self.time_precision = DEFAULT_TIME_PRECISION
127
+
128
+ self.enable_autoregistration = false
124
129
  end
125
130
 
126
131
  def can_use_multiple_databases?
127
132
  enable_multiple_database_support && ActiveRecord.version > Gem::Version.new('6.1.0')
128
133
  end
129
134
 
130
- def replayed_ids_table_name=(table_name)
131
- fail ArgumentError, 'table_name can not be nil' unless table_name
132
-
133
- @replayed_ids_table_name = table_name
134
- Sequent::Migrations::ViewSchema::ReplayedIds.table_name = table_name
135
- end
136
-
137
135
  def versions_table_name=(table_name)
138
136
  fail ArgumentError, 'table_name can not be nil' unless table_name
139
137
 
140
138
  @versions_table_name = table_name
141
- Sequent::Migrations::ViewSchema::Versions.table_name = table_name
139
+ Sequent::Migrations::Versions.table_name = table_name
142
140
  end
143
141
 
144
142
  def migrations_class_name=(class_name)
@@ -149,5 +147,58 @@ module Sequent
149
147
 
150
148
  @migrations_class_name = class_name
151
149
  end
150
+
151
+ # @!visibility private
152
+ def autoregister!
153
+ return unless enable_autoregistration
154
+
155
+ # Only autoregister the AggregateSnapshotter if the autoregistration is enabled
156
+ Sequent::Core::AggregateSnapshotter.skip_autoregister = false
157
+
158
+ autoload_if_in_rails
159
+
160
+ self.class.instance.command_handlers ||= []
161
+ for_each_autoregisterable_descenant_of(Sequent::CommandHandler) do |command_handler_class|
162
+ Sequent.logger.debug("[Configuration] Autoregistering CommandHandler #{command_handler_class}")
163
+ self.class.instance.command_handlers << command_handler_class.new
164
+ end
165
+
166
+ self.class.instance.event_handlers ||= []
167
+ for_each_autoregisterable_descenant_of(Sequent::Projector) do |projector_class|
168
+ Sequent.logger.debug("[Configuration] Autoregistering Projector #{projector_class}")
169
+ self.class.instance.event_handlers << projector_class.new
170
+ end
171
+
172
+ for_each_autoregisterable_descenant_of(Sequent::Workflow) do |workflow_class|
173
+ Sequent.logger.debug("[Configuration] Autoregistering Workflow #{workflow_class}")
174
+ self.class.instance.event_handlers << workflow_class.new
175
+ end
176
+
177
+ self.class.instance.command_handlers.map(&:class).tally.each do |(clazz, count)|
178
+ if count > 1
179
+ fail "CommandHandler #{clazz} is registered #{count} times. A CommandHandler can only be registered once"
180
+ end
181
+ end
182
+
183
+ self.class.instance.event_handlers.map(&:class).tally.each do |(clazz, count)|
184
+ if count > 1
185
+ fail "EventHandler #{clazz} is registered #{count} times. An EventHandler can only be registered once"
186
+ end
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ def autoload_if_in_rails
193
+ Rails.autoloaders.main.eager_load(force: true) if defined?(Rails) && Rails.respond_to?(:autoloaders)
194
+ end
195
+
196
+ def for_each_autoregisterable_descenant_of(clazz, &block)
197
+ clazz
198
+ .descendants
199
+ .reject(&:abstract_class)
200
+ .reject(&:skip_autoregister)
201
+ .each(&block)
202
+ end
152
203
  end
153
204
  end
@@ -79,8 +79,8 @@ module Sequent
79
79
  .configuration
80
80
  .event_store
81
81
  .stream_events_for_aggregate(aggregate_id, load_until: load_until) do |event_stream|
82
- aggregate.stream_from_history(event_stream)
83
- end
82
+ aggregate.stream_from_history(event_stream)
83
+ end
84
84
 
85
85
  if clazz
86
86
  fail TypeError, "#{aggregate.class} is not a #{clazz}" unless aggregate.class <= clazz
@@ -15,6 +15,10 @@ module Sequent
15
15
  end
16
16
 
17
17
  class AggregateSnapshotter < BaseCommandHandler
18
+ # By default skip autoregistering this CommandHandler.
19
+ # The AggregateSnapshotter is only autoregistered if autoregistration is enabled.
20
+ self.skip_autoregister = true
21
+
18
22
  on SnapshotCommand do |command|
19
23
  aggregate_ids = Sequent.configuration.event_store.aggregates_that_need_snapshots(
20
24
  @last_aggregate_id,
@@ -21,6 +21,11 @@ module Sequent
21
21
  class BaseCommandHandler
22
22
  include Sequent::Core::Helpers::UuidHelper
23
23
  include Sequent::Core::Helpers::MessageHandler
24
+ extend ActiveSupport::DescendantsTracker
25
+
26
+ class << self
27
+ attr_accessor :abstract_class, :skip_autoregister
28
+ end
24
29
 
25
30
  protected
26
31
 
@@ -16,7 +16,7 @@ require_relative 'projector'
16
16
  require_relative 'event_store'
17
17
  require_relative 'event_record'
18
18
  require_relative 'command_record'
19
- require_relative 'aggregate_snapshotter'
20
19
  require_relative 'workflow'
21
20
  require_relative 'random_uuid_generator'
22
21
  require_relative 'event_publisher'
22
+ require_relative 'aggregate_snapshotter'
@@ -29,8 +29,8 @@ module Sequent
29
29
  .reject { |k| payload_variables.include?(k) }
30
30
  .select { |k| self.class.types.keys.include?(to_attribute_name(k)) }
31
31
  .each do |k|
32
- result[k.to_s[1..-1].to_sym] = instance_variable_get(k)
33
- end
32
+ result[k.to_s[1..-1].to_sym] = instance_variable_get(k)
33
+ end
34
34
  result
35
35
  end
36
36
 
@@ -76,6 +76,7 @@ module Sequent
76
76
  include SerializesEvent
77
77
 
78
78
  self.table_name = 'event_records'
79
+ self.ignored_columns = %w[xact_id]
79
80
 
80
81
  belongs_to :stream_record
81
82
  belongs_to :command_record
@@ -35,14 +35,6 @@ module Sequent
35
35
  end
36
36
  end
37
37
 
38
- def initialize(cache_event_types: true)
39
- @event_types = if cache_event_types
40
- ThreadSafe::Cache.new
41
- else
42
- NoEventTypesCache.new
43
- end
44
- end
45
-
46
38
  ##
47
39
  # Stores the events in the EventStore and publishes the events
48
40
  # to the registered event_handlers.
@@ -114,13 +106,13 @@ module Sequent
114
106
  events
115
107
  .group_by(&:aggregate_id)
116
108
  .map do |aggregate_id, es|
117
- [
118
- streams.find do |stream_record|
119
- stream_record.aggregate_id == aggregate_id
120
- end.event_stream,
121
- es,
122
- ]
123
- end
109
+ [
110
+ streams.find do |stream_record|
111
+ stream_record.aggregate_id == aggregate_id
112
+ end.event_stream,
113
+ es,
114
+ ]
115
+ end
124
116
  end
125
117
 
126
118
  def aggregate_query(aggregate_id)
@@ -217,6 +209,18 @@ module Sequent
217
209
 
218
210
  private
219
211
 
212
+ def quote_table_name(table_name)
213
+ Sequent.configuration.event_record_class.connection.quote_table_name(table_name)
214
+ end
215
+
216
+ def event_types
217
+ @event_types = if Sequent.configuration.event_store_cache_event_types
218
+ ThreadSafe::Cache.new
219
+ else
220
+ NoEventTypesCache.new
221
+ end
222
+ end
223
+
220
224
  def column_names
221
225
  @column_names ||= Sequent
222
226
  .configuration
@@ -238,7 +242,7 @@ module Sequent
238
242
  end
239
243
 
240
244
  def resolve_event_type(event_type)
241
- @event_types.fetch_or_store(event_type) { |k| Class.const_get(k) }
245
+ event_types.fetch_or_store(event_type) { |k| Class.const_get(k) }
242
246
  end
243
247
 
244
248
  def publish_events(events)
@@ -79,8 +79,8 @@ module Sequent
79
79
  super if defined?(super)
80
80
  ensure_known_attributes(attrs)
81
81
  #{@types.map do |attribute, _|
82
- "@#{attribute} = attrs[:#{attribute}]"
83
- end.join("\n ")}
82
+ "@#{attribute} = attrs[:#{attribute}]"
83
+ end.join("\n ")}
84
84
  self
85
85
  end
86
86
  EOS
@@ -89,8 +89,8 @@ EOS
89
89
  def update_all_attributes_from_json(attrs)
90
90
  super if defined?(super)
91
91
  #{@types.map do |attribute, type|
92
- "@#{attribute} = #{type}.deserialize_from_json(attrs['#{attribute}'])"
93
- end.join("\n ")}
92
+ "@#{attribute} = #{type}.deserialize_from_json(attrs['#{attribute}'])"
93
+ end.join("\n ")}
94
94
  end
95
95
  EOS
96
96
  end
@@ -206,10 +206,10 @@ EOS
206
206
  value
207
207
  .select { |val| val.respond_to?(:validation_errors) }
208
208
  .each_with_index do |val, index|
209
- val.validation_errors.each do |k, v|
210
- result["#{field[0]}_#{index}_#{k}".to_sym] = v
209
+ val.validation_errors.each do |k, v|
210
+ result["#{field[0]}_#{index}_#{k}".to_sym] = v
211
+ end
211
212
  end
212
- end
213
213
  end
214
214
  end
215
215
  prefix ? HashWithIndifferentAccess[result.map { |k, v| ["#{prefix}_#{k}", v] }] : result
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative 'message_handler_option_registry'
4
4
  require_relative 'message_router'
5
- require_relative 'message_dispatcher'
6
5
 
7
6
  module Sequent
8
7
  module Core
@@ -60,11 +59,7 @@ module Sequent
60
59
  end
61
60
 
62
61
  def message_mapping
63
- message_router
64
- .routes
65
- .select { |matcher, _handlers| matcher.is_a?(MessageMatchers::InstanceOf) }
66
- .map { |k, v| [k.expected_class, v] }
67
- .to_h
62
+ message_router.instanceof_routes
68
63
  end
69
64
 
70
65
  def handles_message?(message)
@@ -106,13 +101,17 @@ module Sequent
106
101
  end
107
102
 
108
103
  def handle_message(message)
109
- message_dispatcher.dispatch_message(message)
104
+ handlers = self.class.message_router.match_message(message)
105
+ dispatch_message(message, handlers) unless handlers.empty?
110
106
  end
111
107
 
112
- private
113
-
114
- def message_dispatcher
115
- MessageDispatcher.new(self.class.message_router, self)
108
+ def dispatch_message(message, handlers)
109
+ handlers.each do |handler|
110
+ if Sequent.logger.debug?
111
+ Sequent.logger.debug("[MessageHandler] Handler #{self.class} handling #{message.class}")
112
+ end
113
+ instance_exec(message, &handler)
114
+ end
116
115
  end
117
116
  end
118
117
  end
@@ -7,7 +7,7 @@ module Sequent
7
7
  module Core
8
8
  module Helpers
9
9
  class MessageRouter
10
- attr_reader :routes
10
+ attr_reader :routes, :instanceof_routes
11
11
 
12
12
  def initialize
13
13
  clear_routes
@@ -21,7 +21,11 @@ module Sequent
21
21
  #
22
22
  def register_matchers(*matchers, handler)
23
23
  matchers.each do |matcher|
24
- @routes[matcher] << handler
24
+ if matcher.is_a?(MessageMatchers::InstanceOf)
25
+ @instanceof_routes[matcher.expected_class] << handler
26
+ else
27
+ @routes[matcher] << handler
28
+ end
25
29
  end
26
30
  end
27
31
 
@@ -29,11 +33,12 @@ module Sequent
29
33
  # Returns a set of handlers that match the given message, or an empty set when none match.
30
34
  #
31
35
  def match_message(message)
32
- @routes
33
- .reduce(Set.new) do |memo, (matcher, handlers)|
34
- memo = memo.merge(handlers) if matcher.matches_message?(message)
35
- memo
36
- end
36
+ result = Set.new
37
+ result.merge(@instanceof_routes[message.class])
38
+ @routes.each do |matcher, handlers|
39
+ result.merge(handlers) if matcher.matches_message?(message)
40
+ end
41
+ result
37
42
  end
38
43
 
39
44
  ##
@@ -47,6 +52,7 @@ module Sequent
47
52
  # Removes all routes from the router.
48
53
  #
49
54
  def clear_routes
55
+ @instanceof_routes = Hash.new { |h, k| h[k] = Set.new }
50
56
  @routes = Hash.new { |h, k| h[k] = Set.new }
51
57
  end
52
58
  end
@@ -117,6 +117,10 @@ module Sequent
117
117
  Sequent::ApplicationRecord.connection.execute(statement)
118
118
  end
119
119
 
120
+ def prepare
121
+ # noop
122
+ end
123
+
120
124
  def commit
121
125
  # noop
122
126
  end
@@ -76,6 +76,11 @@ module Sequent
76
76
  fail 'Method not supported in this persistor'
77
77
  end
78
78
 
79
+ # Hook to implement for instance the persistor batches statements
80
+ def prepare
81
+ fail 'Method not supported in this persistor'
82
+ end
83
+
79
84
  # Hook to implement for instance the persistor batches statements
80
85
  def commit
81
86
  fail 'Method not supported in this persistor'