eventus 0.4.3 → 0.5.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 408485e918627490910f920486837c0c510d5a0e
4
+ data.tar.gz: 29465f58b251b03282354035a6e9d1b1a3aedbd5
5
+ SHA512:
6
+ metadata.gz: 19f20a16e354b861fa4bd92056d8cff60b03c1826286e839f8b9ac7f5b67919405c0ad93e706c9d02efb82ef88add6ca7fe086e235a1e408d4cb33159bedd720
7
+ data.tar.gz: 6cd4a5a6c1e14f0b9db9f192c92590041942918f255c9be722425553aff3c206c8b7c0ab5c2937e2758a27877b08842975a744b98aaf210a357c7e858aef148e
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - ruby-head
4
+ - 2.1.0
5
+ - 2.0.0
6
+ - 1.9.3
7
+ - jruby
8
+ - rbx
9
+
10
+ services:
11
+ - mongodb
12
+ - redis-server
data/Gemfile CHANGED
@@ -1,12 +1,14 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
5
  group :development do
6
+ gem 'rake'
6
7
  gem 'rspec'
7
- gem 'guard-rspec'
8
- gem 'libnotify' if RUBY_PLATFORM =~ /linux/i
9
8
  gem 'uuid'
10
- gem 'kyotocabinet-ruby'
11
9
  gem 'mongo'
10
+ gem 'bson_ext', :platforms => [:mri, :rbx]
11
+ gem 'redis'
12
+
13
+ gem 'kyotocabinet-ruby', :platforms => [:mri, :rbx] unless ENV['TRAVIS']
12
14
  end
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ Bundler.require
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -8,11 +8,6 @@ module Eventus
8
8
  instance
9
9
  end
10
10
 
11
- def apply(event_name, &block)
12
- raise "A block is required" unless block_given?
13
- define_method("apply_#{event_name}", &block)
14
- end
15
-
16
11
  def persistence
17
12
  @persistence ||= Eventus.persistence
18
13
  end
@@ -33,12 +28,6 @@ module Eventus
33
28
  end
34
29
 
35
30
  module InstanceMethods
36
- def populate(stream)
37
- @stream = stream
38
- stream.committed_events.each do |event|
39
- apply_change event['name'], event['body'], false
40
- end
41
- end
42
31
 
43
32
  def save
44
33
  version = @stream.version
@@ -53,20 +42,15 @@ module Eventus
53
42
  def on_concurrency_error(version, e)
54
43
  committed = @stream.committed_events.drop(version)
55
44
  uncommitted = @stream.uncommitted_events
56
- conflict = committed.any?{ |e| uncommitted.any? {|u| self.class.conflict?(e['name'], u['name'])} }
45
+ conflict = committed.any?{ |c| uncommitted.any? {|u| self.class.conflict?(c['name'], u['name'])} }
57
46
  raise Eventus::ConflictError if conflict
58
47
  false
59
48
  end
60
49
 
61
- def apply_change(name, body=nil, is_new=true)
62
- method_name = "apply_#{name}"
63
- self.send method_name, body if self.respond_to?(method_name)
64
-
65
- @stream.add(name, body) if is_new
66
- end
67
50
  end
68
51
 
69
52
  def self.included(base)
53
+ base.send :include, Consumer
70
54
  base.send :include, InstanceMethods
71
55
  base.send :extend, ClassMethods
72
56
  end
@@ -0,0 +1,37 @@
1
+ module Eventus
2
+ module Consumer
3
+ module ClassMethods
4
+ def apply(event_name, &block)
5
+ raise "A block is required" unless block_given?
6
+ define_method("apply_#{event_name}", &block)
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def populate(events)
12
+ if events.respond_to? :committed_events
13
+ @stream = events
14
+ events = events.committed_events
15
+ end
16
+
17
+ events.each do |event|
18
+ apply_change event['name'], event['body'], false
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def apply_change(name, body=nil, is_new=true)
25
+ method_name = "apply_#{name}"
26
+ self.send method_name, body if self.respond_to?(method_name)
27
+
28
+ @stream.add(name, body) if @stream && is_new
29
+ end
30
+ end
31
+
32
+ def self.included(base)
33
+ base.send :include, InstanceMethods
34
+ base.send :extend, ClassMethods
35
+ end
36
+ end
37
+ end
@@ -5,10 +5,9 @@ module Eventus
5
5
  class Mongo
