sequent 4.0.0 → 4.3.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequent +33 -26
  3. data/lib/notices.rb +2 -0
  4. data/lib/sequent/application_record.rb +2 -0
  5. data/lib/sequent/configuration.rb +24 -31
  6. data/lib/sequent/core/aggregate_repository.rb +48 -13
  7. data/lib/sequent/core/aggregate_root.rb +36 -7
  8. data/lib/sequent/core/aggregate_roots.rb +24 -0
  9. data/lib/sequent/core/aggregate_snapshotter.rb +8 -5
  10. data/lib/sequent/core/base_command_handler.rb +4 -2
  11. data/lib/sequent/core/command.rb +17 -9
  12. data/lib/sequent/core/command_record.rb +8 -3
  13. data/lib/sequent/core/command_service.rb +18 -18
  14. data/lib/sequent/core/core.rb +2 -0
  15. data/lib/sequent/core/current_event.rb +2 -0
  16. data/lib/sequent/core/event.rb +16 -11
  17. data/lib/sequent/core/event_publisher.rb +16 -15
  18. data/lib/sequent/core/event_record.rb +7 -7
  19. data/lib/sequent/core/event_store.rb +89 -51
  20. data/lib/sequent/core/ext/ext.rb +9 -1
  21. data/lib/sequent/core/helpers/array_with_type.rb +4 -1
  22. data/lib/sequent/core/helpers/association_validator.rb +9 -7
  23. data/lib/sequent/core/helpers/attribute_support.rb +45 -28
  24. data/lib/sequent/core/helpers/autoset_attributes.rb +4 -4
  25. data/lib/sequent/core/helpers/boolean_validator.rb +6 -1
  26. data/lib/sequent/core/helpers/copyable.rb +2 -2
  27. data/lib/sequent/core/helpers/date_time_validator.rb +4 -1
  28. data/lib/sequent/core/helpers/date_validator.rb +6 -1
  29. data/lib/sequent/core/helpers/default_validators.rb +12 -10
  30. data/lib/sequent/core/helpers/equal_support.rb +8 -6
  31. data/lib/sequent/core/helpers/helpers.rb +2 -0
  32. data/lib/sequent/core/helpers/mergable.rb +6 -5
  33. data/lib/sequent/core/helpers/message_handler.rb +3 -1
  34. data/lib/sequent/core/helpers/param_support.rb +19 -15
  35. data/lib/sequent/core/helpers/secret.rb +14 -12
  36. data/lib/sequent/core/helpers/string_support.rb +5 -4
  37. data/lib/sequent/core/helpers/string_to_value_parsers.rb +7 -2
  38. data/lib/sequent/core/helpers/string_validator.rb +6 -1
  39. data/lib/sequent/core/helpers/type_conversion_support.rb +5 -3
  40. data/lib/sequent/core/helpers/uuid_helper.rb +5 -2
  41. data/lib/sequent/core/helpers/value_validators.rb +23 -9
  42. data/lib/sequent/core/persistors/active_record_persistor.rb +19 -9
  43. data/lib/sequent/core/persistors/persistor.rb +16 -14
  44. data/lib/sequent/core/persistors/persistors.rb +2 -0
  45. data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +70 -47
  46. data/lib/sequent/core/projector.rb +53 -22
  47. data/lib/sequent/core/random_uuid_generator.rb +2 -0
  48. data/lib/sequent/core/sequent_oj.rb +2 -0
  49. data/lib/sequent/core/stream_record.rb +9 -3
  50. data/lib/sequent/core/transactions/active_record_transaction_provider.rb +30 -9
  51. data/lib/sequent/core/transactions/no_transactions.rb +2 -1
  52. data/lib/sequent/core/transactions/read_only_active_record_transaction_provider.rb +46 -0
  53. data/lib/sequent/core/transactions/transactions.rb +3 -0
  54. data/lib/sequent/core/value_object.rb +8 -10
  55. data/lib/sequent/core/workflow.rb +35 -5
  56. data/lib/sequent/generator/aggregate.rb +16 -10
  57. data/lib/sequent/generator/command.rb +26 -19
  58. data/lib/sequent/generator/event.rb +19 -17
  59. data/lib/sequent/generator/generator.rb +2 -0
  60. data/lib/sequent/generator/project.rb +9 -0
  61. data/lib/sequent/generator/template_project/Gemfile +1 -1
  62. data/lib/sequent/generator/template_project/ruby-version +1 -0
  63. data/lib/sequent/generator.rb +2 -0
  64. data/lib/sequent/migrations/executor.rb +22 -13
  65. data/lib/sequent/migrations/functions.rb +5 -6
  66. data/lib/sequent/migrations/migrate_events.rb +12 -9
  67. data/lib/sequent/migrations/migrations.rb +2 -1
  68. data/lib/sequent/migrations/planner.rb +33 -23
  69. data/lib/sequent/migrations/projectors.rb +4 -3
  70. data/lib/sequent/migrations/sql.rb +2 -0
  71. data/lib/sequent/migrations/view_schema.rb +84 -45
  72. data/lib/sequent/rake/migration_tasks.rb +58 -22
  73. data/lib/sequent/rake/tasks.rb +5 -2
  74. data/lib/sequent/sequent.rb +2 -0
  75. data/lib/sequent/support/database.rb +30 -15
  76. data/lib/sequent/support/view_projection.rb +6 -3
  77. data/lib/sequent/support/view_schema.rb +2 -0
  78. data/lib/sequent/support.rb +2 -0
  79. data/lib/sequent/test/command_handler_helpers.rb +35 -17
  80. data/lib/sequent/test/event_handler_helpers.rb +10 -4
  81. data/lib/sequent/test/event_stream_helpers.rb +7 -3
  82. data/lib/sequent/test/time_comparison.rb +12 -5
  83. data/lib/sequent/test.rb +2 -0
  84. data/lib/sequent/util/dry_run.rb +28 -20
  85. data/lib/sequent/util/printer.rb +6 -5
  86. data/lib/sequent/util/skip_if_already_processing.rb +3 -1
  87. data/lib/sequent/util/timer.rb +2 -0
  88. data/lib/sequent/util/util.rb +2 -0
  89. data/lib/sequent.rb +2 -0
  90. data/lib/version.rb +3 -1
  91. metadata +84 -67
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3322a4b950e847a5818555b2718df70e2b81a8e398074309a0b58c2a8d332ce5
4
- data.tar.gz: 6136697a8a9596e999fabf3814509428b06188a0ac577fa3443a80ec190cbdd7
3
+ metadata.gz: feea2aeb3dba28570a36615392e4ecc1037bfdfd1c2d667bcc787113e4c7fdba
4
+ data.tar.gz: 79dc60b21109885a56f3f68cf97185afee5692167f54db1934eaa89dc9d030de
5
5
  SHA512:
