eventus 0.4.3 → 0.5.0

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: 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