6
6
  attr_reader :db, :commits
7
7
 
8
- def initialize(uri, options={})
9
- collection_name = options.delete(:collection) || 'eventus_commits'
10
- @db = build_db(uri, options)
11
- @commits = db.collection(collection_name)
8
+ def initialize(collection)
9
+ @db = collection.db
10
+ @commits = collection
12
11
  @commits.ensure_index :sid => ::Mongo::ASCENDING
13
12
  @commits.ensure_index :sid => ::Mongo::ASCENDING, :min => ::Mongo::ASCENDING
14
13
  end
@@ -28,7 +27,7 @@ module Eventus
28
27
  future = @commits.find_one({sid:doc['sid'], max:{:$gte => doc['min']}})
29
28
  raise Eventus::ConcurrencyError if future
30
29
  begin
31
- @commits.insert(doc)
30
+ @commits.insert(doc)
32
31
  rescue ::Mongo::OperationFailure => e
33
32
  raise Eventus::ConcurrencyError if e.error_code == 11000
34
33
  raise
@@ -0,0 +1,47 @@
1
+ require 'json'
2
+
3
+ module Eventus
4
+ module Persistence
5
+ class Redis
6
+ def initialize(redis)
7
+ @redis = redis
8
+ end
9
+
10
+ def load(id, min=1)
11
+ raw_events = @redis.zrange id, min-1, -1
12
+ raw_events.map { |e| JSON.parse(e) }
13
+ end
14
+
15
+ def commit(events)
16
+ streamId = events[0]['sid']
17
+ version = events[0]['sequence']
18
+ json_events = events.map{|e| e.to_json}
19
+ run_commit streamId, version, json_events
20
+
21
+ rescue ::Redis::CommandError
22
+ raise Eventus::ConcurrencyError
23
+ end
24
+
25
+ def run_commit(streamId, version, events)
26
+ @sha ||= @redis.script :load, COMMIT_LUA
27
+ @redis.evalsha(@sha, [streamId], [version] + events)
28
+ end
29
+
30
+ COMMIT_LUA = <<-LUA
31
+ local streamId = KEYS[1]
32
+ local version = tonumber(ARGV[1])
33
+
34
+ local actualVersion = tonumber(redis.call('zcount', streamId, '-inf', '+inf')) + 1
35
+ if actualVersion ~= version then
36
+ return redis.error_reply('conflict')
37
+ end
38
+
39
+ for i=2,#ARGV do
40
+ redis.call('zadd', streamId, version+i-2, ARGV[i])
41
+ end
42
+
43
+ return {'commit', tostring(version)}
44
+ LUA
45
+ end
46
+ end
47
+ end
@@ -2,6 +2,7 @@ module Eventus
2
2
  module Persistence
3
3
  autoload :KyotoCabinet, 'eventus/persistence/kyotocabinet'
4
4
  autoload :Mongo, 'eventus/persistence/mongo'
5
+ autoload :Redis, 'eventus/persistence/redis'
5
6
  autoload :InMemory, 'eventus/persistence/in_memory'
6
7
  end
7
8
  end
@@ -1,3 +1,3 @@
1
1
  module Eventus
2
- VERSION = "0.4.3"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/eventus.rb CHANGED
@@ -2,7 +2,6 @@ require 'logger'
2
2
 
3
3
  module Eventus
4
4
  autoload :Serializers, 'eventus/serializers'
5
- autoload :AggregateRoot, 'eventus/aggregate_root'
6
5
  autoload :Dispatchers, 'eventus/dispatchers'
7
6
  autoload :Persistence, 'eventus/persistence'
8
7
  autoload :VERSION, 'eventus/version'
@@ -38,4 +37,4 @@ module Eventus
38
37
  end
39
38
  end
40
39
 
41
- %w{stream errors}.each { |r| require "eventus/#{r}" }
40
+ %w{stream errors aggregate_root consumer}.each { |r| require "eventus/#{r}" }
@@ -18,7 +18,7 @@ end
18
18
 
19
19
  describe Eventus::AggregateRoot do
20
20
  let(:events) { [] }
21
- let(:persistence) { stub.as_null_object }
21
+ let(:persistence) { double.as_null_object }
22
22
 
23
23
  before do