6
- metadata.gz: f8ce675ae0a16274630066192086fb8c46bb0306e9280fd4b8901500af4a7af56844c5d6c40cd0f45a87342c33a4faeb2eee48f0dc0fb05a0a09046a2dea7414
7
- data.tar.gz: e750e54d72de1c8641a513839c675adeb32fbeaa3435528a8aca755a951a7572d696e9348b7594e104f044978a0e9780f8e080ff1ad5e4ea9b7cc203fac6fee2
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
- _args = args.dup
12
- name = _args.shift
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, __dir__)}
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
- _args = args.dup
47
- aggregate_name = _args.shift
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
- _args = args.dup
56
- aggregate_name = _args.shift
57
- command_name = _args.shift
58
- attrs = _args
58
+ arguments = args.dup
59
+ aggregate_name = arguments.shift
60
+ command_name = arguments.shift
61
+ attrs = arguments
59
62
 
60
- abort('Please specify an aggregate name and command name. i.e. `sequent g command user AddUser`') unless args_valid?(aggregate_name, command_name)
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
- _args = args.dup
67
- aggregate_name = _args.shift
68
- event_name = _args.shift
69
- attrs = _args
70
-
71
- abort('Please specify an aggregate name and event name. i.e. `sequent g event user AddUser`') unless args_valid?(aggregate_name, event_name)
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
- _args = args.dup
78
- entity = _args.shift
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
- when 'aggregate'
83
- generate_aggregate(_args)
84
- when 'command'
85
- generate_command(_args)
86
- when 'event'
87
- generate_event(_args)
88
- else
89
- abort("Unknown argument #{entity} for `generate`. Try `sequent g aggregate user`")
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
  # This file is for any notices such as deprecation warnings, which should appear
