sequent 6.0.1 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.
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'