sequent 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|