sandthorn 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.autotest +3 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +72 -0
- data/Gemfile.lock.old +54 -0
- data/LICENSE +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +1 -0
- data/lib/sandthorn.rb +78 -0
- data/lib/sandthorn/aggregate_root_base.rb +186 -0
- data/lib/sandthorn/aggregate_root_dirty_hashy.rb +30 -0
- data/lib/sandthorn/aggregate_root_snapshot.rb +39 -0
- data/lib/sandthorn/errors.rb +8 -0
- data/lib/sandthorn/event_inspector.rb +63 -0
- data/lib/sandthorn/version.rb +3 -0
- data/sandthorn.gemspec +35 -0
- data/spec/aggregate_delta_spec.rb +92 -0
- data/spec/aggregate_root_spec.rb +118 -0
- data/spec/aggregate_snapshot_spec.rb +260 -0
- data/spec/benchmark_spec.rb +55 -0
- data/spec/complex_aggregate_spec.rb +77 -0
- data/spec/db/sequel_driver.sqlite3_old +0 -0
- data/spec/different_driver_spec.rb +101 -0
- data/spec/event_inspector_spec.rb +148 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/tracing_spec.rb +121 -0
- metadata +255 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require "dirty_hashy"
|
2
|
+
require "sandthorn/aggregate_root_base"
|
3
|
+
|
4
|
+
module Sandthorn
|
5
|
+
module AggregateRoot
|
6
|
+
module DirtyHashy
|
7
|
+
include Sandthorn::AggregateRoot::Base
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(Sandthorn::AggregateRoot::Base::ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
def aggregate_initialize
|
14
|
+
@hashy = ::DirtyHashy.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_delta
|
18
|
+
extract_relevant_aggregate_instance_variables.each do |var|
|
19
|
+
@hashy[var.to_s.delete("@")] = self.instance_variable_get("#{var}")
|
20
|
+
end
|
21
|
+
aggregate_attribute_deltas = []
|
22
|
+
@hashy.changes.each do |attribute|
|
23
|
+
aggregate_attribute_deltas << { :attribute_name => attribute[0], :old_value => attribute[1][0], :new_value => attribute[1][1]}
|
24
|
+
end
|
25
|
+
aggregate_attribute_deltas
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sandthorn
|
2
|
+
module AggregateRootSnapshot
|
3
|
+
attr_reader :aggregate_snapshot
|
4
|
+
|
5
|
+
def aggregate_snapshot!
|
6
|
+
|
7
|
+
if @aggregate_events.count > 0
|
8
|
+
raise "Can't take snapshot on object with unsaved events"
|
9
|
+
end
|
10
|
+
|
11
|
+
@aggregate_snapshot = {
|
12
|
+
:event_name => "aggregate_set_from_snapshot",
|
13
|
+
:event_args => [self],
|
14
|
+
:aggregate_version => @aggregate_current_event_version
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def save_snapshot
|
19
|
+
raise "No snapshot has been created!" unless @aggregate_snapshot
|
20
|
+
@aggregate_snapshot[:event_data] = Sandthorn.serialize @aggregate_snapshot[:event_args]
|
21
|
+
@aggregate_snapshot[:event_args] = nil
|
22
|
+
Sandthorn.save_snapshot @aggregate_snapshot, @aggregate_id, self.class.name
|
23
|
+
@aggregate_snapshot = nil
|
24
|
+
end
|
25
|
+
private
|
26
|
+
def aggregate_create_event_when_extended
|
27
|
+
self.aggregate_snapshot!
|
28
|
+
vars = extract_relevant_aggregate_instance_variables
|
29
|
+
vars.each do |var_name|
|
30
|
+
value = instance_variable_get var_name
|
31
|
+
dump = Marshal.dump(value)
|
32
|
+
store_aggregate_instance_variable var_name, dump
|
33
|
+
end
|
34
|
+
#@aggregate_snapshot[:event_data] = Sandthorn.serialize @aggregate_snapshot[:event_args]
|
35
|
+
store_aggregate_event "instance_extended_as_aggregate", @aggregate_snapshot[:event_args]
|
36
|
+
@aggregate_snapshot = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Sandthorn
|
2
|
+
module EventInspector
|
3
|
+
def has_unsaved_event? event_name, options = {}
|
4
|
+
unsaved = events_with_trace_info
|
5
|
+
if self.aggregate_events.empty?
|
6
|
+
unsaved = []
|
7
|
+
else
|
8
|
+
unsaved.reject! { |e| e[:aggregate_version] < self.aggregate_events.first[:aggregate_version] }
|
9
|
+
end
|
10
|
+
matching_events = unsaved.select { |e| e[:event_name] == event_name }
|
11
|
+
event_exists = matching_events.length > 0
|
12
|
+
trace = has_trace? matching_events, options.fetch(:trace, {})
|
13
|
+
|
14
|
+
return event_exists && trace
|
15
|
+
end
|
16
|
+
def has_saved_event? event_name, options = {}
|
17
|
+
saved = events_with_trace_info
|
18
|
+
saved.reject! { |e| e[:aggregate_version] >= self.aggregate_events.first[:aggregate_version] } unless self.aggregate_events.empty?
|
19
|
+
matching_events = saved.select { |e| e[:event_name] == event_name }
|
20
|
+
event_exists = matching_events.length > 0
|
21
|
+
trace = has_trace? matching_events, options.fetch(:trace, {})
|
22
|
+
|
23
|
+
return event_exists && trace
|
24
|
+
end
|
25
|
+
def has_event? event_name, options = {}
|
26
|
+
matching_events = events_with_trace_info.select { |e| e[:event_name] == event_name }
|
27
|
+
event_exists = matching_events.length > 0
|
28
|
+
trace = has_trace? matching_events, options.fetch(:trace, {})
|
29
|
+
return event_exists && trace
|
30
|
+
end
|
31
|
+
def events_with_trace_info
|
32
|
+
saved = Sandthorn.get_aggregate_events self.aggregate_id, self.class
|
33
|
+
unsaved = self.aggregate_events
|
34
|
+
all = saved.concat(unsaved).sort { |a, b| a[:aggregate_version] <=> b[:aggregate_version] }
|
35
|
+
extracted = all.collect do |e|
|
36
|
+
if e[:event_args].nil? && !e[:event_data].nil?
|
37
|
+
data = Sandthorn.deserialize e[:event_data]
|
38
|
+
else
|
39
|
+
data = e[:event_args]
|
40
|
+
end
|
41
|
+
trace = data[:trace] unless data.nil? || !data.is_a?(Hash)
|
42
|
+
{aggregate_version: e[:aggregate_version], event_name: e[:event_name].to_sym, trace: trace }
|
43
|
+
end
|
44
|
+
return extracted
|
45
|
+
end
|
46
|
+
private
|
47
|
+
def get_unsaved_events event_name
|
48
|
+
self.aggregate_events.select { |e| e[:event_name] == event_name.to_s }
|
49
|
+
end
|
50
|
+
def get_saved_events event_name
|
51
|
+
saved_events = Sandthorn.get_aggregate_events self.aggregate_id, self.class
|
52
|
+
saved_events.select { |e| e[:event_name] == event_name.to_s }
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_trace? events_to_check, trace_info
|
56
|
+
return true if trace_info.empty?
|
57
|
+
events_to_check.each do |event|
|
58
|
+
return false if event[:trace] != trace_info
|
59
|
+
end
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/sandthorn.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sandthorn/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sandthorn"
|
8
|
+
spec.version = Sandthorn::VERSION
|
9
|
+
spec.authors = ["Lars Krantz", "Morgan Hallgren"]
|
10
|
+
spec.email = ["lars.krantz@alaz.se", "morgan.hallgren@gmail.com"]
|
11
|
+
spec.description = %q{Event sourcing gem}
|
12
|
+
spec.summary = %q{Event sourcing gem}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "gem-release"
|
25
|
+
spec.add_development_dependency "pry"
|
26
|
+
spec.add_development_dependency "pry-doc"
|
27
|
+
spec.add_development_dependency "awesome_print"
|
28
|
+
spec.add_development_dependency "autotest-standalone"
|
29
|
+
spec.add_development_dependency "sqlite3"
|
30
|
+
spec.add_development_dependency "sandthorn_driver_sequel"
|
31
|
+
|
32
|
+
spec.add_runtime_dependency "dirty_hashy"
|
33
|
+
spec.add_runtime_dependency "uuidtools"
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'uuidtools'
|
3
|
+
require 'sandthorn/aggregate_root_dirty_hashy'
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
class PersonTest
|
8
|
+
include Sandthorn::AggregateRoot::DirtyHashy
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :age
|
11
|
+
attr_reader :relationship_status
|
12
|
+
attr_reader :my_array
|
13
|
+
attr_reader :my_hash
|
14
|
+
|
15
|
+
def initialize name, age, relationship_status
|
16
|
+
@name = name
|
17
|
+
@age = age
|
18
|
+
@relationship_status = relationship_status
|
19
|
+
@my_array = []
|
20
|
+
@my_hash = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def change_name new_name
|
24
|
+
@name = new_name
|
25
|
+
record_event new_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def change_relationship new_relationship
|
29
|
+
@relationship_status = new_relationship
|
30
|
+
record_event new_relationship
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_to_array element
|
34
|
+
@my_array << element
|
35
|
+
record_event element
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_to_hash name,value
|
39
|
+
@my_hash[name] = value
|
40
|
+
record_event name,value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'Property Delta Event Sourcing' do
|
45
|
+
let(:person) { PersonTest.new "Lasse",40,:married}
|
46
|
+
|
47
|
+
it 'should be able to set name' do
|
48
|
+
person.change_name "Klabbarparen"
|
49
|
+
person.name.should eql("Klabbarparen")
|
50
|
+
#puts person.aggregate_events
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should be able to build from events' do
|
54
|
+
person.change_name "Klabbarparen"
|
55
|
+
builded = PersonTest.aggregate_build person.aggregate_events
|
56
|
+
builded.name.should eql(person.name)
|
57
|
+
builded.aggregate_id.should eql(person.aggregate_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should not have any events when built up' do
|
61
|
+
person.change_name "Mattias"
|
62
|
+
builded = PersonTest.aggregate_build person.aggregate_events
|
63
|
+
builded.aggregate_events.should be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should detect change on array' do
|
67
|
+
person.add_to_array "Foo"
|
68
|
+
person.add_to_array "bar"
|
69
|
+
|
70
|
+
builded = PersonTest.aggregate_build person.aggregate_events
|
71
|
+
builded.my_array.should include "Foo"
|
72
|
+
builded.my_array.should include "bar"
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should detect change on hash' do
|
76
|
+
person.add_to_hash :foo, "bar"
|
77
|
+
person.add_to_hash :bar, "foo"
|
78
|
+
|
79
|
+
builded = PersonTest.aggregate_build person.aggregate_events
|
80
|
+
builded.my_hash[:foo].should eql("bar")
|
81
|
+
builded.my_hash[:bar].should eql("foo")
|
82
|
+
|
83
|
+
person.add_to_hash :foo, "BAR"
|
84
|
+
|
85
|
+
#events = person.aggregate_events
|
86
|
+
#events << builded.aggregate_events
|
87
|
+
#puts events
|
88
|
+
|
89
|
+
builded2 = PersonTest.aggregate_build person.aggregate_events
|
90
|
+
builded2.my_hash[:foo].should eql("BAR")
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sandthorn/aggregate_root_dirty_hashy'
|
3
|
+
|
4
|
+
module Sandthorn
|
5
|
+
module AggregateRoot
|
6
|
+
class DirtyClass
|
7
|
+
include Sandthorn::AggregateRoot::DirtyHashy
|
8
|
+
attr_reader :name, :age
|
9
|
+
attr :sex
|
10
|
+
attr_writer :writer
|
11
|
+
|
12
|
+
def initialize args = {}
|
13
|
+
@name = args.fetch(:name, nil)
|
14
|
+
@sex = args.fetch(:sex, nil)
|
15
|
+
@writer = args.fetch(:writer, nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def change_name value
|
19
|
+
unless name == value
|
20
|
+
@name = value
|
21
|
+
commit
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def change_sex value
|
26
|
+
unless sex == value
|
27
|
+
@sex = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def change_writer value
|
32
|
+
unless writer == value
|
33
|
+
@writer = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
describe "when making a change on a aggregate" do
|
42
|
+
let(:dirty_obejct) {
|
43
|
+
o = DirtyClass.new
|
44
|
+
o
|
45
|
+
}
|
46
|
+
|
47
|
+
context "new with args" do
|
48
|
+
|
49
|
+
let(:subject) { DirtyClass.new(name: "Mogge", sex: "hen", writer: true) }
|
50
|
+
it "should set the values" do
|
51
|
+
expect(subject.name).to eql "Mogge"
|
52
|
+
expect(subject.sex).to eql "hen"
|
53
|
+
expect{subject.writer}.to raise_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when changing name (attr_reader)" do
|
58
|
+
|
59
|
+
it "should get new_name" do
|
60
|
+
dirty_obejct.change_name "new_name"
|
61
|
+
dirty_obejct.name.should eql "new_name"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should generate one event on new" do
|
65
|
+
expect(dirty_obejct.aggregate_events.length).to eql 1
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should generate 2 events new and change_name" do
|
69
|
+
dirty_obejct.change_name "new_name"
|
70
|
+
expect(dirty_obejct.aggregate_events.length).to eql 2
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when changing sex (attr)" do
|
75
|
+
it "should get new_sex" do
|
76
|
+
dirty_obejct.change_sex "new_sex"
|
77
|
+
dirty_obejct.sex.should eql "new_sex"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when changing writer (attr_writer)" do
|
82
|
+
it "should raise error" do
|
83
|
+
expect{dirty_obejct.change_writer "new_writer"}.to raise_error
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "save" do
|
88
|
+
it "should not have events on aggregete after save" do
|
89
|
+
expect(dirty_obejct.save.aggregate_events.length).to eql 0
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should have aggregate_originating_version == 0 pre save" do
|
93
|
+
expect(dirty_obejct.aggregate_originating_version).to eql 0
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should have aggregate_originating_version == 1 post save" do
|
97
|
+
expect(dirty_obejct.save.aggregate_originating_version).to eql 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "find" do
|
102
|
+
before(:each) { dirty_obejct.save }
|
103
|
+
it "should find by id" do
|
104
|
+
expect(DirtyClass.find(dirty_obejct.id).id).to eql dirty_obejct.id
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should hold changed name" do
|
108
|
+
dirty_obejct.change_name("morgan").save
|
109
|
+
expect(DirtyClass.find(dirty_obejct.id).name).to eql "morgan"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should raise error if trying to find id that not exist" do
|
113
|
+
expect{DirtyClass.find("666")}.to raise_error
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'uuidtools'
|
3
|
+
require 'sandthorn/aggregate_root_dirty_hashy'
|
4
|
+
require 'sandthorn/aggregate_root_snapshot'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
|
8
|
+
module BankAccountInterestCommands
|
9
|
+
def calculate_interest! until_date = DateTime.now
|
10
|
+
# skipping all safety-checks..
|
11
|
+
# and this is of course horribly wrong financially speaking.. whatever
|
12
|
+
pay_out_unpaid_interest!
|
13
|
+
interest_calculation_time = until_date - @last_interest_calculation
|
14
|
+
days_with_interest = interest_calculation_time.to_i
|
15
|
+
unpaid_interest = @balance * @current_interest_info[:interest_rate] * days_with_interest / 365.2425
|
16
|
+
added_unpaid_interest_event unpaid_interest,until_date
|
17
|
+
end
|
18
|
+
|
19
|
+
def pay_out_unpaid_interest!
|
20
|
+
paid_out_unpaid_interest_balance_event @unpaid_interest_balance
|
21
|
+
end
|
22
|
+
|
23
|
+
def change_interest! new_interest_rate, interest_valid_from
|
24
|
+
calculate_interest!
|
25
|
+
changed_interest_rate_event new_interest_rate,interest_valid_from
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module BankAccountWithdrawalCommands
|
29
|
+
def withdraw_from_atm! amount, atm_id
|
30
|
+
withdrew_amount_from_atm_event amount, atm_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def withdraw_from_cashier! amount, cashier_id
|
34
|
+
withdrew_amount_from_cashier_event amount, cashier_id
|
35
|
+
charged_cashier_withdrawal_fee_event 50
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module BankAccountVisaCardPurchasesCommands
|
40
|
+
def charge_card! amount, merchant_id
|
41
|
+
visa = VisaCardTransactionGateway.new
|
42
|
+
transaction_id = visa.charge_card "3030-3333-4252-2535", merchant_id, amount
|
43
|
+
paid_with_visa_card_event amount, transaction_id
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class VisaCardTransactionGateway
|
49
|
+
def initialize
|
50
|
+
@visa_connector = "foo_bar"
|
51
|
+
end
|
52
|
+
def charge_card visa_card_number, merchant_id, amount
|
53
|
+
transaction_id = UUIDTools::UUID.random_create.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module BankAccountDepositCommmands
|
58
|
+
def deposit_at_bank_office! amount, cashier_id
|
59
|
+
deposited_to_cashier_event amount, cashier_id
|
60
|
+
end
|
61
|
+
|
62
|
+
def transfer_money_from_another_account! amount, from_account_number
|
63
|
+
incoming_transfer_event amount,from_account_number
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class BankAccount
|
68
|
+
include Sandthorn::AggregateRoot::DirtyHashy
|
69
|
+
|
70
|
+
attr_reader :balance
|
71
|
+
attr_reader :account_number
|
72
|
+
attr_reader :current_interest_info
|
73
|
+
attr_reader :account_creation_date
|
74
|
+
attr_reader :unpaid_interest_balance
|
75
|
+
attr_reader :last_interest_calculation
|
76
|
+
|
77
|
+
def initialize *args
|
78
|
+
account_number = args[0]
|
79
|
+
interest_rate = args[1]
|
80
|
+
creation_date = args[2]
|
81
|
+
|
82
|
+
@current_interest_info = {}
|
83
|
+
@current_interest_info[:interest_rate] = interest_rate
|
84
|
+
@current_interest_info[:interest_valid_from] = creation_date
|
85
|
+
@balance = 0
|
86
|
+
@unpaid_interest_balance = 0
|
87
|
+
@account_creation_date = creation_date
|
88
|
+
@last_interest_calculation = creation_date
|
89
|
+
end
|
90
|
+
|
91
|
+
def changed_interest_rate_event new_interest_rate, interest_valid_from
|
92
|
+
@current_interest_info[:interest_rate] = new_interest_rate
|
93
|
+
@current_interest_info[:interest_valid_from] = interest_valid_from
|
94
|
+
record_event new_interest_rate,interest_valid_from
|
95
|
+
end
|
96
|
+
|
97
|
+
def added_unpaid_interest_event interest_amount, calculated_until
|
98
|
+
@unpaid_interest_balance += interest_amount
|
99
|
+
@last_interest_calculation = calculated_until
|
100
|
+
record_event interest_amount, calculated_until
|
101
|
+
end
|
102
|
+
|
103
|
+
def paid_out_unpaid_interest_balance_event interest_amount
|
104
|
+
@unpaid_interest_balance -= interest_amount
|
105
|
+
@balance += interest_amount
|
106
|
+
record_event interest_amount
|
107
|
+
end
|
108
|
+
|
109
|
+
def withdrew_amount_from_atm_event amount, atm_id
|
110
|
+
@balance -= amount
|
111
|
+
record_event amount,atm_id
|
112
|
+
end
|
113
|
+
|
114
|
+
def withdrew_amount_from_cashier_event amount, cashier_id
|
115
|
+
@balance -= amount
|
116
|
+
record_event amount, cashier_id
|
117
|
+
end
|
118
|
+
|
119
|
+
def paid_with_visa_card_event amount, visa_card_transaction_id
|
120
|
+
@balance -= amount
|
121
|
+
record_event amount,visa_card_transaction_id
|
122
|
+
end
|
123
|
+
|
124
|
+
def charged_cashier_withdrawal_fee_event amount
|
125
|
+
@balance -= amount
|
126
|
+
record_event amount
|
127
|
+
end
|
128
|
+
|
129
|
+
def deposited_to_cashier_event amount, cashier_id
|
130
|
+
@balance = self.balance + amount
|
131
|
+
record_event amount,cashier_id
|
132
|
+
end
|
133
|
+
|
134
|
+
def incoming_transfer_event amount, from_account_number
|
135
|
+
current_balance = self.balance
|
136
|
+
@balance = amount + current_balance
|
137
|
+
record_event amount, from_account_number
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
def a_test_account
|
143
|
+
a = BankAccount.new "91503010111",0.031415, Date.new(2011,10,12)
|
144
|
+
a.extend BankAccountDepositCommmands
|
145
|
+
a.transfer_money_from_another_account! 90000, "FOOBAR"
|
146
|
+
a.deposit_at_bank_office! 10000, "Lars Idorn"
|
147
|
+
|
148
|
+
a.extend BankAccountVisaCardPurchasesCommands
|
149
|
+
a.charge_card! 1000, "Starbucks Coffee"
|
150
|
+
|
151
|
+
a.extend BankAccountInterestCommands
|
152
|
+
a.calculate_interest!
|
153
|
+
return a
|
154
|
+
end
|
155
|
+
|
156
|
+
#Tests part
|
157
|
+
describe "when doing aggregate_find on an aggregate with a snapshot" do
|
158
|
+
let(:aggregate) do
|
159
|
+
a = a_test_account
|
160
|
+
a.save
|
161
|
+
a.extend Sandthorn::AggregateRootSnapshot
|
162
|
+
a.aggregate_snapshot!
|
163
|
+
a.save_snapshot
|
164
|
+
a.charge_card! 9000, "Apple"
|
165
|
+
a.save
|
166
|
+
a
|
167
|
+
end
|
168
|
+
it "should be loaded with correct version" do
|
169
|
+
org = aggregate
|
170
|
+
loaded = BankAccount.find org.aggregate_id
|
171
|
+
expect(loaded.balance).to eql org.balance
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'when generating state on an aggregate root' do
|
176
|
+
|
177
|
+
before(:each) do
|
178
|
+
@original_account = a_test_account
|
179
|
+
events = @original_account.aggregate_events
|
180
|
+
@account = BankAccount.aggregate_build events
|
181
|
+
@account.extend Sandthorn::AggregateRootSnapshot
|
182
|
+
@account.aggregate_snapshot!
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'account should have properties set' do
|
186
|
+
@account.balance.should eql 99000
|
187
|
+
@account.unpaid_interest_balance.should be > 1000
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should store snapshot data in aggregate_snapshot' do
|
191
|
+
@account.aggregate_snapshot.should be_a(Hash)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should store aggregate_version in aggregate_snapshot' do
|
195
|
+
@account.aggregate_snapshot[:aggregate_version].should eql(@original_account.aggregate_current_event_version)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should be able to load up from snapshot' do
|
199
|
+
|
200
|
+
events = [@account.aggregate_snapshot]
|
201
|
+
loaded = BankAccount.aggregate_build events
|
202
|
+
|
203
|
+
loaded.balance.should eql(@original_account.balance)
|
204
|
+
loaded.account_number.should eql(@original_account.account_number)
|
205
|
+
loaded.current_interest_info.should eql(@original_account.current_interest_info)
|
206
|
+
loaded.account_creation_date.should eql(@original_account.account_creation_date)
|
207
|
+
loaded.unpaid_interest_balance.should eql(@original_account.unpaid_interest_balance)
|
208
|
+
loaded.last_interest_calculation.should eql(@original_account.last_interest_calculation)
|
209
|
+
loaded.aggregate_id.should eql(@original_account.aggregate_id)
|
210
|
+
loaded.aggregate_originating_version.should eql(@account.aggregate_originating_version)
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
describe 'when saving to repository' do
|
217
|
+
let(:account) {a_test_account.extend Sandthorn::AggregateRootSnapshot}
|
218
|
+
it 'should raise an error if trying to save before creating a snapshot' do
|
219
|
+
lambda {account.save_snapshot}.should raise_error (RuntimeError)
|
220
|
+
end
|
221
|
+
it 'should not raise an error if snapshot was created' do
|
222
|
+
account.save
|
223
|
+
account.aggregate_snapshot!
|
224
|
+
lambda {account.save_snapshot}.should_not raise_error
|
225
|
+
end
|
226
|
+
it 'should set aggregate_snapshot to nil' do
|
227
|
+
account.save
|
228
|
+
account.aggregate_snapshot!
|
229
|
+
account.save_snapshot
|
230
|
+
account.aggregate_snapshot.should eql(nil)
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should raise error if trying to create snapshot before events are saved on object' do
|
234
|
+
lambda {account.aggregate_snapshot!}.should raise_error
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'should not raise an error if trying to create snapshot on object when events are saved' do
|
238
|
+
account.save
|
239
|
+
lambda {account.aggregate_snapshot!}.should_not raise_error
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should get snapshot on account find when a snapshot is saved' do
|
243
|
+
|
244
|
+
account.save
|
245
|
+
account.aggregate_snapshot!
|
246
|
+
account.save_snapshot
|
247
|
+
|
248
|
+
loaded = BankAccount.find account.aggregate_id
|
249
|
+
|
250
|
+
loaded.balance.should eql(account.balance)
|
251
|
+
loaded.account_number.should eql(account.account_number)
|
252
|
+
loaded.current_interest_info.should eql(account.current_interest_info)
|
253
|
+
loaded.account_creation_date.should eql(account.account_creation_date)
|
254
|
+
loaded.unpaid_interest_balance.should eql(account.unpaid_interest_balance)
|
255
|
+
loaded.last_interest_calculation.should eql(account.last_interest_calculation)
|
256
|
+
loaded.aggregate_id.should eql(account.aggregate_id)
|
257
|
+
loaded.aggregate_originating_version.should eql(account.aggregate_originating_version)
|
258
|
+
|
259
|
+
end
|
260
|
+
end
|