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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/bin/sequent +31 -25
  3. data/lib/notices.rb +6 -0
  4. data/lib/sequent/application_record.rb +2 -0
  5. data/lib/sequent/configuration.rb +29 -29
  6. data/lib/sequent/core/aggregate_repository.rb +24 -14
  7. data/lib/sequent/core/aggregate_root.rb +16 -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 +30 -11
  12. data/lib/sequent/core/command_record.rb +12 -4
  13. data/lib/sequent/core/command_service.rb +41 -25
  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 +20 -15
  18. data/lib/sequent/core/event_record.rb +7 -7
  19. data/lib/sequent/core/event_store.rb +75 -49
  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 +64 -33
  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 -4
  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 +25 -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 +7 -9
  51. data/lib/sequent/core/transactions/no_transactions.rb +2 -1
  52. data/lib/sequent/core/transactions/transactions.rb +2 -0
  53. data/lib/sequent/core/value_object.rb +8 -10
  54. data/lib/sequent/core/workflow.rb +7 -5
  55. data/lib/sequent/generator/aggregate.rb +16 -10
  56. data/lib/sequent/generator/command.rb +26 -19
  57. data/lib/sequent/generator/event.rb +19 -17
  58. data/lib/sequent/generator/generator.rb +6 -0
  59. data/lib/sequent/generator/project.rb +3 -1
  60. data/lib/sequent/generator/template_project/Gemfile +1 -1
  61. data/lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb +1 -1
  62. data/lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb +1 -1
  63. data/lib/sequent/generator.rb +3 -4
  64. data/lib/sequent/migrations/executor.rb +30 -9
  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 +93 -44
  72. data/lib/sequent/rake/migration_tasks.rb +59 -23
  73. data/lib/sequent/rake/tasks.rb +5 -2
  74. data/lib/sequent/sequent.rb +6 -1
  75. data/lib/sequent/support/database.rb +39 -17
  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 +39 -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 +194 -0
  85. data/lib/sequent/util/printer.rb +6 -5
  86. data/lib/sequent/util/skip_if_already_processing.rb +21 -5
  87. data/lib/sequent/util/timer.rb +2 -0
  88. data/lib/sequent/util/util.rb +3 -0
  89. data/lib/sequent.rb +4 -0
  90. data/lib/version.rb +3 -1
  91. metadata +110 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68422698850db5ac4fa36b805bec257d00d40e1937ae736359b423104e7486fc
4
- data.tar.gz: 868cd1cc45201cb6b7d9cbaec96ec2de6053a4b8b1f55ac5aa4b00f0bee32755
3
+ metadata.gz: 5e0c606e57bb36fdcaeeb102904dd450a0ff9fc2a84fdd5aff7f365a914c45d3
4
+ data.tar.gz: ea03e8a0c7ab524ff8c73fded97a726edf3dff240b95db62496496d5a6a6d580
5
5
  SHA512:
6
- metadata.gz: 0eec0c36ffc290976c7654ca6aa69f6b473e093303c465f92b25653426deabd186ed80cda350af0808faa978b2d1488861e92308fde5b35319c42731e5dd1a3e
7
- data.tar.gz: 5649b65b48cca40d22dd6c375d48b46389d2cdcddcbe75ec0079ece9d12a4cdb4b3456470f032a16a16b0b0c27011b7ee5337b65c1e91a2b684566474054f3d5
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
- _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
@@ -43,8 +45,8 @@ def new_project(args)
43
45
  end
44
46
 
45
47
  def generate_aggregate(args)
46
- _args = args.dup
47
- aggregate_name = _args.shift
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
- _args = args.dup
56
- aggregate_name = _args.shift
57
- command_name = _args.shift
58
- attrs = _args
57
+ arguments = args.dup
58
+ aggregate_name = arguments.shift
59
+ command_name = arguments.shift
60
+ attrs = arguments
59
61
 
60
- abort('Please specify an aggregate name and command name. i.e. `sequent g command user AddUser`') unless args_valid?(aggregate_name, command_name)
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
- _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)
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
- _args = args.dup
78
- entity = _args.shift
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
- 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`")
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 '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,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
- attr_accessor :aggregate_repository
33
+ DEFAULT_ERROR_LOCALE_RESOLVER = -> { I18n.locale || :en }
32
34
 
33
- attr_accessor :event_store,
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
- attr_accessor :event_record_hooks_class
42
-
43
- attr_accessor :command_handlers,
44
- :command_filters
45
-
46
- attr_accessor :event_handlers
47
-
48
- attr_accessor :uuid_generator
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
- :event_store_schema_name
62
-
63
- attr_accessor :strict_check_attributes_on_apply_events
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.new('table_name can not be nil') unless table_name
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.new('table_name can not be nil') unless table_name
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
- 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
+
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
- 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
@@ -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.new('aggregate_ids is required') unless aggregate_ids
74
+ fail ArgumentError, 'aggregate_ids is required' unless aggregate_ids
73
75
  return [] if aggregate_ids.empty?
74
76
 
75
- _aggregate_ids = aggregate_ids.uniq
76
- _aggregates = aggregates.values_at(*_aggregate_ids).compact
77
- _query_ids = _aggregate_ids - _aggregates.map(&:id)
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
- _aggregates += Sequent.configuration.event_store.load_events_for_aggregates(_query_ids).map do |stream, events|
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 _aggregates.count != _aggregate_ids.count
85
- missing_aggregate_ids = _aggregate_ids - _aggregates.map(&:id)
86
- raise AggregateNotFound.new(missing_aggregate_ids)
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
- _aggregates.each do |aggregate|
91
- raise TypeError, "#{aggregate.class} is not a #{clazz}" if !(aggregate.class <= clazz)
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
- _aggregates.map do |aggregate|
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
- [ aggregate.event_stream, aggregate.uncommitted_events ]
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() # 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
@@ -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
- .event_attribute_keys(event_class)
127
- .any? { |k| instance_variable_get(:"@#{k.to_s}") != args[k.to_sym] }
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 < 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'
@@ -7,15 +9,23 @@ require_relative 'helpers/mergable'
7
9
 
8
10
  module Sequent
9
11
  module Core
10
- # Base command
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 ActiveModel::Validations,
13
- Sequent::Core::Helpers::Copyable,
14
- Sequent::Core::Helpers::AttributeSupport,
15
- Sequent::Core::Helpers::UuidHelper,
16
- Sequent::Core::Helpers::EqualSupport,
17
- Sequent::Core::Helpers::ParamSupport,
18
- 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
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, 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
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 command
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
- raise ArgumentError, "Missing aggregate_id" if args[:aggregate_id].nil?
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?(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,14 +39,17 @@ 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
 
42
46
  validates_presence_of :command_type, :command_json
43
47
 
44
48
  def parent
45
- EventRecord.find_by(aggregate_id: event_aggregate_id, sequence_number: event_sequence_number)
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