sandthorn 0.0.1
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 +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
|