ruby_cqrs 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ require_relative('../spec_helper.rb')
2
+
3
+ describe 'Snapshotable module' do
4
+ let(:command_context) {}
5
+ let(:repository) { RubyCqrs::Domain::AggregateRepository.new\
6
+ RubyCqrs::Data::InMemoryEventStore.new, command_context }
7
+ let(:aggregate_not_snapshot) { SomeDomain::AggregateRootNoSnapshot.new }
8
+ let(:aggregate_wrong_snapshot) { SomeDomain::AggregateRootWronglyImplementSnapshot.new }
9
+ let(:aggregate) { SomeDomain::AggregateRoot.new }
10
+ # an aggregate has a SNAPSHOT_THRESHOLD of 45
11
+ let(:aggregate_s_45) { SomeDomain::AggregateRoot45Snapshot.new }
12
+
13
+ context 'when an aggregate is not snapshotable' do
14
+ it 'specifies an aggregate is not snapshotable' do
15
+ expect(aggregate_not_snapshot.is_a? RubyCqrs::Domain::Snapshotable).to be_falsy
16
+ end
17
+
18
+ it "aggregate's #get_changes returns no snapshot field when default amount of events get fired" do
19
+ (1..30).each { |x| aggregate_not_snapshot.test_fire }
20
+ changes = aggregate_not_snapshot.send(:get_changes)
21
+ expect(changes[:snapshot]).to be_nil
22
+ end
23
+
24
+ it "saves and loads the correct aggregate back" do
25
+ (1..45).each { |x| aggregate_not_snapshot.test_fire }
26
+ repository.save aggregate_not_snapshot
27
+ loaded_aggregate = repository.find_by aggregate_not_snapshot.aggregate_id
28
+ expect(loaded_aggregate.state).to eq(45)
29
+ end
30
+ end
31
+
32
+ context "when an aggregate's snapshot feature is incorrectly implemented" do
33
+ it "raises NotADomainSnapshotError when enough events get fired" do
34
+ (1..30).each { |x| aggregate_wrong_snapshot.test_fire }
35
+ expect{ aggregate_wrong_snapshot.send(:get_changes) }.to\
36
+ raise_error(RubyCqrs::NotADomainSnapshotError)
37
+ end
38
+ end
39
+
40
+ context 'when an aggregate is snapshotable' do
41
+ it 'specifies an aggregate is snapshotable' do
42
+ expect(aggregate.is_a? RubyCqrs::Domain::Snapshotable).to be_truthy
43
+ expect(aggregate_s_45.is_a? RubyCqrs::Domain::Snapshotable).to be_truthy
44
+ end
45
+
46
+ it "aggregate's #get_changes returns an addtional snapshot field when enough events get fired" do
47
+ (1..30).each { |x| aggregate.test_fire }
48
+ changes = aggregate.send(:get_changes)
49
+ expect(changes[:snapshot]).to_not be_nil
50
+
51
+ (1..45).each { |x| aggregate_s_45.test_fire }
52
+ changes = aggregate_s_45.send(:get_changes)
53
+ expect(changes[:snapshot]).to_not be_nil
54
+ end
55
+
56
+ it "aggregate's #get_changes returns no snapshot field when not enough events get fired" do
57
+ (1..29).each { |x| aggregate.test_fire }
58
+ changes = aggregate.send(:get_changes)
59
+ expect(changes[:snapshot]).to be_nil
60
+
61
+ (1..44).each { |x| aggregate_s_45.test_fire }
62
+ changes = aggregate_s_45.send(:get_changes)
63
+ expect(changes[:snapshot]).to be_nil
64
+ end
65
+
66
+ it "saves and loads the correct aggregate back with no snapshots are taken" do
67
+ (1..29).each { |x| aggregate.test_fire }
68
+ repository.save aggregate
69
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
70
+ expect(loaded_aggregate.state).to eq(29)
71
+
72
+ (1..44).each { |x| aggregate_s_45.test_fire }
73
+ repository.save aggregate_s_45
74
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
75
+ expect(loaded_aggregate.state).to eq(44)
76
+ end
77
+
78
+ it "saves and loads the correct aggregate back with 1 snapshot's taken" do
79
+ (1..45).each { |x| aggregate.test_fire }
80
+ repository.save aggregate
81
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
82
+ expect(loaded_aggregate.state).to eq(45)
83
+
84
+ (1..60).each { |x| aggregate_s_45.test_fire }
85
+ repository.save aggregate_s_45
86
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
87
+ expect(loaded_aggregate.state).to eq(60)
88
+ end
89
+
90
+ it "saves and loads the correct aggregate back incrementally and eventually triggers a snapshot" do
91
+ (1..15).each { |x| aggregate.test_fire }
92
+ repository.save aggregate
93
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
94
+ expect(loaded_aggregate.state).to eq(15)
95
+
96
+ (1..15).each { |x| loaded_aggregate.test_fire }
97
+ repository.save loaded_aggregate
98
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
99
+ expect(loaded_aggregate.state).to eq(30)
100
+
101
+ (1..30).each { |x| aggregate_s_45.test_fire }
102
+ repository.save aggregate_s_45
103
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
104
+ expect(loaded_aggregate.state).to eq(30)
105
+
106
+ (1..15).each { |x| loaded_aggregate.test_fire }
107
+ repository.save loaded_aggregate
108
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
109
+ expect(loaded_aggregate.state).to eq(45)
110
+ end
111
+
112
+ it "saves aggregate incrementally and eventually triggers a snapshot, without reloading aggregate during the process" do
113
+ (1..15).each { |x| aggregate.test_fire }
114
+ repository.save aggregate
115
+ expect(aggregate.state).to eq(15)
116
+
117
+ (1..15).each { |x| aggregate.test_fire }
118
+ repository.save aggregate
119
+ expect(aggregate.state).to eq(30)
120
+
121
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
122
+ expect(loaded_aggregate.state).to eq(30)
123
+
124
+ (1..30).each { |x| aggregate_s_45.test_fire }
125
+ repository.save aggregate_s_45
126
+ expect(aggregate_s_45.state).to eq(30)
127
+
128
+ (1..15).each { |x| aggregate_s_45.test_fire }
129
+ repository.save aggregate_s_45
130
+ expect(aggregate_s_45.state).to eq(45)
131
+
132
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
133
+ expect(loaded_aggregate.state).to eq(45)
134
+ end
135
+
136
+ it "saves and loads the correct aggregate back with two snapshot taken" do
137
+ (1..30).each { |x| aggregate.test_fire }
138
+ repository.save aggregate
139
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
140
+ expect(loaded_aggregate.state).to eq(30)
141
+
142
+ (1..30).each { |x| loaded_aggregate.test_fire }
143
+ repository.save loaded_aggregate
144
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
145
+ expect(loaded_aggregate.state).to eq(60)
146
+
147
+ (1..45).each { |x| aggregate_s_45.test_fire }
148
+ repository.save aggregate_s_45
149
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
150
+ expect(loaded_aggregate.state).to eq(45)
151
+
152
+ (1..45).each { |x| loaded_aggregate.test_fire }
153
+ repository.save loaded_aggregate
154
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
155
+ expect(loaded_aggregate.state).to eq(90)
156
+ end
157
+
158
+ it "saves and loads the correct aggregate back incrementally and eventually triggers two snapshot" do
159
+ (1..30).each { |x| aggregate.test_fire }
160
+ repository.save aggregate
161
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
162
+ expect(loaded_aggregate.state).to eq(30)
163
+
164
+ (1..15).each { |x| loaded_aggregate.test_fire }
165
+ repository.save loaded_aggregate
166
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
167
+ expect(loaded_aggregate.state).to eq(45)
168
+
169
+ (1..15).each { |x| loaded_aggregate.test_fire }
170
+ repository.save loaded_aggregate
171
+ loaded_aggregate = repository.find_by aggregate.aggregate_id
172
+ expect(loaded_aggregate.state).to eq(60)
173
+
174
+ (1..45).each { |x| aggregate_s_45.test_fire }
175
+ repository.save aggregate_s_45
176
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
177
+ expect(loaded_aggregate.state).to eq(45)
178
+
179
+ (1..30).each { |x| loaded_aggregate.test_fire }
180
+ repository.save loaded_aggregate
181
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
182
+ expect(loaded_aggregate.state).to eq(75)
183
+
184
+ (1..15).each { |x| loaded_aggregate.test_fire }
185
+ repository.save loaded_aggregate
186
+ loaded_aggregate = repository.find_by aggregate_s_45.aggregate_id
187
+ expect(loaded_aggregate.state).to eq(90)
188
+ end
189
+
190
+ it "saves and loads the correct aggregate back incrementally and eventually triggers two snapshot, without reloading aggregate during the process" do
191
+ (1..30).each { |x| aggregate.test_fire }
192
+ repository.save aggregate
193
+ expect(aggregate.state).to eq(30)
194
+
195
+ (1..15).each { |x| aggregate.test_fire }
196
+ repository.save aggregate
197
+ expect(aggregate.state).to eq(45)
198
+
199
+ (1..15).each { |x| aggregate.test_fire }
200
+ repository.save aggregate
201
+ expect(aggregate.state).to eq(60)
202
+
203
+ (1..45).each { |x| aggregate_s_45.test_fire }
204
+ repository.save aggregate_s_45
205
+ expect(aggregate_s_45.state).to eq(45)
206
+
207
+ (1..30).each { |x| aggregate_s_45.test_fire }
208
+ repository.save aggregate_s_45
209
+ expect(aggregate_s_45.state).to eq(75)
210
+
211
+ (1..15).each { |x| aggregate_s_45.test_fire }
212
+ repository.save aggregate_s_45
213
+ expect(aggregate_s_45.state).to eq(90)
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,182 @@
1
+ require 'beefcake'
2
+
3
+ module SomeDomain
4
+ class AggregateRoot
5
+ include RubyCqrs::Domain::Aggregate
6
+ include RubyCqrs::Domain::Snapshotable
7
+
8
+ attr_reader :state
9
+
10
+ def initialize
11
+ @state = 0
12
+ super
13
+ end
14
+
15
+ def fire_weird_stuff
16
+ raise_event Object.new
17
+ end
18
+
19
+ def test_fire
20
+ raise_event THIRD_EVENT_INSTANCE
21
+ end
22
+
23
+ def test_fire_ag
24
+ raise_event FORTH_EVENT_INSTANCE
25
+ end
26
+
27
+ private
28
+ def on_first_event event; @state += 1; end
29
+ def on_second_event event; @state += 1; end
30
+ def on_third_event event; @state += 1; end
31
+ def on_forth_event event; @state += 1; end
32
+
33
+ def take_a_snapshot
34
+ SomeSnapshot.new(:state => @state)
35
+ end
36
+
37
+ def apply_snapshot snapshot_object
38
+ @state = snapshot_object.state
39
+ end
40
+ end
41
+
42
+ class AggregateRoot45Snapshot
43
+ include RubyCqrs::Domain::Aggregate
44
+ include RubyCqrs::Domain::Snapshotable
45
+
46
+ SNAPSHOT_THRESHOLD = 45
47
+
48
+ attr_reader :state
49
+
50
+ def initialize
51
+ @state = 0
52
+ super
53
+ end
54
+
55
+ def test_fire
56
+ raise_event THIRD_EVENT_INSTANCE
57
+ end
58
+
59
+ private
60
+ def on_third_event event; @state += 1; end
61
+
62
+ def take_a_snapshot
63
+ SomeSnapshot.new(:state => @state)
64
+ end
65
+
66
+ def apply_snapshot snapshot_object
67
+ @state = snapshot_object.state
68
+ end
69
+ end
70
+
71
+ class AggregateRootWronglyImplementSnapshot
72
+ include RubyCqrs::Domain::Aggregate
73
+ include RubyCqrs::Domain::Snapshotable
74
+
75
+ attr_reader :state
76
+
77
+ def initialize
78
+ @state = 0
79
+ super
80
+ end
81
+
82
+ def test_fire
83
+ raise_event THIRD_EVENT_INSTANCE
84
+ end
85
+
86
+ private
87
+ def on_third_event event; @state += 1; end
88
+
89
+ def take_a_snapshot
90
+ WrongSnapshot.new(:state => @state)
91
+ end
92
+
93
+ def apply_snapshot snapshot_object
94
+ @state = snapshot_object.state
95
+ end
96
+ end
97
+
98
+ class AggregateRootNoSnapshot
99
+ include RubyCqrs::Domain::Aggregate
100
+
101
+ attr_reader :state
102
+
103
+ def initialize
104
+ @state = 0
105
+ super
106
+ end
107
+
108
+ def test_fire
109
+ raise_event THIRD_EVENT_INSTANCE
110
+ end
111
+
112
+ private
113
+ def on_third_event event; @state += 1; end
114
+ end
115
+
116
+ class SomeSnapshot
117
+ include Beefcake::Message
118
+ include RubyCqrs::Domain::Snapshot
119
+
120
+ required :state, :int32, 1
121
+ end
122
+
123
+ class WrongSnapshot
124
+ include Beefcake::Message
125
+
126
+ required :state, :int32, 1
127
+ end
128
+
129
+ AGGREGATE_ID = 'cbb688cc-d49a-11e4-9f39-3c15c2d13d4e'
130
+
131
+ class FirstEvent
132
+ include RubyCqrs::Domain::Event
133
+ include Beefcake::Message
134
+
135
+ def initialize
136
+ @aggregate_id = AGGREGATE_ID
137
+ @version = 1
138
+ end
139
+ end
140
+
141
+ class SecondEvent
142
+ include RubyCqrs::Domain::Event
143
+ include Beefcake::Message
144
+
145
+ def initialize
146
+ @aggregate_id = AGGREGATE_ID
147
+ @version = 2
148
+ end
149
+ end
150
+
151
+ class ThirdEvent
152
+ include RubyCqrs::Domain::Event
153
+ include Beefcake::Message
154
+
155
+ required :id, :int32, 1
156
+ required :name, :string, 2
157
+ required :phone, :string, 3
158
+
159
+ optional :note, :string, 4
160
+ end
161
+
162
+ class ForthEvent
163
+ include RubyCqrs::Domain::Event
164
+ include Beefcake::Message
165
+
166
+ required :order_id, :int32, 1
167
+ required :price, :int32, 2
168
+ required :customer_id,:int32, 3
169
+
170
+ optional :note, :string, 4
171
+ end
172
+
173
+ class FifthEvent
174
+ include RubyCqrs::Domain::Event
175
+ end
176
+
177
+ THIRD_EVENT_INSTANCE = ThirdEvent.new(:id => 1, :name => 'some dude',\
178
+ :phone => '13322244444', :note => 'good luck')
179
+
180
+ FORTH_EVENT_INSTANCE = ForthEvent.new(:order_id => 100, :price => 2000,\
181
+ :customer_id => 1, :note => 'sold!')
182
+ end
@@ -0,0 +1,11 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'bundler'
5
+ Bundler.setup(:default, :test)
6
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
7
+
8
+ require('ruby_cqrs')
9
+
10
+ require('fixture/typical_domain')
11
+ require('support/matchers')
@@ -0,0 +1,8 @@
1
+ require 'rspec/expectations'
2
+ require_relative '../spec_helper.rb'
3
+
4
+ RSpec::Matchers.define :be_a_valid_uuid do
5
+ match do |uuid_string|
6
+ RubyCqrs::Guid.validate? uuid_string
7
+ end
8
+ end