24
24
  TestAgg.stub(:persistence).and_return(persistence)
@@ -43,7 +43,7 @@ describe Eventus::AggregateRoot do
43
43
 
44
44
  describe "when applying a new change" do
45
45
  let(:aggregate) { TestAgg.new }
46
- let(:stream) { stub(:stream, :committed_events => []) }
46
+ let(:stream) { double(:stream, :committed_events => []) }
47
47
 
48
48
  before do
49
49
  aggregate.populate(stream)
@@ -57,7 +57,7 @@ describe Eventus::AggregateRoot do
57
57
 
58
58
  describe "when saving" do
59
59
  let(:aggregate) { TestAgg.new }
60
- let(:stream) { stub(:stream, :committed_events => events, :version => 0) }
60
+ let(:stream) { double(:stream, :committed_events => events, :version => 0) }
61
61
  let(:events) { [{'name' => :lemon_squeezed}] }
62
62
 
63
63
  before do
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ class TestConsumer
4
+ include Eventus::Consumer
5
+
6
+ attr_accessor :loaded
7
+
8
+ def bake_cake
9
+ apply_change :cake_baked, :flavor => 'strawberry'
10
+ end
11
+
12
+ apply :dino do |e|
13
+ @loaded = true
14
+ end
15
+ end
16
+
17
+ describe Eventus::Consumer do
18
+ it "should populate from events" do
19
+ events = [
20
+ {'name' => 'dino', 'body' => {}}
21
+ ]
22
+ cons = TestConsumer.new
23
+ cons.populate events
24
+ cons.loaded.should == true
25
+ end
26
+
27
+ it "should ignore events it's unconcerned with" do
28
+ cons = TestConsumer.new
29
+ cons.bake_cake
30
+ end
31
+ end
@@ -1,12 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Eventus::Dispatchers::Synchronous do
4
- let(:persistence){ stub.as_null_object }
4
+ let(:persistence){ double.as_null_object }
5
5
  let(:dispatcher) { Eventus::Dispatchers::Synchronous.new(persistence) { @hit = true } }
6
6
 
7
7
  before do
8
8
  persistence.should_receive(:mark_dispatched)
9
- dispatcher.dispatch([stub])
9
+ dispatcher.dispatch([double])
10
10
  end
11
11
 
12
12
  it "should invoke block" do
@@ -77,7 +77,7 @@ describe Eventus::Persistence::InMemory do
77
77
  end
78
78
 
79
79
  describe "when serialization is set" do
80
- let(:serializer) { stub }
80
+ let(:serializer) { double }
81
81
  before do
82
82
  options[:serializer] = serializer
83
83
  end
@@ -93,7 +93,7 @@ describe Eventus::Persistence::KyotoCabinet do
93
93
  end
94
94
 
95
95
  describe "when serialization is set" do
96
- let(:serializer) { stub }
96
+ let(:serializer) { double }
97
97
  let(:persistence) { Eventus::Persistence::KyotoCabinet.new(:path => '%', :serializer => serializer) }
98
98
 
99
99
  it "should use serializer" do
@@ -111,5 +111,5 @@ describe Eventus::Persistence::KyotoCabinet do
111
111
  result[0].should == input
112
112
  end
113
113
  end
114
- end
114
+ end unless ENV['TRAVIS'] || RUBY_ENGINE == 'jruby'
115
115
 
@@ -5,10 +5,8 @@ describe Eventus::Persistence::Mongo do
5
5
  let(:uuid) { UUID.new }
6
6
 
7
7
  before(:all) do
8
- #Wipe collection
9
- Eventus::Persistence::Mongo.new(MONGO_URI).commits.drop
10
-
11
- @persistence = Eventus::Persistence::Mongo.new(MONGO_URI)
8
+ db = Mongo::MongoClient.from_uri(MONGO_URI).db
9
+ @persistence = Eventus::Persistence::Mongo.new(db['eventus_commits'])
12
10
  @persistence.commits.remove
13
11
  end
14
12
 
@@ -63,7 +61,7 @@ describe Eventus::Persistence::Mongo do
63
61
  end
64
62
 
65
63
  it "should load undispatched events" do
66
- result = persistence.load_undispatched_commits
64
+ persistence.load_undispatched_commits
67
65
  end
68
66
 
69
67
  it "should mark an event as dispatched" do
