sequent 0.1.10 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/db/sequent_schema.rb +0 -3
- data/lib/sequent/configuration.rb +10 -0
- data/lib/sequent/core/aggregate_repository.rb +41 -8
- data/lib/sequent/core/aggregate_root.rb +0 -24
- data/lib/sequent/core/aggregate_snapshotter.rb +2 -2
- data/lib/sequent/core/base_command_handler.rb +2 -6
- data/lib/sequent/core/base_event_handler.rb +1 -1
- data/lib/sequent/core/command.rb +23 -15
- data/lib/sequent/core/command_service.rb +1 -1
- data/lib/sequent/core/core.rb +1 -1
- data/lib/sequent/core/event.rb +0 -21
- data/lib/sequent/core/event_record.rb +11 -1
- data/lib/sequent/core/event_store.rb +122 -18
- data/lib/sequent/core/ext/ext.rb +20 -0
- data/lib/sequent/core/helpers/array_with_type.rb +4 -0
- data/lib/sequent/core/helpers/association_validator.rb +22 -7
- data/lib/sequent/core/helpers/attribute_support.rb +15 -6
- data/lib/sequent/core/helpers/param_support.rb +28 -29
- data/lib/sequent/core/helpers/self_applier.rb +4 -3
- data/lib/sequent/core/helpers/string_to_value_parsers.rb +10 -3
- data/lib/sequent/core/helpers/type_conversion_support.rb +5 -0
- data/lib/sequent/core/helpers/uuid_helper.rb +2 -2
- data/lib/sequent/core/helpers/value_validators.rb +2 -2
- data/lib/sequent/core/random_uuid_generator.rb +9 -0
- data/lib/sequent/core/record_sessions/active_record_session.rb +11 -5
- data/lib/sequent/core/record_sessions/replay_events_session.rb +138 -108
- data/lib/sequent/core/sequent_oj.rb +4 -3
- data/lib/sequent/core/stream_record.rb +1 -1
- data/lib/sequent/core/transactions/active_record_transaction_provider.rb +1 -1
- data/lib/sequent/migrations/migrate_events.rb +22 -15
- data/lib/sequent/rake/tasks.rb +102 -0
- data/lib/sequent/sequent.rb +4 -0
- data/lib/sequent/support.rb +3 -0
- data/lib/sequent/support/database.rb +55 -0
- data/lib/sequent/support/view_projection.rb +58 -0
- data/lib/sequent/support/view_schema.rb +22 -0
- data/lib/sequent/test/command_handler_helpers.rb +21 -8
- data/lib/sequent/test/event_handler_helpers.rb +7 -3
- data/lib/version.rb +1 -1
- metadata +54 -9
- data/lib/sequent/core/tenant_event_store.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1c376e79b2afb9c653058f4cb38e7d24bb7d742
|
4
|
+
data.tar.gz: 751c997c10407d7dd073b70b268ed69d16d1b459
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 042064fb131306a5197e035330dceb3e467c2031f73932777286ec72219545ef7fcc5bbd3b0322177801d994e87adf66735be07e4b03fbf2e725d209b11e8b09
|
7
|
+
data.tar.gz: 777558493270ceeb8e80038e6b2336bfebbdbb103e74c341f676e53599971e8723862b3b6aa424d008f294ff78108a580652f31a90600fa90a275387696ba5f2
|
data/db/sequent_schema.rb
CHANGED
@@ -2,7 +2,6 @@ ActiveRecord::Schema.define do
|
|
2
2
|
|
3
3
|
create_table "event_records", :force => true do |t|
|
4
4
|
t.string "aggregate_id", :null => false
|
5
|
-
t.string "organization_id"
|
6
5
|
t.integer "sequence_number", :null => false
|
7
6
|
t.datetime "created_at", :null => false
|
8
7
|
t.string "event_type", :null => false
|
@@ -12,7 +11,6 @@ ActiveRecord::Schema.define do
|
|
12
11
|
end
|
13
12
|
|
14
13
|
create_table "command_records", :force => true do |t|
|
15
|
-
t.string "organization_id"
|
16
14
|
t.string "user_id"
|
17
15
|
t.string "aggregate_id"
|
18
16
|
t.string "command_type", :null => false
|
@@ -31,7 +29,6 @@ CREATE UNIQUE INDEX unique_event_per_aggregate ON event_records (
|
|
31
29
|
CREATE INDEX snapshot_events ON event_records (aggregate_id, sequence_number DESC) WHERE event_type = 'Sequent::Core::SnapshotEvent'
|
32
30
|
}
|
33
31
|
add_index "event_records", ["command_record_id"], :name => "index_event_records_on_command_record_id"
|
34
|
-
add_index "event_records", ["organization_id"], :name => "index_event_records_on_organization_id"
|
35
32
|
add_index "event_records", ["event_type"], :name => "index_event_records_on_event_type"
|
36
33
|
add_index "event_records", ["created_at"], :name => "index_event_records_on_created_at"
|
37
34
|
|
@@ -19,6 +19,10 @@ module Sequent
|
|
19
19
|
|
20
20
|
attr_accessor :event_handlers
|
21
21
|
|
22
|
+
attr_accessor :uuid_generator
|
23
|
+
|
24
|
+
attr_accessor :disable_event_handlers
|
25
|
+
|
22
26
|
def self.instance
|
23
27
|
@instance ||= new
|
24
28
|
end
|
@@ -27,6 +31,10 @@ module Sequent
|
|
27
31
|
@instance = new
|
28
32
|
end
|
29
33
|
|
34
|
+
def self.restore(configuration)
|
35
|
+
@instance = configuration
|
36
|
+
end
|
37
|
+
|
30
38
|
def initialize
|
31
39
|
self.command_handlers = []
|
32
40
|
self.command_filters = []
|
@@ -38,6 +46,8 @@ module Sequent
|
|
38
46
|
self.stream_record_class = Sequent::Core::StreamRecord
|
39
47
|
self.snapshot_event_class = Sequent::Core::SnapshotEvent
|
40
48
|
self.transaction_provider = Sequent::Core::Transactions::NoTransactions.new
|
49
|
+
self.uuid_generator = Sequent::Core::RandomUuidGenerator
|
50
|
+
self.disable_event_handlers = false
|
41
51
|
end
|
42
52
|
|
43
53
|
def event_store=(event_store)
|
@@ -18,13 +18,13 @@ module Sequent
|
|
18
18
|
|
19
19
|
attr_reader :event_store
|
20
20
|
|
21
|
-
class NonUniqueAggregateId <
|
21
|
+
class NonUniqueAggregateId < StandardError
|
22
22
|
def initialize(existing, new)
|
23
23
|
super "Duplicate aggregate #{new} with same key as existing #{existing}"
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
class AggregateNotFound <
|
27
|
+
class AggregateNotFound < StandardError
|
28
28
|
def initialize(id)
|
29
29
|
super "Aggregate with id #{id} not found"
|
30
30
|
end
|
@@ -56,16 +56,49 @@ module Sequent
|
|
56
56
|
# Loads aggregate by given id and class
|
57
57
|
# Returns the one in the current Unit Of Work otherwise loads it from history.
|
58
58
|
def load_aggregate(aggregate_id, clazz = nil)
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
load_aggregates([aggregate_id], clazz)[0]
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Loads multiple aggregates at once.
|
64
|
+
# Returns the ones in the current Unit Of Work otherwise loads it from history.
|
65
|
+
#
|
66
|
+
# Note: This will load all the aggregates in memory, so querying 100s of aggregates
|
67
|
+
# with 100s of events could cause memory issues.
|
68
|
+
#
|
69
|
+
# Returns all aggregates or raises +AggregateNotFound+
|
70
|
+
# If +clazz+ is given and one of the aggregates is not of the correct type
|
71
|
+
# a +TypeError+ is raised.
|
72
|
+
#
|
73
|
+
# +aggregate_ids+ The ids of the aggregates to be loaded
|
74
|
+
# +clazz+ Optional argument that checks if all aggregates are of type +clazz+
|
75
|
+
def load_aggregates(aggregate_ids, clazz = nil)
|
76
|
+
fail ArgumentError.new('aggregate_ids is required') unless aggregate_ids
|
77
|
+
return [] if aggregate_ids.empty?
|
78
|
+
|
79
|
+
_aggregate_ids = aggregate_ids.uniq
|
80
|
+
_aggregates = aggregates.values_at(*_aggregate_ids).compact
|
81
|
+
_query_ids = _aggregate_ids - _aggregates.map(&:id)
|
82
|
+
|
83
|
+
_aggregates += @event_store.load_events_for_aggregates(_query_ids).map do |stream, events|
|
62
84
|
aggregate_class = Class.const_get(stream.aggregate_type)
|
63
|
-
|
85
|
+
aggregate_class.load_from_history(stream, events)
|
86
|
+
end
|
87
|
+
|
88
|
+
if _aggregates.count != _aggregate_ids.count
|
89
|
+
missing_aggregate_ids = _aggregate_ids - _aggregates.map(&:id)
|
90
|
+
raise AggregateNotFound.new(missing_aggregate_ids)
|
64
91
|
end
|
65
92
|
|
66
|
-
|
93
|
+
if clazz
|
94
|
+
_aggregates.each do |aggregate|
|
95
|
+
raise TypeError, "#{aggregate.class} is not a #{clazz}" if !(aggregate.class <= clazz)
|
96
|
+
end
|
97
|
+
end
|
67
98
|
|
68
|
-
|
99
|
+
_aggregates.map do |aggregate|
|
100
|
+
aggregates[aggregate.id] = aggregate
|
101
|
+
end
|
69
102
|
end
|
70
103
|
|
71
104
|
##
|
@@ -103,29 +103,5 @@ module Sequent
|
|
103
103
|
@uncommitted_events << event
|
104
104
|
end
|
105
105
|
end
|
106
|
-
|
107
|
-
# You can use this class when running in a multi tenant environment
|
108
|
-
# It basically makes sure that the +organization_id+ (the tenant_id for historic reasons)
|
109
|
-
# is available for the subclasses
|
110
|
-
class TenantAggregateRoot < AggregateRoot
|
111
|
-
attr_reader :organization_id
|
112
|
-
|
113
|
-
def initialize(id, organization_id)
|
114
|
-
super(id)
|
115
|
-
@organization_id = organization_id
|
116
|
-
end
|
117
|
-
|
118
|
-
def load_from_history(stream, events)
|
119
|
-
raise "Empty history" if events.empty?
|
120
|
-
@organization_id = events.first.organization_id
|
121
|
-
super
|
122
|
-
end
|
123
|
-
|
124
|
-
protected
|
125
|
-
|
126
|
-
def build_event(event, params = {})
|
127
|
-
super(event, params.merge({organization_id: @organization_id}))
|
128
|
-
end
|
129
|
-
end
|
130
106
|
end
|
131
107
|
end
|
@@ -6,7 +6,7 @@ module Sequent
|
|
6
6
|
|
7
7
|
class AggregateSnapshotter < BaseCommandHandler
|
8
8
|
|
9
|
-
def handles_message?(message)
|
9
|
+
def self.handles_message?(message)
|
10
10
|
message.is_a? SnapshotCommand
|
11
11
|
end
|
12
12
|
|
@@ -27,7 +27,7 @@ module Sequent
|
|
27
27
|
Sequent.logger.info "Taking snapshot for aggregate #{aggregate}"
|
28
28
|
aggregate.take_snapshot!
|
29
29
|
rescue => e
|
30
|
-
Sequent.logger.
|
30
|
+
Sequent.logger.error("Failed to take snapshot for aggregate #{aggregate_id}: #{e}, #{e.inspect}")
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -26,16 +26,12 @@ module Sequent
|
|
26
26
|
@repository = repository
|
27
27
|
end
|
28
28
|
|
29
|
-
def handles_message?(command)
|
30
|
-
self.class.message_mapping.keys.include? command.class
|
31
|
-
end
|
32
|
-
|
33
29
|
protected
|
34
|
-
|
30
|
+
|
31
|
+
def do_with_aggregate(command, clazz = nil, aggregate_id = nil)
|
35
32
|
aggregate = @repository.load_aggregate(aggregate_id.nil? ? command.aggregate_id : aggregate_id, clazz)
|
36
33
|
yield aggregate if block_given?
|
37
34
|
end
|
38
|
-
|
39
35
|
end
|
40
36
|
end
|
41
37
|
end
|
@@ -44,7 +44,7 @@ module Sequent
|
|
44
44
|
|
45
45
|
def_delegators :@record_session, :update_record, :create_record, :create_or_update_record, :get_record!, :get_record,
|
46
46
|
:delete_all_records, :update_all_records, :do_with_records, :do_with_record, :delete_record,
|
47
|
-
:find_records, :last_record
|
47
|
+
:find_records, :last_record, :execute
|
48
48
|
|
49
49
|
end
|
50
50
|
|
data/lib/sequent/core/command.rb
CHANGED
@@ -25,6 +25,9 @@ module Sequent
|
|
25
25
|
@created_at = DateTime.now
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.inherited(subclass)
|
29
|
+
Commands << subclass
|
30
|
+
end
|
28
31
|
end
|
29
32
|
|
30
33
|
module UpdateSequenceNumber
|
@@ -36,6 +39,26 @@ module Sequent
|
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
42
|
+
class Commands
|
43
|
+
class << self
|
44
|
+
def commands
|
45
|
+
@commands ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
def all
|
49
|
+
commands
|
50
|
+
end
|
51
|
+
|
52
|
+
def <<(command)
|
53
|
+
commands << command
|
54
|
+
end
|
55
|
+
|
56
|
+
def find(command_name)
|
57
|
+
commands.find { |c| c.name == command_name }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
39
62
|
# Most commonly used command
|
40
63
|
# Command can be instantiated just by using:
|
41
64
|
#
|
@@ -53,25 +76,10 @@ module Sequent
|
|
53
76
|
raise ArgumentError, "Missing aggregate_id" if args[:aggregate_id].nil?
|
54
77
|
super
|
55
78
|
end
|
56
|
-
|
57
79
|
end
|
58
80
|
|
59
81
|
class UpdateCommand < Command
|
60
82
|
include UpdateSequenceNumber
|
61
83
|
end
|
62
|
-
|
63
|
-
class TenantCommand < Command
|
64
|
-
attrs organization_id: String
|
65
|
-
|
66
|
-
def initialize(args = {})
|
67
|
-
raise ArgumentError, "Missing organization_id" if args[:organization_id].nil?
|
68
|
-
super
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
class UpdateTenantCommand < TenantCommand
|
73
|
-
include UpdateSequenceNumber
|
74
|
-
end
|
75
|
-
|
76
84
|
end
|
77
85
|
end
|
@@ -41,7 +41,7 @@ module Sequent
|
|
41
41
|
|
42
42
|
raise CommandNotValid.new(command) unless command.valid?
|
43
43
|
parsed_command = command.parse_attrs_to_correct_types
|
44
|
-
command_handlers.select { |h| h.handles_message?(parsed_command) }.each { |h| h.handle_message parsed_command }
|
44
|
+
command_handlers.select { |h| h.class.handles_message?(parsed_command) }.each { |h| h.handle_message parsed_command }
|
45
45
|
repository.commit(parsed_command)
|
46
46
|
end
|
47
47
|
end
|
data/lib/sequent/core/core.rb
CHANGED
@@ -11,8 +11,8 @@ require_relative 'command_service'
|
|
11
11
|
require_relative 'value_object'
|
12
12
|
require_relative 'base_event_handler'
|
13
13
|
require_relative 'event_store'
|
14
|
-
require_relative 'tenant_event_store'
|
15
14
|
require_relative 'event_record'
|
16
15
|
require_relative 'command_record'
|
17
16
|
require_relative 'aggregate_snapshotter'
|
18
17
|
require_relative 'workflow'
|
18
|
+
require_relative 'random_uuid_generator'
|
data/lib/sequent/core/event.rb
CHANGED
@@ -42,29 +42,8 @@ module Sequent
|
|
42
42
|
|
43
43
|
end
|
44
44
|
|
45
|
-
class TenantEvent < Event
|
46
|
-
|
47
|
-
attrs organization_id: String
|
48
|
-
|
49
|
-
def initialize(args = {})
|
50
|
-
super
|
51
|
-
raise "Missing organization_id" unless @organization_id
|
52
|
-
end
|
53
|
-
|
54
|
-
protected
|
55
|
-
def payload_variables
|
56
|
-
super << :"@organization_id"
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
class CreateEvent < TenantEvent
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
45
|
class SnapshotEvent < Event
|
66
46
|
attrs data: String
|
67
47
|
end
|
68
|
-
|
69
48
|
end
|
70
49
|
end
|
@@ -16,7 +16,17 @@ module Sequent
|
|
16
16
|
self.organization_id = event.organization_id if event.respond_to?(:organization_id)
|
17
17
|
self.event_type = event.class.name
|
18
18
|
self.created_at = event.created_at
|
19
|
-
self.event_json =
|
19
|
+
self.event_json = self.class.serialize_to_json(event)
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def serialize_to_json(event)
|
24
|
+
Sequent::Core::Oj.dump(event)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.included(host_class)
|
29
|
+
host_class.extend(ClassMethods)
|
20
30
|
end
|
21
31
|
end
|
22
32
|
|
@@ -9,6 +9,35 @@ module Sequent
|
|
9
9
|
include ActiveRecord::ConnectionAdapters::Quoting
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
+
class PublishEventError < RuntimeError
|
13
|
+
attr_reader :event_handler_class, :event
|
14
|
+
|
15
|
+
def initialize(event_handler_class, event)
|
16
|
+
@event_handler_class = event_handler_class
|
17
|
+
@event = event
|
18
|
+
end
|
19
|
+
|
20
|
+
def message
|
21
|
+
"Event Handler: #{@event_handler_class.inspect}\nEvent: #{@event.inspect}\nCause: #{cause.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class OptimisticLockingError < RuntimeError
|
26
|
+
end
|
27
|
+
|
28
|
+
class DeserializeEventError < RuntimeError
|
29
|
+
attr_reader :event_hash
|
30
|
+
|
31
|
+
def initialize(event_hash)
|
32
|
+
@event_hash = event_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def message
|
36
|
+
"Event hash: #{event_hash.inspect}\nCause: #{cause.inspect}"
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
12
41
|
attr_accessor :configuration
|
13
42
|
def_delegators :@configuration, :stream_record_class, :event_record_class, :snapshot_event_class, :event_handlers
|
14
43
|
|
@@ -26,28 +55,37 @@ module Sequent
|
|
26
55
|
#
|
27
56
|
def commit_events(command, streams_with_events)
|
28
57
|
store_events(command, streams_with_events)
|
29
|
-
publish_events(streams_with_events.flat_map {|_, events| events}, event_handlers)
|
58
|
+
publish_events(streams_with_events.flat_map { |_, events| events }, event_handlers)
|
30
59
|
end
|
31
60
|
|
32
61
|
##
|
33
62
|
# Returns all events for the aggregate ordered by sequence_number
|
34
63
|
#
|
35
64
|
def load_events(aggregate_id)
|
36
|
-
|
37
|
-
|
65
|
+
load_events_for_aggregates([aggregate_id])[0]
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_events_for_aggregates(aggregate_ids)
|
69
|
+
return [] if aggregate_ids.none?
|
70
|
+
|
71
|
+
streams = stream_record_class.where(aggregate_id: aggregate_ids)
|
72
|
+
|
38
73
|
events = event_record_class.connection.select_all(%Q{
|
39
74
|
SELECT event_type, event_json
|
40
75
|
FROM #{quote_table_name event_record_class.table_name}
|
41
|
-
|
42
|
-
|
76
|
+
WHERE aggregate_id in (#{aggregate_ids.map{ |aggregate_id| quote(aggregate_id)}.join(",")})
|
77
|
+
AND sequence_number >= COALESCE((SELECT MAX(sequence_number)
|
43
78
|
FROM #{quote_table_name event_record_class.table_name}
|
44
79
|
WHERE event_type = #{quote snapshot_event_class.name}
|
45
|
-
AND aggregate_id
|
46
|
-
|
80
|
+
AND aggregate_id in (#{aggregate_ids.map{ |aggregate_id| quote(aggregate_id)}.join(",")})), 0)
|
81
|
+
ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote snapshot_event_class.name} THEN 0 ELSE 1 END) ASC
|
47
82
|
}).map! do |event_hash|
|
48
83
|
deserialize_event(event_hash)
|
49
84
|
end
|
50
|
-
|
85
|
+
|
86
|
+
events
|
87
|
+
.group_by { |event| event.aggregate_id }
|
88
|
+
.map { |aggregate_id, _events| [streams.find { |stream_record| stream_record.aggregate_id == aggregate_id }.event_stream, _events] }
|
51
89
|
end
|
52
90
|
|
53
91
|
def stream_exists?(aggregate_id)
|
@@ -58,11 +96,47 @@ SELECT event_type, event_json
|
|
58
96
|
# Replays all events in the event store to the registered event_handlers.
|
59
97
|
#
|
60
98
|
# @param block that returns the events.
|
99
|
+
# <b>DEPRECATED:</b> use <tt>replay_events_from_cursor</tt> instead.
|
61
100
|
def replay_events
|
62
|
-
|
101
|
+
warn "[DEPRECATION] `replay_events` is deprecated in favor of `replay_events_from_cursor`"
|
102
|
+
events = yield.map { |event_hash| deserialize_event(event_hash) }
|
63
103
|
publish_events(events, event_handlers)
|
64
104
|
end
|
65
105
|
|
106
|
+
##
|
107
|
+
# Replays all events on an `EventRecord` cursor from the given block.
|
108
|
+
#
|
109
|
+
# Prefer this replay method if your db adapter supports cursors.
|
110
|
+
#
|
111
|
+
# @param get_events lambda that returns the events cursor
|
112
|
+
# @param on_progress lambda that gets called on substantial progress
|
113
|
+
def replay_events_from_cursor(block_size: 2000,
|
114
|
+
get_events:,
|
115
|
+
on_progress: PRINT_PROGRESS)
|
116
|
+
progress = 0
|
117
|
+
cursor = get_events.call
|
118
|
+
ids_replayed = []
|
119
|
+
cursor.each_row(block_size: block_size).each do |record|
|
120
|
+
event = deserialize_event(record)
|
121
|
+
publish_events([event], event_handlers)
|
122
|
+
progress += 1
|
123
|
+
ids_replayed << record['id']
|
124
|
+
if progress % block_size == 0
|
125
|
+
on_progress[progress, false, ids_replayed]
|
126
|
+
ids_replayed.clear
|
127
|
+
end
|
128
|
+
end
|
129
|
+
on_progress[progress, true, ids_replayed]
|
130
|
+
end
|
131
|
+
|
132
|
+
PRINT_PROGRESS = lambda do |progress, done, _|
|
133
|
+
if done
|
134
|
+
puts "Done replaying #{progress} events"
|
135
|
+
else
|
136
|
+
puts "Replayed #{progress} events"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
66
140
|
##
|
67
141
|
# Returns the ids of aggregates that need a new snapshot.
|
68
142
|
#
|
@@ -72,7 +146,7 @@ SELECT event_type, event_json
|
|
72
146
|
query = %Q{
|
73
147
|
SELECT aggregate_id
|
74
148
|
FROM #{stream_table} stream
|
75
|
-
WHERE aggregate_id > COALESCE(#{quote last_aggregate_id}, '')
|
149
|
+
WHERE aggregate_id::varchar > COALESCE(#{quote last_aggregate_id}, '')
|
76
150
|
AND snapshot_threshold IS NOT NULL
|
77
151
|
AND snapshot_threshold <= (
|
78
152
|
(SELECT MAX(events.sequence_number) FROM #{event_table} events WHERE events.event_type <> #{quote snapshot_event_class.name} AND stream.aggregate_id = events.aggregate_id) -
|
@@ -81,7 +155,7 @@ SELECT aggregate_id
|
|
81
155
|
LIMIT #{quote limit}
|
82
156
|
FOR UPDATE
|
83
157
|
}
|
84
|
-
event_record_class.connection.select_all(query).map {|x| x['aggregate_id']}
|
158
|
+
event_record_class.connection.select_all(query).map { |x| x['aggregate_id'] }
|
85
159
|
end
|
86
160
|
|
87
161
|
def find_event_stream(aggregate_id)
|
@@ -95,10 +169,16 @@ SELECT aggregate_id
|
|
95
169
|
|
96
170
|
private
|
97
171
|
|
172
|
+
def column_names
|
173
|
+
@column_names ||= event_record_class.column_names.reject { |c| c == 'id' }
|
174
|
+
end
|
175
|
+
|
98
176
|
def deserialize_event(event_hash)
|
99
177
|
event_type = event_hash.fetch("event_type")
|
100
178
|
event_json = Sequent::Core::Oj.strict_load(event_hash.fetch("event_json"))
|
101
179
|
resolve_event_type(event_type).deserialize_from_json(event_json)
|
180
|
+
rescue
|
181
|
+
raise DeserializeEventError.new(event_hash)
|
102
182
|
end
|
103
183
|
|
104
184
|
def resolve_event_type(event_type)
|
@@ -106,28 +186,52 @@ SELECT aggregate_id
|
|
106
186
|
end
|
107
187
|
|
108
188
|
def publish_events(events, event_handlers)
|
109
|
-
|
110
|
-
|
111
|
-
|
189
|
+
return if configuration.disable_event_handlers
|
190
|
+
event_handlers.each do |handler|
|
191
|
+
events.each do |event|
|
192
|
+
begin
|
193
|
+
handler.handle_message event
|
194
|
+
rescue
|
195
|
+
raise PublishEventError.new(handler.class, event)
|
196
|
+
end
|
112
197
|
end
|
113
198
|
end
|
114
199
|
end
|
115
200
|
|
116
201
|
def store_events(command, streams_with_events = [])
|
117
202
|
command_record = CommandRecord.create!(command: command)
|
118
|
-
streams_with_events.
|
203
|
+
event_records = streams_with_events.flat_map do |event_stream, uncommitted_events|
|
119
204
|
unless event_stream.stream_record_id
|
120
205
|
stream_record = stream_record_class.new
|
121
206
|
stream_record.event_stream = event_stream
|
122
207
|
stream_record.save!
|
123
208
|
event_stream.stream_record_id = stream_record.id
|
124
209
|
end
|
125
|
-
uncommitted_events.
|
126
|
-
|
210
|
+
uncommitted_events.map do |event|
|
211
|
+
values = {
|
212
|
+
command_record_id: command_record.id,
|
213
|
+
stream_record_id: event_stream.stream_record_id,
|
214
|
+
aggregate_id: event.aggregate_id,
|
215
|
+
sequence_number: event.sequence_number,
|
216
|
+
event_type: event.class.name,
|
217
|
+
event_json: event_record_class.serialize_to_json(event),
|
218
|
+
created_at: event.created_at
|
219
|
+
}
|
220
|
+
values = values.merge(organization_id: event.organization_id) if event.respond_to?(:organization_id)
|
221
|
+
|
222
|
+
event_record_class.new(values)
|
127
223
|
end
|
128
224
|
end
|
225
|
+
connection = event_record_class.connection
|
226
|
+
values = event_records
|
227
|
+
.map { |r| "(#{column_names.map { |c| connection.quote(r[c.to_sym]) }.join(',')})" }
|
228
|
+
.join(',')
|
229
|
+
columns = column_names.map { |c| connection.quote_column_name(c) }.join(',')
|
230
|
+
sql = %Q{insert into #{connection.quote_table_name(event_record_class.table_name)} (#{columns}) values #{values}}
|
231
|
+
event_record_class.connection.insert(sql)
|
232
|
+
rescue ActiveRecord::RecordNotUnique
|
233
|
+
fail OptimisticLockingError.new
|
129
234
|
end
|
130
235
|
end
|
131
|
-
|
132
236
|
end
|
133
237
|
end
|