sequent 0.1.4 → 0.1.5
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.
- checksums.yaml +4 -4
- data/db/sequent_schema.rb +53 -0
- data/lib/sequent/configuration.rb +49 -0
- data/lib/sequent/core/aggregate_repository.rb +30 -19
- data/lib/sequent/core/aggregate_root.rb +59 -15
- data/lib/sequent/core/aggregate_snapshotter.rb +34 -0
- data/lib/sequent/core/base_command_handler.rb +4 -6
- data/lib/sequent/core/command_service.rb +29 -45
- data/lib/sequent/core/core.rb +2 -1
- data/lib/sequent/core/event.rb +4 -2
- data/lib/sequent/core/event_record.rb +4 -9
- data/lib/sequent/core/event_store.rb +80 -62
- data/lib/sequent/core/ext/ext.rb +9 -0
- data/lib/sequent/core/helpers/attribute_support.rb +0 -2
- data/lib/sequent/core/helpers/param_support.rb +17 -15
- data/lib/sequent/core/helpers/self_applier.rb +2 -4
- data/lib/sequent/core/snapshots.rb +23 -0
- data/lib/sequent/core/stream_record.rb +36 -0
- data/lib/sequent/core/tenant_event_store.rb +11 -5
- data/lib/sequent/migrations/migrate_events.rb +7 -4
- data/lib/sequent/sequent.rb +26 -0
- data/lib/sequent/test/command_handler_helpers.rb +48 -11
- data/lib/version.rb +1 -1
- metadata +35 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 814f54e9a769f6f73fc7c0cab26eebc4b9a191e1
|
4
|
+
data.tar.gz: c346224df919223cba73db9fe74097c235bacd32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e39adc404ed14163bd5b54b4ebdc558a12d4d56be8b60ad54140ee1cbc9102d89e17359a08d7a37eeeb299b99788359777d9f6cf778362969cb45b3182d493a
|
7
|
+
data.tar.gz: 667ab8ca0e0b5255184f162210d33a6775b26e952e8868a42a6a8a8ef408b24cec46f118c6fa9400b71a01632a0fb32388e2e23a6ab9362301f2247c2730726a
|
@@ -0,0 +1,53 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
|
3
|
+
create_table "event_records", :force => true do |t|
|
4
|
+
t.string "aggregate_id", :null => false
|
5
|
+
t.string "organization_id"
|
6
|
+
t.integer "sequence_number", :null => false
|
7
|
+
t.datetime "created_at", :null => false
|
8
|
+
t.string "event_type", :null => false
|
9
|
+
t.text "event_json", :null => false
|
10
|
+
t.integer "command_record_id", :null => false
|
11
|
+
t.integer "stream_record_id", :null => false
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table "command_records", :force => true do |t|
|
15
|
+
t.string "organization_id"
|
16
|
+
t.string "user_id"
|
17
|
+
t.string "aggregate_id"
|
18
|
+
t.string "command_type", :null => false
|
19
|
+
t.text "command_json", :null => false
|
20
|
+
t.datetime "created_at", :null => false
|
21
|
+
end
|
22
|
+
|
23
|
+
execute %Q{
|
24
|
+
CREATE UNIQUE INDEX unique_event_per_aggregate ON event_records (
|
25
|
+
aggregate_id,
|
26
|
+
sequence_number,
|
27
|
+
(CASE event_type WHEN 'Sequent::Core::SnapshotEvent' THEN 0 ELSE 1 END)
|
28
|
+
)
|
29
|
+
}
|
30
|
+
execute %Q{
|
31
|
+
CREATE INDEX snapshot_events ON event_records (aggregate_id, sequence_number DESC) WHERE event_type = 'Sequent::Core::SnapshotEvent'
|
32
|
+
}
|
33
|
+
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
|
+
add_index "event_records", ["event_type"], :name => "index_event_records_on_event_type"
|
36
|
+
add_index "event_records", ["created_at"], :name => "index_event_records_on_created_at"
|
37
|
+
|
38
|
+
create_table "stream_records", :force => true do |t|
|
39
|
+
t.datetime "created_at", :null => false
|
40
|
+
t.string "aggregate_type", :null => false
|
41
|
+
t.string "aggregate_id", :null => false
|
42
|
+
t.integer "snapshot_threshold"
|
43
|
+
end
|
44
|
+
|
45
|
+
add_index "stream_records", ["aggregate_id"], :name => "index_stream_records_on_aggregate_id", :unique => true
|
46
|
+
execute %q{
|
47
|
+
ALTER TABLE event_records ADD CONSTRAINT command_fkey FOREIGN KEY (command_record_id) REFERENCES command_records (id)
|
48
|
+
}
|
49
|
+
execute %q{
|
50
|
+
ALTER TABLE event_records ADD CONSTRAINT stream_fkey FOREIGN KEY (stream_record_id) REFERENCES stream_records (id)
|
51
|
+
}
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'core/event_store'
|
2
|
+
require_relative 'core/command_service'
|
3
|
+
require_relative 'core/transactions/no_transactions'
|
4
|
+
require_relative 'core/aggregate_repository'
|
5
|
+
|
6
|
+
module Sequent
|
7
|
+
class Configuration
|
8
|
+
attr_reader :aggregate_repository
|
9
|
+
|
10
|
+
attr_accessor :event_store,
|
11
|
+
:command_service,
|
12
|
+
:event_record_class,
|
13
|
+
:stream_record_class,
|
14
|
+
:snapshot_event_class,
|
15
|
+
:transaction_provider
|
16
|
+
|
17
|
+
attr_accessor :command_handlers,
|
18
|
+
:command_filters
|
19
|
+
|
20
|
+
attr_accessor :event_handlers
|
21
|
+
|
22
|
+
def self.instance
|
23
|
+
@instance ||= new
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.reset
|
27
|
+
@instance = new
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
self.command_handlers = []
|
32
|
+
self.command_filters = []
|
33
|
+
self.event_handlers = []
|
34
|
+
|
35
|
+
self.event_store = Sequent::Core::EventStore.new(self)
|
36
|
+
self.command_service = Sequent::Core::CommandService.new(self)
|
37
|
+
self.event_record_class = Sequent::Core::EventRecord
|
38
|
+
self.stream_record_class = Sequent::Core::StreamRecord
|
39
|
+
self.snapshot_event_class = Sequent::Core::SnapshotEvent
|
40
|
+
self.transaction_provider = Sequent::Core::Transactions::NoTransactions.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def event_store=(event_store)
|
44
|
+
@event_store = event_store
|
45
|
+
@aggregate_repository = Sequent::Core::AggregateRepository.new(event_store)
|
46
|
+
self.command_handlers.each { |c| c.repository = @aggregate_repository }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -16,12 +16,20 @@ module Sequent
|
|
16
16
|
# Key used in thread local
|
17
17
|
AGGREGATES_KEY = 'Sequent::Core::AggregateRepository::aggregates'.to_sym
|
18
18
|
|
19
|
+
attr_reader :event_store
|
20
|
+
|
19
21
|
class NonUniqueAggregateId < Exception
|
20
22
|
def initialize(existing, new)
|
21
23
|
super "Duplicate aggregate #{new} with same key as existing #{existing}"
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
27
|
+
class AggregateNotFound < Exception
|
28
|
+
def initialize(id)
|
29
|
+
super "Aggregate with id #{id} not found"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
25
33
|
def initialize(event_store)
|
26
34
|
@event_store = event_store
|
27
35
|
clear
|
@@ -33,8 +41,9 @@ module Sequent
|
|
33
41
|
# and all uncammited_events are stored in the +event_store+
|
34
42
|
#
|
35
43
|
def add_aggregate(aggregate)
|
36
|
-
|
37
|
-
|
44
|
+
existing = aggregates[aggregate.id]
|
45
|
+
if existing && !existing.equal?(aggregate)
|
46
|
+
raise NonUniqueAggregateId.new(aggregate, aggregates[aggregate.id])
|
38
47
|
else
|
39
48
|
aggregates[aggregate.id] = aggregate
|
40
49
|
end
|
@@ -47,17 +56,17 @@ module Sequent
|
|
47
56
|
|
48
57
|
# Loads aggregate by given id and class
|
49
58
|
# Returns the one in the current Unit Of Work otherwise loads it from history.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
result
|
57
|
-
else
|
58
|
-
events = @event_store.load_events(aggregate_id)
|
59
|
-
aggregates[aggregate_id] = clazz.load_from_history(events)
|
59
|
+
def load_aggregate(aggregate_id, clazz = nil)
|
60
|
+
result = aggregates.fetch(aggregate_id) do |aggregate_id|
|
61
|
+
stream, events = @event_store.load_events(aggregate_id)
|
62
|
+
raise AggregateNotFound.new(aggregate_id) unless stream
|
63
|
+
aggregate_class = Class.const_get(stream.aggregate_type)
|
64
|
+
aggregates[aggregate_id] = aggregate_class.load_from_history(stream, events)
|
60
65
|
end
|
66
|
+
|
67
|
+
raise TypeError, "#{result.class} is not a #{clazz}" if result && clazz && !(result.class <= clazz)
|
68
|
+
|
69
|
+
result
|
61
70
|
end
|
62
71
|
|
63
72
|
# Gets all uncommitted_events from the 'registered' aggregates
|
@@ -68,11 +77,13 @@ module Sequent
|
|
68
77
|
# This is all abstracted away if you use the Sequent::Core::CommandService
|
69
78
|
#
|
70
79
|
def commit(command)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
updated_aggregates = aggregates.values.reject {|x| x.uncommitted_events.empty?}
|
81
|
+
return if updated_aggregates.empty?
|
82
|
+
streams_with_events = updated_aggregates.map do |aggregate|
|
83
|
+
[ aggregate.event_stream, aggregate.uncommitted_events ]
|
84
|
+
end
|
85
|
+
updated_aggregates.each(&:clear_events)
|
86
|
+
store_events command, streams_with_events
|
76
87
|
end
|
77
88
|
|
78
89
|
# Clears the Unit of Work.
|
@@ -86,8 +97,8 @@ module Sequent
|
|
86
97
|
Thread.current[AGGREGATES_KEY]
|
87
98
|
end
|
88
99
|
|
89
|
-
def store_events(command,
|
90
|
-
@event_store.commit_events(command,
|
100
|
+
def store_events(command, streams_with_events)
|
101
|
+
@event_store.commit_events(command, streams_with_events)
|
91
102
|
end
|
92
103
|
end
|
93
104
|
end
|
@@ -1,19 +1,51 @@
|
|
1
|
+
require 'base64'
|
1
2
|
require_relative 'helpers/self_applier'
|
3
|
+
require_relative 'stream_record'
|
2
4
|
|
3
5
|
module Sequent
|
4
6
|
module Core
|
7
|
+
module SnapshotConfiguration
|
8
|
+
module ClassMethods
|
9
|
+
##
|
10
|
+
# Enable snapshots for this aggregate. The aggregate instance
|
11
|
+
# must define the *load_from_snapshot* and *save_to_snapshot*
|
12
|
+
# methods.
|
13
|
+
#
|
14
|
+
def enable_snapshots(default_threshold: 20)
|
15
|
+
@snapshot_default_threshold = default_threshold
|
16
|
+
end
|
17
|
+
|
18
|
+
def snapshots_enabled?
|
19
|
+
!snapshot_default_threshold.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :snapshot_default_threshold
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(host_class)
|
26
|
+
host_class.extend(ClassMethods)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
5
30
|
# Base class for all your domain classes.
|
6
31
|
#
|
7
32
|
# +load_from_history+ functionality to be loaded_from_history, meaning a stream of events.
|
8
33
|
#
|
9
34
|
class AggregateRoot
|
10
35
|
include Helpers::SelfApplier
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
36
|
+
include SnapshotConfiguration
|
37
|
+
|
38
|
+
attr_reader :id, :uncommitted_events, :sequence_number, :event_stream
|
39
|
+
|
40
|
+
def self.load_from_history(stream, events)
|
41
|
+
first, *rest = events
|
42
|
+
if first.is_a? SnapshotEvent
|
43
|
+
aggregate_root = Marshal.load(Base64.decode64(first.data))
|
44
|
+
rest.each { |x| aggregate_root.apply_event(x) }
|
45
|
+
else
|
46
|
+
aggregate_root = allocate() # allocate without calling new
|
47
|
+
aggregate_root.load_from_history(stream, events)
|
48
|
+
end
|
17
49
|
aggregate_root
|
18
50
|
end
|
19
51
|
|
@@ -21,23 +53,36 @@ module Sequent
|
|
21
53
|
@id = id
|
22
54
|
@uncommitted_events = []
|
23
55
|
@sequence_number = 1
|
56
|
+
@event_stream = EventStream.new aggregate_type: self.class.name,
|
57
|
+
aggregate_id: id,
|
58
|
+
snapshot_threshold: self.class.snapshot_default_threshold
|
24
59
|
end
|
25
60
|
|
26
|
-
def load_from_history(events)
|
61
|
+
def load_from_history(stream, events)
|
27
62
|
raise "Empty history" if events.empty?
|
28
63
|
@id = events.first.aggregate_id
|
29
64
|
@uncommitted_events = []
|
30
|
-
@sequence_number =
|
31
|
-
|
65
|
+
@sequence_number = 1
|
66
|
+
@event_stream = stream
|
67
|
+
events.each { |event| apply_event(event) }
|
32
68
|
end
|
33
69
|
|
34
70
|
def to_s
|
35
71
|
"#{self.class.name}: #{@id}"
|
36
72
|
end
|
37
73
|
|
38
|
-
|
39
74
|
def clear_events
|
40
|
-
uncommitted_events
|
75
|
+
@uncommitted_events = []
|
76
|
+
end
|
77
|
+
|
78
|
+
def take_snapshot!
|
79
|
+
snapshot = build_event SnapshotEvent, data: Base64.encode64(Marshal.dump(self))
|
80
|
+
@uncommitted_events << snapshot
|
81
|
+
end
|
82
|
+
|
83
|
+
def apply_event(event)
|
84
|
+
handle_message(event)
|
85
|
+
@sequence_number = event.sequence_number + 1
|
41
86
|
end
|
42
87
|
|
43
88
|
protected
|
@@ -54,9 +99,8 @@ module Sequent
|
|
54
99
|
#
|
55
100
|
def apply(event, params={})
|
56
101
|
event = build_event(event, params) if event.is_a?(Class)
|
57
|
-
|
102
|
+
apply_event(event)
|
58
103
|
@uncommitted_events << event
|
59
|
-
@sequence_number += 1
|
60
104
|
end
|
61
105
|
end
|
62
106
|
|
@@ -71,10 +115,10 @@ module Sequent
|
|
71
115
|
@organization_id = organization_id
|
72
116
|
end
|
73
117
|
|
74
|
-
def load_from_history(events)
|
118
|
+
def load_from_history(stream, events)
|
75
119
|
raise "Empty history" if events.empty?
|
76
120
|
@organization_id = events.first.organization_id
|
77
|
-
super
|
121
|
+
super
|
78
122
|
end
|
79
123
|
|
80
124
|
protected
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
class SnapshotCommand < Sequent::Core::BaseCommand
|
4
|
+
attrs limit: Integer
|
5
|
+
end
|
6
|
+
|
7
|
+
class AggregateSnapshotter < BaseCommandHandler
|
8
|
+
|
9
|
+
def handles_message?(message)
|
10
|
+
message.is_a? SnapshotCommand
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Take up to `limit` snapshots when needed. Throws `:done` when done.
|
15
|
+
#
|
16
|
+
on SnapshotCommand do |command|
|
17
|
+
aggregate_ids = repository.event_store.aggregates_that_need_snapshots(@last_aggregate_id, command.limit)
|
18
|
+
aggregate_ids.each do |aggregate_id|
|
19
|
+
take_snapshot!(aggregate_id)
|
20
|
+
end
|
21
|
+
@last_aggregate_id = aggregate_ids.last
|
22
|
+
throw :done if @last_aggregate_id.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def take_snapshot!(aggregate_id)
|
26
|
+
aggregate = @repository.load_aggregate(aggregate_id)
|
27
|
+
Sequent.logger.info "Taking snapshot for aggregate #{aggregate}"
|
28
|
+
aggregate.take_snapshot!
|
29
|
+
rescue => e
|
30
|
+
Sequent.logger.warn "Failed to take snapshot for aggregate #{aggregate_id}: #{e}", e.inspect
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -12,7 +12,7 @@ module Sequent
|
|
12
12
|
# repository.add_aggregate Invoice.new(command.aggregate_id)
|
13
13
|
# end
|
14
14
|
#
|
15
|
-
# on
|
15
|
+
# on PayInvoiceCommand do |command|
|
16
16
|
# do_with_aggregate(command, Invoice) {|invoice|invoice.pay(command.pay_date)}
|
17
17
|
# end
|
18
18
|
# end
|
@@ -20,7 +20,9 @@ module Sequent
|
|
20
20
|
include Sequent::Core::Helpers::SelfApplier,
|
21
21
|
Sequent::Core::Helpers::UuidHelper
|
22
22
|
|
23
|
-
|
23
|
+
attr_accessor :repository
|
24
|
+
|
25
|
+
def initialize(repository = Sequent.configuration.aggregate_repository)
|
24
26
|
@repository = repository
|
25
27
|
end
|
26
28
|
|
@@ -30,10 +32,6 @@ module Sequent
|
|
30
32
|
yield aggregate if block_given?
|
31
33
|
end
|
32
34
|
|
33
|
-
protected
|
34
|
-
def repository
|
35
|
-
@repository
|
36
|
-
end
|
37
35
|
end
|
38
36
|
end
|
39
37
|
end
|
@@ -2,21 +2,6 @@ require_relative 'transactions/no_transactions'
|
|
2
2
|
|
3
3
|
module Sequent
|
4
4
|
module Core
|
5
|
-
|
6
|
-
class CommandServiceConfiguration
|
7
|
-
attr_accessor :event_store,
|
8
|
-
:command_handler_classes,
|
9
|
-
:transaction_provider,
|
10
|
-
:filters
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@command_handler_classes = []
|
14
|
-
@transaction_provider = Sequent::Core::Transactions::NoTransactions.new
|
15
|
-
@filters = []
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
5
|
#
|
21
6
|
# Single point in the application where subclasses of Sequent::Core::BaseCommand
|
22
7
|
# are executed. This will initiate the entire flow of:
|
@@ -28,21 +13,7 @@ module Sequent
|
|
28
13
|
# * Unit of Work is cleared
|
29
14
|
#
|
30
15
|
class CommandService
|
31
|
-
|
32
|
-
class << self
|
33
|
-
attr_accessor :configuration,
|
34
|
-
:instance
|
35
|
-
end
|
36
|
-
|
37
|
-
# Creates a new CommandService and overwrites all existing config.
|
38
|
-
# The new CommandService can be retrieved via the +CommandService.instance+ method.
|
39
|
-
#
|
40
|
-
# If you don't want a singleton you can always instantiate it yourself using the +CommandService.new+.
|
41
|
-
def self.configure
|
42
|
-
self.configuration = CommandServiceConfiguration.new
|
43
|
-
yield(configuration) if block_given?
|
44
|
-
self.instance = CommandService.new(configuration)
|
45
|
-
end
|
16
|
+
attr_accessor :configuration
|
46
17
|
|
47
18
|
# +DefaultCommandServiceConfiguration+ Configuration class for the CommandService containing:
|
48
19
|
#
|
@@ -51,11 +22,7 @@ module Sequent
|
|
51
22
|
# +transaction_provider+ How to do transaction management. Defaults to Sequent::Core::Transactions::NoTransactions
|
52
23
|
# +filters+ List of filter that respond_to :execute(command). Can be useful to do extra checks (security and such).
|
53
24
|
def initialize(configuration = CommandServiceConfiguration.new)
|
54
|
-
|
55
|
-
@repository = AggregateRepository.new(configuration.event_store)
|
56
|
-
@filters = configuration.filters
|
57
|
-
@transaction_provider = configuration.transaction_provider
|
58
|
-
@command_handlers = configuration.command_handler_classes.map { |handler| handler.new(@repository) }
|
25
|
+
self.configuration = configuration
|
59
26
|
end
|
60
27
|
|
61
28
|
# Executes the given commands in a single transactional block as implemented by the +transaction_provider+
|
@@ -67,26 +34,47 @@ module Sequent
|
|
67
34
|
# * The +repository+ commits the command and all uncommitted_events resulting from the command
|
68
35
|
def execute_commands(*commands)
|
69
36
|
begin
|
70
|
-
|
37
|
+
transaction_provider.transactional do
|
71
38
|
commands.each do |command|
|
72
|
-
|
39
|
+
filters.each { |filter| filter.execute(command) }
|
73
40
|
|
74
41
|
raise CommandNotValid.new(command) unless command.valid?
|
75
42
|
parsed_command = command.parse_attrs_to_correct_types
|
76
|
-
|
77
|
-
|
43
|
+
command_handlers.select { |h| h.handles_message?(parsed_command) }.each { |h| h.handle_message parsed_command }
|
44
|
+
repository.commit(parsed_command)
|
78
45
|
end
|
79
46
|
end
|
80
47
|
ensure
|
81
|
-
|
48
|
+
repository.clear
|
82
49
|
end
|
83
50
|
|
84
51
|
end
|
85
52
|
|
86
53
|
def remove_event_handler(clazz)
|
87
|
-
|
54
|
+
event_store.remove_event_handler(clazz)
|
88
55
|
end
|
89
56
|
|
57
|
+
private
|
58
|
+
|
59
|
+
def event_store
|
60
|
+
configuration.event_store
|
61
|
+
end
|
62
|
+
|
63
|
+
def repository
|
64
|
+
configuration.aggregate_repository
|
65
|
+
end
|
66
|
+
|
67
|
+
def filters
|
68
|
+
configuration.command_filters
|
69
|
+
end
|
70
|
+
|
71
|
+
def transaction_provider
|
72
|
+
configuration.transaction_provider
|
73
|
+
end
|
74
|
+
|
75
|
+
def command_handlers
|
76
|
+
configuration.command_handlers
|
77
|
+
end
|
90
78
|
end
|
91
79
|
|
92
80
|
# Raised when BaseCommand.valid? returns false
|
@@ -106,9 +94,5 @@ module Sequent
|
|
106
94
|
errors(@command.class.to_s.underscore)
|
107
95
|
end
|
108
96
|
end
|
109
|
-
|
110
97
|
end
|
111
98
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
data/lib/sequent/core/core.rb
CHANGED
@@ -2,15 +2,16 @@ require_relative 'sequent_oj'
|
|
2
2
|
require_relative 'helpers/helpers'
|
3
3
|
require_relative 'record_sessions/record_sessions'
|
4
4
|
require_relative 'transactions/transactions'
|
5
|
+
require_relative 'event'
|
5
6
|
require_relative 'aggregate_repository'
|
6
7
|
require_relative 'aggregate_root'
|
7
8
|
require_relative 'base_command_handler'
|
8
9
|
require_relative 'command'
|
9
10
|
require_relative 'command_service'
|
10
|
-
require_relative 'event'
|
11
11
|
require_relative 'value_object'
|
12
12
|
require_relative 'base_event_handler'
|
13
13
|
require_relative 'event_store'
|
14
14
|
require_relative 'tenant_event_store'
|
15
15
|
require_relative 'event_record'
|
16
16
|
require_relative 'command_record'
|
17
|
+
require_relative 'aggregate_snapshotter'
|
data/lib/sequent/core/event.rb
CHANGED
@@ -5,10 +5,9 @@ module Sequent
|
|
5
5
|
module Core
|
6
6
|
|
7
7
|
module SerializesEvent
|
8
|
-
|
9
8
|
def event
|
10
9
|
payload = Sequent::Core::Oj.strict_load(self.event_json)
|
11
|
-
Class.const_get(self.event_type
|
10
|
+
Class.const_get(self.event_type).deserialize_from_json(payload)
|
12
11
|
end
|
13
12
|
|
14
13
|
def event=(event)
|
@@ -19,7 +18,6 @@ module Sequent
|
|
19
18
|
self.created_at = event.created_at
|
20
19
|
self.event_json = Sequent::Core::Oj.dump(event.attributes)
|
21
20
|
end
|
22
|
-
|
23
21
|
end
|
24
22
|
|
25
23
|
class EventRecord < ActiveRecord::Base
|
@@ -27,15 +25,12 @@ module Sequent
|
|
27
25
|
|
28
26
|
self.table_name = "event_records"
|
29
27
|
|
28
|
+
belongs_to :stream_record
|
30
29
|
belongs_to :command_record
|
31
30
|
|
32
|
-
validates_presence_of :aggregate_id, :sequence_number, :event_type, :event_json
|
33
|
-
validates_numericality_of :sequence_number
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
validates_presence_of :aggregate_id, :sequence_number, :event_type, :event_json, :stream_record, :command_record
|
32
|
+
validates_numericality_of :sequence_number, only_integer: true, greater_than: 0
|
37
33
|
end
|
38
34
|
|
39
35
|
end
|
40
36
|
end
|
41
|
-
|
@@ -1,90 +1,105 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require_relative 'event_record'
|
2
3
|
require_relative 'sequent_oj'
|
3
4
|
|
4
5
|
module Sequent
|
5
6
|
module Core
|
6
|
-
class EventStoreConfiguration
|
7
|
-
attr_accessor :record_class, :event_handlers
|
8
|
-
|
9
|
-
def initialize(record_class = Sequent::Core::EventRecord, event_handlers = [])
|
10
|
-
@record_class = record_class
|
11
|
-
@event_handlers = event_handlers
|
12
|
-
end
|
13
|
-
end
|
14
7
|
|
15
8
|
class EventStore
|
9
|
+
include ActiveRecord::ConnectionAdapters::Quoting
|
10
|
+
extend Forwardable
|
16
11
|
|
17
|
-
|
18
|
-
|
19
|
-
:instance
|
20
|
-
end
|
21
|
-
|
22
|
-
# Creates a new EventStore and overwrites all existing config.
|
23
|
-
# The new EventStore can be retrieved via the +EventStore.instance+ method.
|
24
|
-
#
|
25
|
-
# If you don't want a singleton you can always instantiate it yourself using the +EventStore.new+.
|
26
|
-
def self.configure
|
27
|
-
self.configuration = EventStoreConfiguration.new
|
28
|
-
yield(configuration) if block_given?
|
29
|
-
EventStore.instance = EventStore.new(configuration)
|
30
|
-
end
|
12
|
+
attr_accessor :configuration
|
13
|
+
def_delegators :@configuration, :stream_record_class, :event_record_class, :snapshot_event_class, :event_handlers
|
31
14
|
|
32
|
-
def initialize(configuration =
|
33
|
-
|
34
|
-
@
|
15
|
+
def initialize(configuration = Sequent.configuration)
|
16
|
+
self.configuration = configuration
|
17
|
+
@event_types = ThreadSafe::Cache.new
|
35
18
|
end
|
36
19
|
|
37
20
|
##
|
38
21
|
# Stores the events in the EventStore and publishes the events
|
39
22
|
# to the registered event_handlers.
|
40
23
|
#
|
41
|
-
|
42
|
-
|
43
|
-
|
24
|
+
# Streams_with_Events is an enumerable of pairs from
|
25
|
+
# `StreamRecord` to arrays of uncommitted `Event`s.
|
26
|
+
#
|
27
|
+
def commit_events(command, streams_with_events)
|
28
|
+
store_events(command, streams_with_events)
|
29
|
+
publish_events(streams_with_events.flat_map {|_, events| events}, event_handlers)
|
44
30
|
end
|
45
31
|
|
46
32
|
##
|
47
33
|
# Returns all events for the aggregate ordered by sequence_number
|
48
34
|
#
|
49
35
|
def load_events(aggregate_id)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
36
|
+
stream = stream_record_class.where(aggregate_id: aggregate_id).first!
|
37
|
+
events = event_record_class.connection.select_all(%Q{
|
38
|
+
SELECT event_type, event_json
|
39
|
+
FROM #{quote_table_name event_record_class.table_name}
|
40
|
+
WHERE aggregate_id = #{quote aggregate_id}
|
41
|
+
AND sequence_number >= COALESCE((SELECT MAX(sequence_number)
|
42
|
+
FROM #{quote_table_name event_record_class.table_name}
|
43
|
+
WHERE event_type = #{quote snapshot_event_class.name}
|
44
|
+
AND aggregate_id = #{quote aggregate_id}), 0)
|
45
|
+
ORDER BY sequence_number ASC, (CASE event_type WHEN #{quote snapshot_event_class.name} THEN 0 ELSE 1 END) ASC
|
46
|
+
}).map! do |event_hash|
|
47
|
+
deserialize_event(event_hash)
|
58
48
|
end
|
49
|
+
[stream.event_stream, events]
|
59
50
|
end
|
60
51
|
|
61
52
|
##
|
62
53
|
# Replays all events in the event store to the registered event_handlers.
|
63
54
|
#
|
64
|
-
# @param block that returns the
|
55
|
+
# @param block that returns the events.
|
65
56
|
def replay_events
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
57
|
+
events = yield.map {|event_hash| deserialize_event(event_hash)}
|
58
|
+
publish_events(events, event_handlers)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Returns the ids of aggregates that need a new snapshot.
|
63
|
+
#
|
64
|
+
def aggregates_that_need_snapshots(last_aggregate_id, limit = 10)
|
65
|
+
stream_table = quote_table_name stream_record_class.table_name
|
66
|
+
event_table = quote_table_name event_record_class.table_name
|
67
|
+
query = %Q{
|
68
|
+
SELECT aggregate_id
|
69
|
+
FROM #{stream_table} stream
|
70
|
+
WHERE aggregate_id > COALESCE(#{quote last_aggregate_id}, '')
|
71
|
+
AND snapshot_threshold IS NOT NULL
|
72
|
+
AND snapshot_threshold <= (
|
73
|
+
(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) -
|
74
|
+
COALESCE((SELECT MAX(snapshots.sequence_number) FROM #{event_table} snapshots WHERE snapshots.event_type = #{quote snapshot_event_class.name} AND stream.aggregate_id = snapshots.aggregate_id), 0))
|
75
|
+
ORDER BY aggregate_id
|
76
|
+
LIMIT #{quote limit}
|
77
|
+
FOR UPDATE
|
78
|
+
}
|
79
|
+
event_record_class.connection.select_all(query).map {|x| x['aggregate_id']}
|
79
80
|
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
def find_event_stream(aggregate_id)
|
83
|
+
record = stream_record_class.where(aggregate_id: aggregate_id).first
|
84
|
+
if record
|
85
|
+
record.event_stream
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end
|
84
89
|
end
|
85
90
|
|
86
91
|
private
|
87
92
|
|
93
|
+
def deserialize_event(event_hash)
|
94
|
+
event_type = event_hash.fetch("event_type")
|
95
|
+
event_json = Sequent::Core::Oj.strict_load(event_hash.fetch("event_json"))
|
96
|
+
resolve_event_type(event_type).deserialize_from_json(event_json)
|
97
|
+
end
|
98
|
+
|
99
|
+
def resolve_event_type(event_type)
|
100
|
+
@event_types.fetch_or_store(event_type) { |k| Class.const_get(k) }
|
101
|
+
end
|
102
|
+
|
88
103
|
def publish_events(events, event_handlers)
|
89
104
|
events.each do |event|
|
90
105
|
event_handlers.each do |handler|
|
@@ -93,17 +108,20 @@ module Sequent
|
|
93
108
|
end
|
94
109
|
end
|
95
110
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
111
|
+
def store_events(command, streams_with_events = [])
|
112
|
+
command_record = CommandRecord.create!(command: command)
|
113
|
+
streams_with_events.each do |event_stream, uncommitted_events|
|
114
|
+
unless event_stream.stream_record_id
|
115
|
+
stream_record = stream_record_class.new
|
116
|
+
stream_record.event_stream = event_stream
|
117
|
+
stream_record.save!
|
118
|
+
event_stream.stream_record_id = stream_record.id
|
119
|
+
end
|
120
|
+
uncommitted_events.each do |event|
|
121
|
+
event_record_class.create!(command_record: command_record, stream_record_id: event_stream.stream_record_id, event: event)
|
122
|
+
end
|
104
123
|
end
|
105
124
|
end
|
106
|
-
|
107
125
|
end
|
108
126
|
|
109
127
|
end
|
data/lib/sequent/core/ext/ext.rb
CHANGED
@@ -12,28 +12,30 @@ module Sequent
|
|
12
12
|
module ParamSupport
|
13
13
|
module ClassMethods
|
14
14
|
def from_params(params = {})
|
15
|
-
|
16
|
-
params = HashWithIndifferentAccess.new(params)
|
17
|
-
result.class.types.each do |attribute, type|
|
18
|
-
value = params[attribute]
|
19
|
-
|
20
|
-
next if value.blank?
|
21
|
-
if type.respond_to? :from_params
|
22
|
-
value = type.from_params(value)
|
23
|
-
elsif type.is_a? Sequent::Core::Helpers::ArrayWithType
|
24
|
-
value = value.map { |v| type.item_type.from_params(v) }
|
25
|
-
end
|
26
|
-
result.instance_variable_set(:"@#{attribute}", value)
|
27
|
-
end
|
28
|
-
result
|
15
|
+
allocate.tap { |x| x.from_params(params) }
|
29
16
|
end
|
30
|
-
|
31
17
|
end
|
18
|
+
|
32
19
|
# extend host class with class methods when we're included
|
33
20
|
def self.included(host_class)
|
34
21
|
host_class.extend(ClassMethods)
|
35
22
|
end
|
36
23
|
|
24
|
+
def from_params(params)
|
25
|
+
params = HashWithIndifferentAccess.new(params)
|
26
|
+
self.class.types.each do |attribute, type|
|
27
|
+
value = params[attribute]
|
28
|
+
|
29
|
+
next if value.blank?
|
30
|
+
if type.respond_to? :from_params
|
31
|
+
value = type.from_params(value)
|
32
|
+
elsif type.is_a? Sequent::Core::Helpers::ArrayWithType
|
33
|
+
value = value.map { |v| type.item_type.from_params(v) }
|
34
|
+
end
|
35
|
+
instance_variable_set(:"@#{attribute}", value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
37
39
|
def to_params(root)
|
38
40
|
make_params root, as_params
|
39
41
|
end
|
@@ -19,12 +19,11 @@ module Sequent
|
|
19
19
|
module ClassMethods
|
20
20
|
|
21
21
|
def on(*message_classes, &block)
|
22
|
-
|
23
|
-
message_classes.each { |message_class| @message_mapping[message_class] = block }
|
22
|
+
message_classes.each { |message_class| message_mapping[message_class] = block }
|
24
23
|
end
|
25
24
|
|
26
25
|
def message_mapping
|
27
|
-
@message_mapping
|
26
|
+
@message_mapping ||= {}
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
@@ -42,4 +41,3 @@ module Sequent
|
|
42
41
|
end
|
43
42
|
end
|
44
43
|
end
|
45
|
-
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Core
|
3
|
+
class Snapshots
|
4
|
+
|
5
|
+
def aggregates_that_need_snapshots(events_since_last_snapshot: 20, limit: 10, last_aggregate_id: nil)
|
6
|
+
query = %Q{
|
7
|
+
SELECT aggregate_id
|
8
|
+
FROM event_records events
|
9
|
+
WHERE aggregate_id > '#{last_aggregate_id}'
|
10
|
+
GROUP BY aggregate_id
|
11
|
+
HAVING MAX(sequence_number) - (COALESCE((SELECT MAX(sequence_number)
|
12
|
+
FROM event_records snapshots
|
13
|
+
WHERE event_type = 'Sequent::Core::SnapshotEvent'
|
14
|
+
AND snapshots.aggregate_id = events.aggregate_id), 0)) > #{events_since_last_snapshot}
|
15
|
+
ORDER BY aggregate_id
|
16
|
+
LIMIT #{limit};
|
17
|
+
}
|
18
|
+
@record_class.connection.select_all(query).to_a
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Core
|
5
|
+
class EventStream
|
6
|
+
attr_accessor :aggregate_type, :aggregate_id, :snapshot_threshold, :stream_record_id
|
7
|
+
|
8
|
+
def initialize(aggregate_type:, aggregate_id:, snapshot_threshold: nil, stream_record_id: nil)
|
9
|
+
@aggregate_type = aggregate_type
|
10
|
+
@aggregate_id = aggregate_id
|
11
|
+
@snapshot_threshold = snapshot_threshold
|
12
|
+
@stream_record_id = stream_record_id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class StreamRecord < ActiveRecord::Base
|
17
|
+
|
18
|
+
self.table_name = "stream_records"
|
19
|
+
|
20
|
+
validates_presence_of :aggregate_type, :aggregate_id
|
21
|
+
validates_numericality_of :snapshot_threshold, only_integer: true, greater_than: 0, allow_nil: true
|
22
|
+
|
23
|
+
has_many :events
|
24
|
+
|
25
|
+
def event_stream
|
26
|
+
EventStream.new(aggregate_type: aggregate_type, aggregate_id: aggregate_id, snapshot_threshold: snapshot_threshold, stream_record_id: id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def event_stream=(data)
|
30
|
+
self.aggregate_type = data.aggregate_type
|
31
|
+
self.aggregate_id = data.aggregate_id
|
32
|
+
self.snapshot_threshold = data.snapshot_threshold
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -4,15 +4,21 @@
|
|
4
4
|
module Sequent
|
5
5
|
module Core
|
6
6
|
class TenantEventStore < EventStore
|
7
|
-
|
8
7
|
def replay_events_for(organization_id)
|
9
8
|
replay_events do
|
10
|
-
|
11
|
-
|
9
|
+
event_record_class.connection.select_all(%Q{
|
10
|
+
SELECT events.event_type, events.event_json
|
11
|
+
FROM #{quote_table_name event_record_class.table_name} events
|
12
|
+
WHERE events.aggregate_id IN (SELECT aggregates.aggregate_id
|
13
|
+
FROM #{quote_table_name event_record_class.table_name} aggregates
|
14
|
+
WHERE aggregates.organization_id = #{quote organization_id}
|
15
|
+
AND aggregates.sequence_number = 1
|
16
|
+
AND aggregates.event_type <> #{quote snapshot_event_class.name})
|
17
|
+
AND events.event_type <> #{quote snapshot_event_class.name}
|
18
|
+
ORDER BY events.id
|
19
|
+
})
|
12
20
|
end
|
13
21
|
end
|
14
|
-
|
15
22
|
end
|
16
|
-
|
17
23
|
end
|
18
24
|
end
|
@@ -36,10 +36,14 @@ module Sequent
|
|
36
36
|
def execute_migrations(current_version, new_version, &after_migration_block)
|
37
37
|
if current_version != new_version and current_version > 0
|
38
38
|
((current_version + 1)..new_version).each do |upgrade_to_version|
|
39
|
-
migration_class =
|
40
|
-
|
39
|
+
migration_class = begin
|
40
|
+
Class.const_get("Database::MigrateToVersion#{upgrade_to_version}")
|
41
|
+
rescue NameError
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
if migration_class
|
41
45
|
begin
|
42
|
-
|
46
|
+
migration_class.new(@env).migrate
|
43
47
|
ensure
|
44
48
|
after_migration_block.call if after_migration_block
|
45
49
|
end
|
@@ -47,7 +51,6 @@ module Sequent
|
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
50
|
-
|
51
54
|
end
|
52
55
|
end
|
53
56
|
end
|
data/lib/sequent/sequent.rb
CHANGED
@@ -1,3 +1,29 @@
|
|
1
1
|
require_relative 'core/core'
|
2
2
|
require_relative 'migrations/migrations'
|
3
3
|
require_relative 'test/test'
|
4
|
+
require_relative 'configuration'
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
module Sequent
|
9
|
+
def self.logger
|
10
|
+
@logger ||= Logger.new(STDOUT).tap {|l| l.level = Logger::INFO }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logger=(logger)
|
14
|
+
@logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.configure
|
18
|
+
yield Configuration.instance
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.configuration
|
22
|
+
Configuration.instance
|
23
|
+
end
|
24
|
+
|
25
|
+
# Short hand for Sequent.configuration.command_service
|
26
|
+
def self.command_service
|
27
|
+
configuration.command_service
|
28
|
+
end
|
29
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'thread_safe'
|
2
|
+
|
1
3
|
module Sequent
|
2
4
|
module Test
|
3
5
|
##
|
@@ -36,32 +38,68 @@ module Sequent
|
|
36
38
|
|
37
39
|
class FakeEventStore
|
38
40
|
def initialize
|
39
|
-
@
|
41
|
+
@event_streams = {}
|
42
|
+
@all_events = {}
|
40
43
|
@stored_events = []
|
41
44
|
end
|
42
45
|
|
43
46
|
def load_events(aggregate_id)
|
44
|
-
|
47
|
+
event_stream = @event_streams[aggregate_id]
|
48
|
+
return nil unless event_stream
|
49
|
+
[event_stream, deserialize_events(@all_events[aggregate_id])]
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_event_stream(aggregate_id)
|
53
|
+
@event_streams[aggregate_id]
|
45
54
|
end
|
46
55
|
|
47
56
|
def stored_events
|
48
57
|
deserialize_events(@stored_events)
|
49
58
|
end
|
50
59
|
|
51
|
-
def commit_events(_,
|
52
|
-
|
53
|
-
|
54
|
-
|
60
|
+
def commit_events(_, streams_with_events)
|
61
|
+
streams_with_events.each do |event_stream, events|
|
62
|
+
serialized = serialize_events(events)
|
63
|
+
@event_streams[event_stream.aggregate_id] = event_stream
|
64
|
+
@all_events[event_stream.aggregate_id] ||= []
|
65
|
+
@all_events[event_stream.aggregate_id] += serialized
|
66
|
+
@stored_events += serialized
|
67
|
+
end
|
55
68
|
end
|
56
69
|
|
57
70
|
def given_events(events)
|
58
|
-
|
71
|
+
commit_events(nil, to_event_streams(events))
|
59
72
|
@stored_events = []
|
60
73
|
end
|
61
74
|
|
62
75
|
private
|
76
|
+
|
77
|
+
def to_event_streams(events)
|
78
|
+
# Specs use a simple list of given events. We need a mapping from StreamRecord to the associated events for the event store.
|
79
|
+
streams_by_aggregate_id = {}
|
80
|
+
events.map do |event|
|
81
|
+
event_stream = streams_by_aggregate_id.fetch(event.aggregate_id) do |aggregate_id|
|
82
|
+
streams_by_aggregate_id[aggregate_id] =
|
83
|
+
find_event_stream(aggregate_id) ||
|
84
|
+
begin
|
85
|
+
aggregate_type = FakeEventStore.aggregate_type_for_event(event)
|
86
|
+
raise "cannot find aggregate type associated with creation event #{event}, did you include an event handler in your aggregate for this event?" unless aggregate_type
|
87
|
+
Sequent::Core::EventStream.new(aggregate_type: aggregate_type.name, aggregate_id: aggregate_id)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
[event_stream, [event]]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.aggregate_type_for_event(event)
|
95
|
+
@event_to_aggregate_type ||= ThreadSafe::Cache.new
|
96
|
+
@event_to_aggregate_type.fetch_or_store(event.class) do |klass|
|
97
|
+
Sequent::Core::AggregateRoot.descendants.find { |x| x.message_mapping.has_key?(klass) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
63
101
|
def serialize_events(events)
|
64
|
-
events.map { |event| [event.class.name
|
102
|
+
events.map { |event| [event.class.name, Sequent::Core::Oj.dump(event)] }
|
65
103
|
end
|
66
104
|
|
67
105
|
def deserialize_events(events)
|
@@ -73,7 +111,6 @@ module Sequent
|
|
73
111
|
end
|
74
112
|
|
75
113
|
def given_events *events
|
76
|
-
raise ArgumentError.new("events can not be nil") if events.compact.empty?
|
77
114
|
@event_store.given_events(events)
|
78
115
|
end
|
79
116
|
|
@@ -85,9 +122,9 @@ module Sequent
|
|
85
122
|
end
|
86
123
|
|
87
124
|
def then_events *events
|
88
|
-
@event_store.stored_events.map(&:class).
|
125
|
+
expect(@event_store.stored_events.map(&:class)).to eq(events.map(&:class))
|
89
126
|
@event_store.stored_events.zip(events).each do |actual, expected|
|
90
|
-
Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(actual.payload)).
|
127
|
+
expect(Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(actual.payload))).to eq(Sequent::Core::Oj.strict_load(Sequent::Core::Oj.dump(expected.payload))) if expected
|
91
128
|
end
|
92
129
|
end
|
93
130
|
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lars Vonk
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-
|
13
|
+
date: 2015-06-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -68,6 +68,20 @@ dependencies:
|
|
68
68
|
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '2.10'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: thread_safe
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.3.5
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 0.3.5
|
71
85
|
- !ruby/object:Gem::Dependency
|
72
86
|
name: rspec
|
73
87
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +138,20 @@ dependencies:
|
|
124
138
|
- - "~>"
|
125
139
|
- !ruby/object:Gem::Version
|
126
140
|
version: '10.4'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: pry
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - "~>"
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0.10'
|
148
|
+
type: :development
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - "~>"
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0.10'
|
127
155
|
description: Sequent is a CQRS and event sourcing framework for Ruby.
|
128
156
|
email:
|
129
157
|
- lars.vonk@gmail.com
|
@@ -133,9 +161,12 @@ executables: []
|
|
133
161
|
extensions: []
|
134
162
|
extra_rdoc_files: []
|
135
163
|
files:
|
164
|
+
- db/sequent_schema.rb
|
136
165
|
- lib/sequent.rb
|
166
|
+
- lib/sequent/configuration.rb
|
137
167
|
- lib/sequent/core/aggregate_repository.rb
|
138
168
|
- lib/sequent/core/aggregate_root.rb
|
169
|
+
- lib/sequent/core/aggregate_snapshotter.rb
|
139
170
|
- lib/sequent/core/base_command_handler.rb
|
140
171
|
- lib/sequent/core/base_event_handler.rb
|
141
172
|
- lib/sequent/core/command.rb
|
@@ -167,6 +198,8 @@ files:
|
|
167
198
|
- lib/sequent/core/record_sessions/record_sessions.rb
|
168
199
|
- lib/sequent/core/record_sessions/replay_events_session.rb
|
169
200
|
- lib/sequent/core/sequent_oj.rb
|
201
|
+
- lib/sequent/core/snapshots.rb
|
202
|
+
- lib/sequent/core/stream_record.rb
|
170
203
|
- lib/sequent/core/tenant_event_store.rb
|
171
204
|
- lib/sequent/core/transactions/active_record_transaction_provider.rb
|
172
205
|
- lib/sequent/core/transactions/no_transactions.rb
|