@@ -96,9 +94,9 @@ describe Eventus::Persistence::Mongo do
96
94
 
97
95
  it 'should reraise non-"already exists" errors' do
98
96
  e = Mongo::OperationFailure.new('fail', 500)
99
- commits = persistence.instance_eval { @commits }
97
+ commits = persistence.commits
100
98
  commits.stub(:insert) { raise e }
101
- lambda {persistence.commit create_commit('commitid', 1, "first", "second", "third")}.should raise_error(e)
99
+ expect {persistence.commit create_commit('commitid', 1, "first", "second", "third")}.to raise_error(Mongo::OperationFailure)
102
100
  end
103
101
  end
104
102
 
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe Eventus::Persistence::Redis do
4
+ let(:persistence) { Eventus::Persistence::Redis.new(@redis) }
5
+ let(:uuid) { UUID.new }
6
+
7
+ before(:all) { @redis = Redis.new }
8
+ before { @redis.flushall }
9
+
10
+ it "should store complex objects" do
11
+ id = uuid.generate :compact
12
+ o = {'a' => 'super', 'complex' => ['object', 'with', {'nested' => ['members', 'galore', 1]}]}
13
+ commit = create_commit(id, 1, o)
14
+ persistence.commit(commit)
15
+
16
+ result = persistence.load id
17
+ result[0].should == commit[0]
18
+ end
19
+
20
+ it "should return no events when key not found" do
21
+ result = persistence.load "my_id"
22
+ result.should be_empty
23
+ end
24
+
25
+ it "should return events ordered" do
26
+ id = uuid.generate :compact
27
+ persistence.commit create_commit(id, 1, "one", "two")
28
+ persistence.commit create_commit(id, 3, "three", "four")
29
+ persistence.commit create_commit(id, 5, "five", "six")
30
+ persistence.commit create_commit("other", 1, "cake", "batter")
31
+
32
+ result = persistence.load id
33
+ result.map{|r| r['body']}.should == ["one", "two", "three", "four", "five", "six"]
34
+ end
35
+
36
+ describe "when events exist" do
37
+ let(:id) { uuid.generate :compact }
38
+ let(:events) { create_commit(id, 1, *(1..20)).each_with_index {|e,i| e['dispatched'] = i.even? } }
39
+ before do
40
+ persistence.commit events
41
+ other_events = create_commit("another", 1, *(1..60)).each_with_index {|e,i| e['dispatched'] = i.even? }
42
+ persistence.commit other_events
43
+ end
44
+
45
+ it "should load events" do
46
+ result = persistence.load id
47
+ result.length.should == 20
48
+ end
49
+
50
+ #it "should load undispatched events" do
51
+ #result = persistence.load_undispatched
52
+ #result.length.should == 40
53
+ #end
54
+
55
+ #it "should mark an event as dispatched" do
56
+ #result = persistence.load_undispatched[0]
57
+ #persistence.mark_dispatched(result['sid'], result['sequence'])
58
+ #persistence.load_undispatched.include?(result).should be_false
59
+ #end
60
+
61
+ it "should throw concurrency exception if the same event number is added" do
62
+ expect {persistence.commit create_commit(id, 3, "This is taken")}.to raise_error(Eventus::ConcurrencyError)
63
+ end
64
+
65
+ it "should rollback changes on concurrency error" do
66
+ begin
67
+ persistence.commit create_commit(id, 3, "first", "second", "third")
68
+ rescue Eventus::ConcurrencyError
69
+ end
70
+
71
+ result = persistence.load id
72
+ result.length.should == 20
73
+ end
74
+
75
+ it "should load all events from a minimum" do
76
+ result = persistence.load id, 10
77
+ result.length.should == 11
78
+ end
79
+ end
80
+ end
data/spec/stream_spec.rb CHANGED
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  describe Eventus::Stream do
4
4
  let(:id) { UUID.generate(:compact) }
5
5
  let(:stream) { Eventus::Stream.new(id, persistence, dispatcher) }
6
- let(:persistence) { stub(:persistence).as_null_object }
7
- let(:dispatcher) { stub(:dispatcher).as_null_object }
6
+ let(:persistence) { double(:persistence).as_null_object }
7
+ let(:dispatcher) { double(:dispatcher).as_null_object }
8
8
 