2
4
  # in the logs during app boot. Adding such warnings in other places causes
3
5
  # lots of noise with duplicated messages, whereas this file is only
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
 
3
5
  module Sequent
@@ -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
- attr_accessor :event_record_hooks_class
45
-
46
- attr_accessor :command_handlers,
47
- :command_filters
48
-
49
- attr_accessor :event_handlers
50
-
51
- attr_accessor :uuid_generator
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.new('table_name can not be nil') unless table_name
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.new('table_name can not be nil') unless table_name
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
- fail ArgumentError.new("#{migration_class} must extend Sequent::Migrations::Projectors") unless migration_class <= Sequent::Migrations::Projectors
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
- raise NonUniqueAggregateId.new(aggregate, aggregates[aggregate.id])
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.new('aggregate_ids is required') unless aggregate_ids
105
+ fail ArgumentError, 'aggregate_ids is required' unless aggregate_ids
73
106
  return [] if aggregate_ids.empty?
74
107
 
75
- _aggregate_ids = aggregate_ids.uniq
76
- _aggregates = aggregates.values_at(*_aggregate_ids).compact
77
- _query_ids = _aggregate_ids - _aggregates.map(&:id)
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
- _aggregates += Sequent.configuration.event_store.load_events_for_aggregates(_query_ids).map do |stream, events|
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 _aggregates.count != _aggregate_ids.count
85
- missing_aggregate_ids = _aggregate_ids - _aggregates.map(&:id)
86
- raise AggregateNotFound.new(missing_aggregate_ids)
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
- _aggregates.each do |aggregate|
91
- raise TypeError, "#{aggregate.class} is not a #{clazz}" if !(aggregate.class <= clazz)
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
- _aggregates.map do |aggregate|
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
- [ aggregate.event_stream, aggregate.uncommitted_events ]
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() # allocate without calling new
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
- raise "Empty history" if events.empty?
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
- .event_attribute_keys(event_class)
127
- .any? { |k| instance_variable_get(:"@#{k.to_s}") != args[k.to_sym] }
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 < Sequent::Core::BaseCommand
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 = repository.event_store.aggregates_that_need_snapshots(@last_aggregate_id, command.limit)
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::MessageHandler,
21
- Sequent::Core::Helpers::UuidHelper
22
+ include Sequent::Core::Helpers::UuidHelper
23
+ include Sequent::Core::Helpers::MessageHandler
22
24
 
23
25
  protected
24
26
 
@@ -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 ActiveModel::Validations,
21
- Sequent::Core::Helpers::Copyable,
22
- Sequent::Core::Helpers::AttributeSupport,
23
- Sequent::Core::Helpers::UuidHelper,
24
- Sequent::Core::Helpers::EqualSupport,
25
- Sequent::Core::Helpers::ParamSupport,
26
- Sequent::Core::Helpers::Mergable
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, only_integer: true, allow_nil: true, allow_blank: true, greater_than: 0
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
- raise ArgumentError, "Missing aggregate_id" if args[:aggregate_id].nil?
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?(command, :event_sequence_number)
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 = "command_records"
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