sandthorn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8c7be6683c105bcc9b6c0366a63d191c15260735
4
+ data.tar.gz: be5e8b9362f8ec00c1fa2949532c5064f81784e4
5
+ SHA512:
6
+ metadata.gz: 064291bac86efb3730d596afd4079dc3d71085f2974aa036df707594561886761a85f3ff73bcaec9d5cfc24cfb47c712b0a4a3263e2c636da9a3079dda319c1a
7
+ data.tar.gz: a28989f9f69f112666b7e21c5b81c01f952451b3141d79012df97ab01964ebe462e28fd74bb9d2bfc897fa520112b07b8d8796c726036598fdd9fb43929efa84
data/.autotest ADDED
@@ -0,0 +1,3 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ %w{.git spec/db}.each {|exception| at.add_exception(exception)}
3
+ end
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ *.sqlite3
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format d
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ sandthorn
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sandthorn.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,72 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sandthorn (0.0.1)
5
+ dirty_hashy
6
+ uuidtools
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (4.0.4)
12
+ i18n (~> 0.6, >= 0.6.9)
13
+ minitest (~> 4.2)
14
+ multi_json (~> 1.3)
15
+ thread_safe (~> 0.1)
16
+ tzinfo (~> 0.3.37)
17
+ atomic (1.1.16)
18
+ autotest-standalone (4.5.11)
19
+ awesome_print (1.2.0)
20
+ coderay (1.1.0)
21
+ diff-lcs (1.2.5)
22
+ dirty_hashy (0.2.1)
23
+ activesupport
24
+ gem-release (0.7.1)
25
+ i18n (0.6.9)
26
+ method_source (0.8.2)
27
+ minitest (4.7.5)
28
+ multi_json (1.9.0)
29
+ pg (0.17.1)
30
+ pry (0.9.12.6)
31
+ coderay (~> 1.0)
32
+ method_source (~> 0.8)
33
+ slop (~> 3.4)
34
+ pry-doc (0.6.0)
35
+ pry (~> 0.9)
36
+ yard (~> 0.8)
37
+ rake (10.1.1)
38
+ rspec (2.14.1)
39
+ rspec-core (~> 2.14.0)
40
+ rspec-expectations (~> 2.14.0)
41
+ rspec-mocks (~> 2.14.0)
42
+ rspec-core (2.14.8)
43
+ rspec-expectations (2.14.5)
44
+ diff-lcs (>= 1.1.3, < 2.0)
45
+ rspec-mocks (2.14.6)
46
+ sandthorn_driver_sequel (1.0.5)
47
+ pg
48
+ sequel
49
+ sequel (4.8.0)
50
+ slop (3.5.0)
51
+ sqlite3 (1.3.9)
52
+ thread_safe (0.2.0)
53
+ atomic (>= 1.1.7, < 2)
54
+ tzinfo (0.3.39)
55
+ uuidtools (2.1.4)
56
+ yard (0.8.7.3)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ autotest-standalone
63
+ awesome_print
64
+ bundler (~> 1.3)
65
+ gem-release
66
+ pry
67
+ pry-doc
68
+ rake
69
+ rspec
70
+ sandthorn!
71
+ sandthorn_driver_sequel
72
+ sqlite3
data/Gemfile.lock.old ADDED
@@ -0,0 +1,54 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sandthorn (0.0.1)
5
+ upptec_event_sequel_driver (~> 1.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ autotest-standalone (4.5.11)
11
+ awesome_print (1.2.0)
12
+ coderay (1.1.0)
13
+ diff-lcs (1.2.5)
14
+ gem-release (0.7.1)
15
+ method_source (0.8.2)
16
+ pg (0.17.1)
17
+ pry (0.9.12.6)
18
+ coderay (~> 1.0)
19
+ method_source (~> 0.8)
20
+ slop (~> 3.4)
21
+ pry-doc (0.5.1)
22
+ pry (>= 0.9)
23
+ yard (>= 0.8)
24
+ rake (10.1.1)
25
+ rspec (2.14.1)
26
+ rspec-core (~> 2.14.0)
27
+ rspec-expectations (~> 2.14.0)
28
+ rspec-mocks (~> 2.14.0)
29
+ rspec-core (2.14.8)
30
+ rspec-expectations (2.14.5)
31
+ diff-lcs (>= 1.1.3, < 2.0)
32
+ rspec-mocks (2.14.6)
33
+ sequel (4.7.0)
34
+ slop (3.4.7)
35
+ sqlite3 (1.3.9)
36
+ upptec_event_sequel_driver (1.2.2)
37
+ pg
38
+ sequel
39
+ yard (0.8.7.3)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ autotest-standalone
46
+ awesome_print
47
+ bundler (~> 1.3)
48
+ gem-release
49
+ pry
50
+ pry-doc
51
+ rake
52
+ rspec
53
+ sandthorn!
54
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Sandthorn Event Sourcing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Lars Krantz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Sandthorn Event Sourcing
2
+ A ruby framwork for saving an object's state as a series of events, and tracking non state changing events.
3
+
4
+ ## What is Event Sourcing
5
+
6
+ "Capture all changes to an application state as a sequence of events."
7
+ [Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html)
8
+
9
+ ## The short story
10
+
11
+ Think of it as an object database where you not only what the new value of the attribute is but when and why it changed.
12
+ _Example:_
13
+
14
+ ```ruby
15
+
16
+ #Setup the Aggregate
17
+
18
+ require 'sandthorn/aggregate_root_dirty_hashy' #the one available right now
19
+
20
+ class Ship
21
+ include Sandthorn::AggregateRoot::DirtyHashy
22
+ attr_reader :name
23
+
24
+ def initialize name: nil, shipping_company: nil
25
+ @name = name
26
+ end
27
+
28
+ # state-changing command
29
+ def rename! new_name: ""
30
+ unless new_name.empty? or new_name == name
31
+ @name = new_name
32
+ ship_was_renamed
33
+ end
34
+ end
35
+ private
36
+ # commit the event and state-change is automatically recorded.
37
+ def ship_was_renamed
38
+ commit
39
+ end
40
+ end
41
+
42
+ #Setup the framework with the sequel driver for persistance
43
+ url = "path to sql" #Example sqlite://path/sequel_driver.sqlite3
44
+ catch_all_config = [ { driver: SandthornDriverSequel.driver_from_url(url: url) } ]
45
+ Sandthorn.configuration = catch_all_config
46
+
47
+ #migrate db schema for the sequel driver
48
+ migrator = SandthornDriverSequel::Migration.new url: url
49
+ SandthornDriverSequel.migrate_db url: url
50
+
51
+ #Usage
52
+ ship = Ship.new "Titanic"
53
+ ship.rename! "Vasa"
54
+ puts ship.aggregate_events
55
+ ship.save
56
+
57
+ new_ship = Ship.find ship.id
58
+ puts ship.name
59
+ .
60
+ .
61
+
62
+ For more info look at the specs.
63
+
64
+ ```
65
+
66
+ ## Installation
67
+
68
+ Add this line to your application's Gemfile:
69
+
70
+ gem 'sandthorn'
71
+
72
+ And then execute:
73
+
74
+ $ bundle
75
+
76
+ Or install it yourself as:
77
+
78
+ $ gem install sandthorn
79
+
80
+ ## Usage
81
+
82
+ TODO: Write usage instructions here
83
+
84
+ ## Contributing
85
+
86
+ 1. Fork it
87
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
89
+ 4. Push to the branch (`git push origin my-new-feature`)
90
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/sandthorn.rb ADDED
@@ -0,0 +1,78 @@
1
+ require "sandthorn/version"
2
+ require "sandthorn/errors"
3
+ require 'uuidtools'
4
+ require 'yaml'
5
+
6
+ module Sandthorn
7
+ class << self
8
+ def configuration= configuration
9
+ @configuration = configuration
10
+ end
11
+ def configuration
12
+ @configuration ||= []
13
+ end
14
+
15
+ def serialize data
16
+ #Marshal.dump(data)
17
+ YAML::dump(data)
18
+ #Oj.dump(data)
19
+ #MessagePack.pack(data, symbolize_keys: true)
20
+ end
21
+
22
+ def deserialize data
23
+ #Marshal.load(data)
24
+ YAML::load(data)
25
+ #Oj.load(data)
26
+ #MessagePack.unpack(data, symbolize_keys: true)
27
+ end
28
+
29
+ def generate_aggregate_id
30
+ UUIDTools::UUID.random_create.to_s
31
+ end
32
+
33
+ def get_aggregate_events aggregate_id, class_name
34
+ driver_for(class_name).get_aggregate_events aggregate_id, class_name
35
+ end
36
+
37
+ def save_events aggregate_events, originating_aggregate_version, aggregate_id, class_name
38
+ #begin
39
+ driver_for(class_name).save_events aggregate_events, originating_aggregate_version, aggregate_id, *class_name
40
+ #rescue UpptecEventSequelDriver::Errors::WrongAggregateVersionError => sequel_error
41
+ # raise UpptecEventFramework::Errors::ConcurrencyError.new sequel_error.message
42
+ #end
43
+ end
44
+
45
+ def get_aggregate aggregate_id, class_name
46
+ driver_for(class_name).get_aggregate aggregate_id, class_name
47
+ end
48
+
49
+ def save_snapshot aggregate_snapshot, aggregate_id, class_name
50
+ driver_for(class_name).save_snapshot aggregate_snapshot, aggregate_id, class_name
51
+ end
52
+
53
+ private
54
+ def driver_for class_name, &block
55
+ driver = identify_driver_from_class class_name
56
+ block.call(driver) if block_given?
57
+ driver
58
+ end
59
+ def identify_driver_from_class class_name
60
+ matches = configuration.select do |conf|
61
+ r = Regexp.new "^#{conf[:aggregate_pattern]}"
62
+ pattern = class_name.to_s
63
+ conf[:aggregate_pattern].nil? || r.match(pattern)
64
+ end
65
+ raise Sandthorn::Errors::ConfigurationError.new "Aggregate class #{class_name} is not configured for EventStore" if matches.empty?
66
+ first_match = matches.first
67
+ first_match[:driver]
68
+ #UpptecEventSequelDriver.driver_from_url url: first_match[:url], context: first_match[:context]
69
+ end
70
+ # def drivers_for_class_names class_names: []
71
+ # return all_drivers if class_names.empty?
72
+ # class_names.map { |e| driver_for e }.uniq { |d| { url: d.url, context: d.context } }
73
+ # end
74
+ # def all_drivers
75
+ # configuration.map { |e| UpptecEventSequelDriver.driver_from_url url: e[:url], context: e[:context] }
76
+ # end
77
+ end
78
+ end
@@ -0,0 +1,186 @@
1
+ module Sandthorn
2
+ module AggregateRoot
3
+ module Base
4
+
5
+ attr_reader :aggregate_id
6
+ attr_reader :aggregate_events
7
+ attr_reader :aggregate_current_event_version
8
+ attr_reader :aggregate_originating_version
9
+ attr_reader :aggregate_stored_serialized_object
10
+
11
+ alias :id :aggregate_id
12
+
13
+
14
+ def aggregate_base_initialize
15
+ @aggregate_current_event_version = 0
16
+ @aggregate_originating_version = 0
17
+ @aggregate_events = []
18
+ end
19
+
20
+
21
+
22
+ def save
23
+ aggregate_events.each do |event|
24
+ event[:event_data] = Sandthorn.serialize event[:event_args]
25
+ event[:event_args] = nil #Not send extra data over the wire
26
+ end
27
+ unless aggregate_events.empty?
28
+ Sandthorn.save_events( aggregate_events, aggregate_originating_version, aggregate_id, self.class.name)
29
+ @aggregate_events = []
30
+ @aggregate_originating_version = @aggregate_current_event_version
31
+ end
32
+ self
33
+ end
34
+
35
+ def commit *args
36
+ increase_current_aggregate_version!
37
+ method_name = caller[0][/`.*'/][1..-2]
38
+ aggregate_attribute_deltas = get_delta
39
+
40
+ unless aggregate_attribute_deltas.empty?
41
+ data = {:method_name => method_name, :method_args => args, :attribute_deltas => aggregate_attribute_deltas}
42
+ data.merge!({trace: @aggregate_trace_information}) unless @aggregate_trace_information.nil? || @aggregate_trace_information.empty?
43
+ @aggregate_events << ({:aggregate_version => @aggregate_current_event_version, :event_name => method_name, :event_args => data})
44
+ end
45
+ self
46
+ end
47
+
48
+ alias :record_event :commit
49
+
50
+
51
+ def all
52
+ end
53
+
54
+ def aggregate_trace args
55
+ @aggregate_trace_information = args
56
+ yield self
57
+ @aggregate_trace_information = nil
58
+ end
59
+
60
+ module ClassMethods
61
+
62
+ @@aggregate_trace_information = nil
63
+ def aggregate_trace args
64
+ @@aggregate_trace_information = args
65
+ @aggregate_trace_information = args
66
+ yield self
67
+ @@aggregate_trace_information = nil
68
+ @aggregate_trace_information = nil
69
+ end
70
+
71
+ def find aggregate_id
72
+ class_name = self.respond_to?(:name) ? self.name : self.class # to be able to extend a string for example.
73
+ events = Sandthorn.get_aggregate(aggregate_id, class_name)
74
+ raise Sandthorn::Errors::AggregateNotFound unless events and !events.empty?
75
+
76
+ transformed_events = events.map { |e| e.merge(event_args: Sandthorn.deserialize(e[:event_data])) }
77
+ aggregate_build transformed_events
78
+ end
79
+
80
+ def new *args
81
+ aggregate = super
82
+ aggregate.aggregate_base_initialize
83
+ aggregate.aggregate_trace @@aggregate_trace_information do |aggr|
84
+ aggr.aggregate_initialize
85
+ aggr.send :set_aggregate_id, Sandthorn.generate_aggregate_id
86
+ aggr.send :commit, *args
87
+ return aggr
88
+ end
89
+ end
90
+
91
+
92
+ def aggregate_build events
93
+ first_event = events.first()
94
+ current_aggregate_version = 0
95
+ if first_event[:event_name] == "aggregate_set_from_snapshot"
96
+ aggregate = first_event[:event_args][0]
97
+ current_aggregate_version = aggregate.aggregate_originating_version
98
+ events.shift
99
+ # elsif first_event[:event_name] == "instance_extended_as_aggregate"
100
+ # aggregate = first_event[:event_args][0]
101
+ #aggregate.extend AggregateRoot
102
+ # events.pop
103
+ else
104
+ new_args = events.first()[:event_args][:method_args]
105
+
106
+ if new_args.nil?
107
+ aggregate = new
108
+ else
109
+ aggregate = new *new_args
110
+ end
111
+ aggregate.send :aggregate_clear_current_event_version!
112
+ end
113
+
114
+ attributes = {}
115
+ events.each do |event|
116
+ event_args = event[:event_args]
117
+ event_name = event[:event_name]
118
+
119
+ if event_name == "aggregate_set_from_snapshot"
120
+ #aggregate.send event_name,*event_args
121
+ #set the attribute hash instead of instance varaibles directly
122
+ next
123
+ end
124
+ next if event_name == "instance_extended_as_aggregate"
125
+
126
+ attribute_deltas = event_args[:attribute_deltas]
127
+ if (event[:aggregate_version].nil? == false)
128
+ current_aggregate_version = event[:aggregate_version]
129
+ end
130
+
131
+ if !attribute_deltas.nil?
132
+ deltas = {}
133
+ attribute_deltas.each do |delta|
134
+ deltas[delta[:attribute_name]] = delta[:new_value]
135
+ end
136
+ attributes.merge! deltas
137
+ end
138
+ end
139
+ aggregate.send :clear_aggregate_events
140
+ aggregate.send :set_orginating_aggregate_version!, current_aggregate_version
141
+ aggregate.send :set_current_aggregate_version!, current_aggregate_version
142
+ aggregate.send :set_instance_variables!, attributes
143
+ aggregate
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def set_instance_variables! attributes
150
+ attributes.each_pair do |k,v|
151
+ self.instance_variable_set "@#{k}", v
152
+ end
153
+ end
154
+
155
+ def extract_relevant_aggregate_instance_variables
156
+ instance_variables.select { |i| i.to_s != "@hashy" && (!i.to_s.start_with?("@aggregate_") || i.to_s == "@aggregate_id") }
157
+ end
158
+
159
+ def set_orginating_aggregate_version! aggregate_version
160
+ @aggregate_originating_version = aggregate_version
161
+ end
162
+
163
+ def increase_current_aggregate_version!
164
+ @aggregate_current_event_version += 1
165
+ end
166
+
167
+ def set_current_aggregate_version! aggregate_version
168
+ @aggregate_current_event_version = aggregate_version
169
+ end
170
+
171
+ def clear_aggregate_events
172
+ @aggregate_events = []
173
+ @aggregate_attribute_deltas = []
174
+ end
175
+
176
+ def aggregate_clear_current_event_version!
177
+ @aggregate_current_event_version = 0
178
+ end
179
+
180
+ def set_aggregate_id aggregate_id
181
+ @aggregate_id = aggregate_id
182
+ end
183
+
184
+ end
185
+ end
186
+ end