9
9
  it "should use id" do
10
10
  stream.id.should == id
@@ -26,7 +26,7 @@ describe Eventus::Stream do
26
26
 
27
27
  describe "when events available from persistence" do
28
28
  before do
29
- persistence.should_receive(:load).and_return([stub, stub])
29
+ persistence.should_receive(:load).and_return([double, double])
30
30
  end
31
31
 
32
32
  it "should have an equal number of events" do
@@ -89,7 +89,7 @@ describe Eventus::Stream do
89
89
  end
90
90
 
91
91
  it "should load latest events" do
92
- persistence.should_receive(:load).with(id, 0).and_return([stub, stub, stub])
92
+ persistence.should_receive(:load).with(id, 0).and_return([double, double, double])
93
93
  stream.commit rescue nil
94
94
  stream.version.should == 3
95
95
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
5
- prerelease:
4
+ version: 0.5.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Jason Staten
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-03-04 00:00:00.000000000 Z
11
+ date: 2014-04-21 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: An Event Store
15
14
  email:
@@ -18,7 +17,8 @@ executables: []
18
17
  extensions: []
19
18
  extra_rdoc_files: []
20
19
  files:
21
- - .gitignore
20
+ - ".gitignore"
21
+ - ".travis.yml"
22
22
  - Gemfile
23
23
  - Guardfile
24
24
  - README.md
@@ -26,6 +26,7 @@ files:
26
26
  - eventus.gemspec
27
27
  - lib/eventus.rb
28
28
  - lib/eventus/aggregate_root.rb
29
+ - lib/eventus/consumer.rb
29
30
  - lib/eventus/dispatchers.rb
30
31
  - lib/eventus/dispatchers/synchronous.rb
31
32
  - lib/eventus/errors.rb
@@ -33,47 +34,51 @@ files:
33
34
  - lib/eventus/persistence/in_memory.rb
34
35
  - lib/eventus/persistence/kyotocabinet.rb
35
36
  - lib/eventus/persistence/mongo.rb
37
+ - lib/eventus/persistence/redis.rb
36
38
  - lib/eventus/serializers.rb
37
39
  - lib/eventus/serializers/marshal.rb
38
40
  - lib/eventus/serializers/msgpack.rb
39
41
  - lib/eventus/stream.rb
40
42
  - lib/eventus/version.rb
41
43
  - spec/aggregate_root_spec.rb
44
+ - spec/consumer_spec.rb
42
45
  - spec/dispatchers/synchronous_spec.rb
43
46
  - spec/persistence/in_memory_spec.rb
44
47
  - spec/persistence/kyotocabinet_spec.rb
45
48
  - spec/persistence/mongo_spec.rb
49
+ - spec/persistence/redis_spec.rb
46
50
  - spec/spec_helper.rb
47
51
  - spec/stream_spec.rb
48
52
  homepage: ''
49
53
  licenses: []
54
+ metadata: {}
50
55
  post_install_message:
51
56
  rdoc_options: []
52
57
  require_paths:
53
58
  - lib
54
59
  required_ruby_version: !ruby/object:Gem::Requirement
55
- none: false
56
60
  requirements:
57
- - - ! '>='
61
+ - - ">="
58
62
  - !ruby/object:Gem::Version
59
63
  version: '0'
60
64
  required_rubygems_version: !ruby/object:Gem::Requirement
61
- none: false
62
65
  requirements:
63
- - - ! '>='
66
+ - - ">="
64
67
  - !ruby/object:Gem::Version
65
68
  version: '0'
66
69
  requirements: []
67
70
  rubyforge_project: eventus
68
- rubygems_version: 1.8.23
71
+ rubygems_version: 2.2.2
69
72
  signing_key:
70
- specification_version: 3
73
+ specification_version: 4
71
74
  summary: Event Store
72
75
  test_files:
73
76
  - spec/aggregate_root_spec.rb
77
+ - spec/consumer_spec.rb
74
78
  - spec/dispatchers/synchronous_spec.rb
75
79
  - spec/persistence/in_memory_spec.rb
76
80
  - spec/persistence/kyotocabinet_spec.rb
77
81
  - spec/persistence/mongo_spec.rb
82
+ - spec/persistence/redis_spec.rb
78
83
  - spec/spec_helper.rb
79
84
  - spec/stream_spec.rb