sequent 0.1.5 → 0.1.6
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/lib/sequent/core/aggregate_repository.rb +10 -5
- data/lib/sequent/core/aggregate_root.rb +2 -2
- data/lib/sequent/core/base_command_handler.rb +4 -0
- data/lib/sequent/core/base_event_handler.rb +3 -0
- data/lib/sequent/core/command_service.rb +6 -5
- data/lib/sequent/core/core.rb +1 -0
- data/lib/sequent/core/event_store.rb +6 -1
- data/lib/sequent/core/helpers/attribute_support.rb +4 -0
- data/lib/sequent/core/helpers/self_applier.rb +1 -1
- data/lib/sequent/core/record_sessions/replay_events_session.rb +1 -1
- data/lib/sequent/core/workflow.rb +13 -0
- data/lib/sequent/sequent.rb +0 -1
- data/lib/sequent/test.rb +4 -0
- data/lib/sequent/test/command_handler_helpers.rb +28 -6
- data/lib/sequent/test/event_handler_helpers.rb +71 -0
- data/lib/sequent/test/event_stream_helpers.rb +78 -0
- data/lib/version.rb +1 -1
- metadata +7 -4
- data/lib/sequent/test/test.rb +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e8e35986af2a9dcdb19a2d66b291606e852b96c
|
4
|
+
data.tar.gz: a049cc85e36f391e22fd35ae3d45061f81eb7562
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 319d6c1cb5397766edb05bb6239a083d51722490a85f7120c68a4973cda6d30d9a92a2efecaec0c0a0df42d864e80d4142ecc23ceea6255c344076f9703e45f1
|
7
|
+
data.tar.gz: e7768f63f1369006ed1a61397ce5d5195e35142a497113ffd6e03c34edffe56f49d20d3271b680b56b18752f3c32f91b8c72f4572fe01cafb4bf3fd16dc6e716
|
@@ -32,7 +32,6 @@ module Sequent
|
|
32
32
|
|
33
33
|
def initialize(event_store)
|
34
34
|
@event_store = event_store
|
35
|
-
clear
|
36
35
|
end
|
37
36
|
|
38
37
|
# Adds the given aggregate to the repository (or unit of work).
|
@@ -57,7 +56,7 @@ module Sequent
|
|
57
56
|
# Loads aggregate by given id and class
|
58
57
|
# Returns the one in the current Unit Of Work otherwise loads it from history.
|
59
58
|
def load_aggregate(aggregate_id, clazz = nil)
|
60
|
-
result = aggregates.fetch(aggregate_id) do |
|
59
|
+
result = aggregates.fetch(aggregate_id) do |_|
|
61
60
|
stream, events = @event_store.load_events(aggregate_id)
|
62
61
|
raise AggregateNotFound.new(aggregate_id) unless stream
|
63
62
|
aggregate_class = Class.const_get(stream.aggregate_type)
|
@@ -69,6 +68,12 @@ module Sequent
|
|
69
68
|
result
|
70
69
|
end
|
71
70
|
|
71
|
+
##
|
72
|
+
# Returns whether the event store has an aggregate with the given id
|
73
|
+
def contains_aggregate?(aggregate_id)
|
74
|
+
@event_store.stream_exists?(aggregate_id)
|
75
|
+
end
|
76
|
+
|
72
77
|
# Gets all uncommitted_events from the 'registered' aggregates
|
73
78
|
# and stores them in the event store.
|
74
79
|
# The command is 'attached' for traceability purpose so we can see
|
@@ -77,7 +82,7 @@ module Sequent
|
|
77
82
|
# This is all abstracted away if you use the Sequent::Core::CommandService
|
78
83
|
#
|
79
84
|
def commit(command)
|
80
|
-
updated_aggregates = aggregates.values.reject {|x| x.uncommitted_events.empty?}
|
85
|
+
updated_aggregates = aggregates.values.reject { |x| x.uncommitted_events.empty? }
|
81
86
|
return if updated_aggregates.empty?
|
82
87
|
streams_with_events = updated_aggregates.map do |aggregate|
|
83
88
|
[ aggregate.event_stream, aggregate.uncommitted_events ]
|
@@ -88,13 +93,13 @@ module Sequent
|
|
88
93
|
|
89
94
|
# Clears the Unit of Work.
|
90
95
|
def clear
|
91
|
-
Thread.current[AGGREGATES_KEY] =
|
96
|
+
Thread.current[AGGREGATES_KEY] = nil
|
92
97
|
end
|
93
98
|
|
94
99
|
private
|
95
100
|
|
96
101
|
def aggregates
|
97
|
-
Thread.current[AGGREGATES_KEY]
|
102
|
+
Thread.current[AGGREGATES_KEY] ||= {}
|
98
103
|
end
|
99
104
|
|
100
105
|
def store_events(command, streams_with_events)
|
@@ -88,7 +88,7 @@ module Sequent
|
|
88
88
|
protected
|
89
89
|
|
90
90
|
def build_event(event, params = {})
|
91
|
-
event.new({aggregate_id: @id, sequence_number: @sequence_number}
|
91
|
+
event.new(params.merge({aggregate_id: @id, sequence_number: @sequence_number}))
|
92
92
|
end
|
93
93
|
|
94
94
|
# Provide subclasses nice DSL to 'apply' events via:
|
@@ -124,7 +124,7 @@ module Sequent
|
|
124
124
|
protected
|
125
125
|
|
126
126
|
def build_event(event, params = {})
|
127
|
-
super(event, {organization_id: @organization_id}
|
127
|
+
super(event, params.merge({organization_id: @organization_id}))
|
128
128
|
end
|
129
129
|
end
|
130
130
|
end
|
@@ -26,6 +26,10 @@ 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
|
+
|
29
33
|
protected
|
30
34
|
def do_with_aggregate(command, clazz, aggregate_id = nil)
|
31
35
|
aggregate = @repository.load_aggregate(aggregate_id.nil? ? command.aggregate_id : aggregate_id, clazz)
|
@@ -15,13 +15,14 @@ module Sequent
|
|
15
15
|
class CommandService
|
16
16
|
attr_accessor :configuration
|
17
17
|
|
18
|
-
#
|
18
|
+
# Create a command service with the given configuration.
|
19
19
|
#
|
20
20
|
# +event_store+ The Sequent::Core::EventStore
|
21
|
-
# +
|
22
|
-
# +transaction_provider+ How to do transaction management.
|
23
|
-
# +
|
24
|
-
|
21
|
+
# +aggregate_repository+ The Sequent::Core::AggregateRepository
|
22
|
+
# +transaction_provider+ How to do transaction management.
|
23
|
+
# +command_handlers+ List of command handlers that need to handle commands
|
24
|
+
# +command_filters+ List of filter that respond_to :execute(command). Can be useful to do extra checks (security and such).
|
25
|
+
def initialize(configuration)
|
25
26
|
self.configuration = configuration
|
26
27
|
end
|
27
28
|
|
data/lib/sequent/core/core.rb
CHANGED
@@ -33,7 +33,8 @@ module Sequent
|
|
33
33
|
# Returns all events for the aggregate ordered by sequence_number
|
34
34
|
#
|
35
35
|
def load_events(aggregate_id)
|
36
|
-
stream = stream_record_class.where(aggregate_id: aggregate_id).first
|
36
|
+
stream = stream_record_class.where(aggregate_id: aggregate_id).first
|
37
|
+
return nil unless stream
|
37
38
|
events = event_record_class.connection.select_all(%Q{
|
38
39
|
SELECT event_type, event_json
|
39
40
|
FROM #{quote_table_name event_record_class.table_name}
|
@@ -49,6 +50,10 @@ SELECT event_type, event_json
|
|
49
50
|
[stream.event_stream, events]
|
50
51
|
end
|
51
52
|
|
53
|
+
def stream_exists?(aggregate_id)
|
54
|
+
stream_record_class.exists?(aggregate_id: aggregate_id)
|
55
|
+
end
|
56
|
+
|
52
57
|
##
|
53
58
|
# Replays all events in the event store to the registered event_handlers.
|
54
59
|
#
|
@@ -11,7 +11,7 @@ module Sequent
|
|
11
11
|
# end
|
12
12
|
#
|
13
13
|
# You typically do not need to include this module in your classes. If you extend from
|
14
|
-
# Sequent::Core::AggregateRoot, Sequent::Core::
|
14
|
+
# Sequent::Core::AggregateRoot, Sequent::Core::Projector, Sequent::Core::Workflow or Sequent::Core::BaseCommandHandler
|
15
15
|
# you will get this functionality for free.
|
16
16
|
#
|
17
17
|
module SelfApplier
|
@@ -18,7 +18,7 @@ module Sequent
|
|
18
18
|
#
|
19
19
|
# Example:
|
20
20
|
#
|
21
|
-
# class InvoiceEventHandler < Sequent::Core::
|
21
|
+
# class InvoiceEventHandler < Sequent::Core::Projector
|
22
22
|
# on RecipientMovedEvent do |event|
|
23
23
|
# update_all_records InvoiceRecord, recipient_id: event.recipient.aggregate_id do |record|
|
24
24
|
# record.recipient_street = record.recipient.street
|
data/lib/sequent/sequent.rb
CHANGED
data/lib/sequent/test.rb
ADDED
@@ -33,6 +33,11 @@ module Sequent
|
|
33
33
|
# then_events InvoicePaidEvent(args)
|
34
34
|
# end
|
35
35
|
#
|
36
|
+
# it "rejects coupon when does not exist" do
|
37
|
+
# given_events CartCreatedEvent.new(args)
|
38
|
+
# when_command ApplyDiscountCouponCommand(args)
|
39
|
+
# then_fails_with CouponDoesNotExist
|
40
|
+
# end
|
36
41
|
# end
|
37
42
|
module CommandHandlerHelpers
|
38
43
|
|
@@ -72,6 +77,10 @@ module Sequent
|
|
72
77
|
@stored_events = []
|
73
78
|
end
|
74
79
|
|
80
|
+
def stream_exists?(aggregate_id)
|
81
|
+
@event_streams.has_key?(aggregate_id)
|
82
|
+
end
|
83
|
+
|
75
84
|
private
|
76
85
|
|
77
86
|
def to_event_streams(events)
|
@@ -107,23 +116,32 @@ module Sequent
|
|
107
116
|
Class.const_get(type).deserialize_from_json(Sequent::Core::Oj.strict_load(json))
|
108
117
|
end
|
109
118
|
end
|
110
|
-
|
111
119
|
end
|
112
120
|
|
113
121
|
def given_events *events
|
114
|
-
@event_store.given_events(events)
|
122
|
+
@event_store.given_events(events.flatten(1))
|
115
123
|
end
|
116
124
|
|
117
125
|
def when_command command
|
118
126
|
raise "@command_handler is mandatory when using the #{self.class}" unless @command_handler
|
119
127
|
raise "Command handler #{@command_handler} cannot handle command #{command}, please configure the command type (forgot an include in the command class?)" unless @command_handler.handles_message?(command)
|
120
|
-
|
128
|
+
begin
|
129
|
+
@command_handler.handle_message(command)
|
130
|
+
rescue => e
|
131
|
+
@unhandled_error = e
|
132
|
+
end
|
121
133
|
@repository.commit(command)
|
134
|
+
@repository.clear
|
135
|
+
end
|
136
|
+
|
137
|
+
def then_fails_with clazz
|
138
|
+
expect(@unhandled_error).to be_kind_of(clazz)
|
139
|
+
@unhandled_error = nil
|
122
140
|
end
|
123
141
|
|
124
142
|
def then_events *events
|
125
|
-
expect(@event_store.stored_events.map(&:class)).to eq(events.map(&:class))
|
126
|
-
@event_store.stored_events.zip(events).each do |actual, expected|
|
143
|
+
expect(@event_store.stored_events.map(&:class)).to eq(events.flatten(1).map(&:class))
|
144
|
+
@event_store.stored_events.zip(events.flatten(1)).each do |actual, expected|
|
127
145
|
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
|
128
146
|
end
|
129
147
|
end
|
@@ -132,7 +150,11 @@ module Sequent
|
|
132
150
|
then_events
|
133
151
|
end
|
134
152
|
|
153
|
+
def self.included(spec)
|
154
|
+
spec.after do
|
155
|
+
raise @unhandled_error if @unhandled_error
|
156
|
+
end
|
157
|
+
end
|
135
158
|
end
|
136
|
-
|
137
159
|
end
|
138
160
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Test
|
3
|
+
##
|
4
|
+
# Use in tests
|
5
|
+
#
|
6
|
+
# This provides a nice DSL for testing your event handlers.
|
7
|
+
# E.g.
|
8
|
+
#
|
9
|
+
# when_event UserWasRegistered.new(args)
|
10
|
+
# then_commands SendWelcomeEmail.new(args)
|
11
|
+
#
|
12
|
+
# Example for Rspec config
|
13
|
+
#
|
14
|
+
# RSpec.configure do |config|
|
15
|
+
# config.include Sequent::Test::WorkflowHelpers
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Then in a spec
|
19
|
+
#
|
20
|
+
# describe SendWelcomeMailWorkflow do
|
21
|
+
# let(:workflow) { SendWelcomeMailWorkflow.new }
|
22
|
+
#
|
23
|
+
# it "sends a welcome mail" do
|
24
|
+
# when_event UserWasRegistered.new(args)
|
25
|
+
# then_commands SendWelcomeEmail.new(args)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
module WorkflowHelpers
|
29
|
+
|
30
|
+
class FakeCommandService
|
31
|
+
attr_reader :recorded_commands
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@recorded_commands = []
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute_commands(*commands)
|
38
|
+
@recorded_commands += commands
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def then_events *events
|
43
|
+
expect(@event_store.stored_events.map(&:class)).to eq(events.flatten(1).map(&:class))
|
44
|
+
@event_store.stored_events.zip(events.flatten(1)).each do |actual, expected|
|
45
|
+
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
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def then_no_events
|
50
|
+
then_events
|
51
|
+
end
|
52
|
+
|
53
|
+
def when_event(event)
|
54
|
+
workflow.handle_message event
|
55
|
+
end
|
56
|
+
|
57
|
+
def then_commands(*commands)
|
58
|
+
recorded = fake_command_service.recorded_commands
|
59
|
+
expect(recorded.map(&:class)).to eq(commands.flatten(1).map(&:class))
|
60
|
+
expect(fake_command_service.recorded_commands).to eq(commands.flatten(1))
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.included(spec)
|
64
|
+
spec.let(:fake_command_service) { FakeCommandService.new }
|
65
|
+
spec.before do
|
66
|
+
Sequent.configure { |c| c.command_service = fake_command_service }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Test
|
3
|
+
##
|
4
|
+
# Use in tests
|
5
|
+
#
|
6
|
+
# This provides a nice DSL for generating streams of events. FactoryGirl is required when using this helper.
|
7
|
+
#
|
8
|
+
# Example for Rspec config
|
9
|
+
#
|
10
|
+
# RSpec.configure do |config|
|
11
|
+
# config.include Sequent::Test::EventStreamHelpers
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Then in a spec
|
15
|
+
#
|
16
|
+
# given_stream_for(aggregate_id: 'X') do |s|
|
17
|
+
# s.group_created owner_aggregate_id: 'Y'
|
18
|
+
# s.group_opened
|
19
|
+
# s.owner_joined_group owner_aggregate_id: 'Y'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Methods on `s` will be FactoryGirl factories. All arguments will be passed on to FactoryGirl's build method.
|
23
|
+
# Aggregate ids and sequence numbers will be set automatically.
|
24
|
+
#
|
25
|
+
# The example above can also be written as follows:
|
26
|
+
#
|
27
|
+
# events = event_stream(aggregate_id: 'X') do |s|
|
28
|
+
# s.group_created owner_aggregate_id: 'Y'
|
29
|
+
# s.group_opened
|
30
|
+
# s.owner_joined_group owner_aggregate_id: 'Y'
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# given_events(events)
|
34
|
+
#
|
35
|
+
module EventStreamHelpers
|
36
|
+
class Builder
|
37
|
+
attr_reader :events
|
38
|
+
|
39
|
+
def initialize(aggregate_id)
|
40
|
+
@aggregate_id = aggregate_id
|
41
|
+
@events = []
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(name, *args, &block)
|
45
|
+
args = prepare_arguments(args)
|
46
|
+
@events << FactoryGirl.build(name, *args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def prepare_arguments(args)
|
52
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
53
|
+
args << options.merge(aggregate_id: @aggregate_id, sequence_number: next_sequence_number)
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_sequence_number
|
57
|
+
@events.count + 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def event_stream(aggregate_id:, &block)
|
62
|
+
builder = Builder.new(aggregate_id)
|
63
|
+
block.call(builder)
|
64
|
+
builder.events
|
65
|
+
end
|
66
|
+
|
67
|
+
def given_stream_for(aggregate_id:, &block)
|
68
|
+
given_events(*event_stream(aggregate_id: aggregate_id, &block))
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.included(spec)
|
72
|
+
require 'factory_girl'
|
73
|
+
rescue LoadError
|
74
|
+
raise ArgumentError, "Factory girl is required to use the event stream helpers"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
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.6
|
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-08-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -205,11 +205,14 @@ files:
|
|
205
205
|
- lib/sequent/core/transactions/no_transactions.rb
|
206
206
|
- lib/sequent/core/transactions/transactions.rb
|
207
207
|
- lib/sequent/core/value_object.rb
|
208
|
+
- lib/sequent/core/workflow.rb
|
208
209
|
- lib/sequent/migrations/migrate_events.rb
|
209
210
|
- lib/sequent/migrations/migrations.rb
|
210
211
|
- lib/sequent/sequent.rb
|
212
|
+
- lib/sequent/test.rb
|
211
213
|
- lib/sequent/test/command_handler_helpers.rb
|
212
|
-
- lib/sequent/test/
|
214
|
+
- lib/sequent/test/event_handler_helpers.rb
|
215
|
+
- lib/sequent/test/event_stream_helpers.rb
|
213
216
|
- lib/version.rb
|
214
217
|
homepage: https://github.com/zilverline/sequent
|
215
218
|
licenses:
|
@@ -231,7 +234,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
234
|
version: '0'
|
232
235
|
requirements: []
|
233
236
|
rubyforge_project:
|
234
|
-
rubygems_version: 2.
|
237
|
+
rubygems_version: 2.4.5
|
235
238
|
signing_key:
|
236
239
|
specification_version: 4
|
237
240
|
summary: Event sourcing framework for Ruby
|
data/lib/sequent/test/test.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require_relative 'command_handler_helpers'
|