rails_event_store 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 55f134f7a328395f2aaaee363409e3174c781ced
4
+ data.tar.gz: c0a7f0ac41fbe5688cc0407cd3ed07a62437d9f1
5
+ SHA512:
6
+ metadata.gz: fae4e8a9c0f3d414720795c557e7a6c2647b6d5b28f2197eba9715d54579b4028b063d6ed8a357055a158c1d694c71a1e0238f03fa8687d6251061101671019a
7
+ data.tar.gz: d4336f83e8c4a6e366ccb9e9e2f0a2c2deb545e766f21446354460fb92b288ec7e4129d8e03063cad436b64c78a18586c75c42536e8618fd2fe54d180cd1e2c8
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+
5
+ before_install: gem install bundler
6
+
7
+ gemfile:
8
+ Gemfile
9
+
10
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # EventStore
2
+
3
+ A Ruby implementation of an EventStore based on Active Record.
4
+
5
+ ## Installation
6
+
7
+ * Add following line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rails_event_store'
11
+ ```
12
+
13
+ * Use provided task to generate a table to store events in you DB.
14
+
15
+ ```ruby
16
+ rails generate rails_event_store:migrate
17
+ rake db:migrate
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ To communicate with ES you have to create instance of `RailsEventStore::Client` class.
23
+
24
+ ```ruby
25
+ client = RailsEventStore::Client.new
26
+ ```
27
+
28
+ #### Creating new event
29
+
30
+ Firstly you have to define own event model extending `RailsEventStore::Event` class.
31
+
32
+ ```ruby
33
+ class OrderCreated < RailsEventStore::Event
34
+ end
35
+ ```
36
+
37
+ ```ruby
38
+ stream_name = "order_1"
39
+ event_data = {
40
+ data: { data: "sample" },
41
+ event_id: "b2d506fd-409d-4ec7-b02f-c6d2295c7edd"
42
+ }
43
+ event = OrderCreated.new(event_data)
44
+
45
+ #publishing event for specific stream
46
+ client.publish_event(event, stream_name)
47
+
48
+ #publishing global event. In this case stream_name is 'all'.
49
+ client.publish_event(event)
50
+ ```
51
+
52
+ #### Creating new event with optimistic locking:
53
+
54
+ ```ruby
55
+ class OrderCreated < RailsEventStore::Event
56
+ end
57
+ ```
58
+
59
+ ```ruby
60
+ stream_name = "order_1"
61
+ event_data = {
62
+ data: { data: "sample" },
63
+ event_id: "b2d506fd-409d-4ec7-b02f-c6d2295c7edd"
64
+ }
65
+ event = OrderCreated.new(event_data)
66
+ expected_version = "850c347f-423a-4158-a5ce-b885396c5b73" #last event_id
67
+ client.publish_event(event, stream_name, expected_version)
68
+ ```
69
+
70
+ #### Reading stream's events forward in batch
71
+
72
+ ```ruby
73
+ stream_name = "order_1"
74
+ start = 1
75
+ count = 40
76
+ client.read_all_events(stream_name, start, count)
77
+ ```
78
+
79
+ #### Reading all events from stream forward
80
+
81
+ This method allows us to load all stream's events ascending.
82
+
83
+ ```ruby
84
+ stream_name = "order_1"
85
+ client.read_all_events(stream_name)
86
+ ```
87
+
88
+ #### Reading all events forward
89
+
90
+ This method allows us to load all stored events ascending.
91
+
92
+ ```ruby
93
+ client.read_all_streams
94
+ ```
95
+
96
+ #### Subscribing to events
97
+
98
+ To listen on specific events synchronously you have to create subscriber reprezentation. The only requirement is that subscriber class has to implement the 'handle_event(event)' method.
99
+
100
+ ```ruby
101
+ class InvoiceReadModel
102
+ def handle_event(event)
103
+ #we deal here with event's data
104
+ end
105
+ end
106
+ ```
107
+
108
+ ```ruby
109
+ invoice = InvoiceReadModel.new
110
+ client.subscribe(invoice, ['PriceChanged', 'ProductAdded'])
111
+ ```
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+
@@ -0,0 +1,14 @@
1
+ require 'rails_event_store/event_entity'
2
+ require 'rails_event_store/generators/migrate_generator'
3
+ require 'rails_event_store/generators/templates/migration_template'
4
+ require 'rails_event_store/version'
5
+ require 'rails_event_store/event'
6
+ require 'rails_event_store/repositories/event_repository'
7
+ require 'rails_event_store/actions/append_event_to_stream'
8
+ require 'rails_event_store/actions/read_all_events'
9
+ require 'rails_event_store/actions/read_events_batch'
10
+ require 'rails_event_store/actions/delete_stream_events'
11
+ require 'rails_event_store/actions/read_all_streams'
12
+ require 'rails_event_store/pub_sub/broker'
13
+ require 'rails_event_store/errors'
14
+ require 'rails_event_store/client'
@@ -0,0 +1,34 @@
1
+ module RailsEventStore
2
+ module Actions
3
+ class AppendEventToStream
4
+
5
+ def initialize(repository)
6
+ @repository = repository
7
+ end
8
+
9
+ def call(stream_name, event, expected_version)
10
+ raise WrongExpectedEventVersion if version_incorrect?(stream_name, expected_version)
11
+ event.validate!
12
+ save_event(event, stream_name)
13
+ return event
14
+ end
15
+
16
+ private
17
+ attr_reader :repository
18
+
19
+ def version_incorrect?(stream_name, expected_version)
20
+ unless expected_version.nil?
21
+ find_last_event_version(stream_name) != expected_version
22
+ end
23
+ end
24
+
25
+ def find_last_event_version(stream_name)
26
+ repository.last_stream_event(stream_name).event_id
27
+ end
28
+
29
+ def save_event(event, stream_name)
30
+ repository.create(event.to_h.merge!(stream: stream_name))
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ module RailsEventStore
2
+ module Actions
3
+ class DeleteStreamEvents
4
+
5
+ def initialize(repository)
6
+ @repository = repository
7
+ end
8
+
9
+ def call(stream_name)
10
+ raise IncorrectStreamData if stream_name.nil? || stream_name.empty?
11
+ delete_stream(stream_name)
12
+ end
13
+
14
+ private
15
+ attr_reader :repository
16
+
17
+ def delete_stream(stream_name)
18
+ repository.delete({stream: stream_name})
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,22 @@
1
+ module RailsEventStore
2
+ module Actions
3
+ class ReadAllEvents
4
+
5
+ def initialize(repository)
6
+ @repository = repository
7
+ end
8
+
9
+ def call(stream_name)
10
+ raise IncorrectStreamData if stream_name.nil? || stream_name.empty?
11
+ get_all_events(stream_name)
12
+ end
13
+
14
+ private
15
+ attr_reader :repository
16
+
17
+ def get_all_events(stream_name)
18
+ repository.load_all_events_forward(stream_name)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module RailsEventStore
2
+ module Actions
3
+ class ReadAllStreams
4
+
5
+ def initialize(repository)
6
+ @repository = repository
7
+ end
8
+
9
+ def call
10
+ get_all_events.group_by { |event| event.stream }
11
+ end
12
+
13
+ private
14
+ attr_reader :repository
15
+
16
+ def get_all_events
17
+ repository.get_all_events
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ module RailsEventStore
2
+ module Actions
3
+ class ReadEventsBatch
4
+
5
+ def initialize(repository)
6
+ @repository = repository
7
+ end
8
+
9
+ def call(stream_name, start, count)
10
+ raise IncorrectStreamData if stream_name.nil? || stream_name.empty?
11
+ event = find_event(start)
12
+ get_events_batch(stream_name, event.id, count)
13
+ end
14
+
15
+ private
16
+ attr_reader :repository
17
+
18
+ def get_events_batch(stream_name, start, count)
19
+ repository.load_events_batch(stream_name, start, count)
20
+ end
21
+
22
+ def find_event(start)
23
+ event = repository.find({event_id: start})
24
+ raise EventNotFound if event.nil?
25
+ event
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ module RailsEventStore
2
+ class Client
3
+
4
+ def initialize(repository = Repositories::EventRepository.new)
5
+ @repository = repository
6
+ end
7
+
8
+ def publish_event(event_data, stream_name = 'all', expected_version = nil)
9
+ event = Actions::AppendEventToStream.new(@repository).call(stream_name, event_data, expected_version)
10
+ event_broker.notify_subscribers(event)
11
+ end
12
+
13
+ def delete_stream(stream_name)
14
+ Actions::DeleteStreamEvents.new(@repository).call(stream_name)
15
+ end
16
+
17
+ def read_events(stream_name, start, count)
18
+ Actions::ReadEventsBatch.new(@repository).call(stream_name, start, count)
19
+ end
20
+
21
+ def read_all_events(stream_name)
22
+ Actions::ReadAllEvents.new(@repository).call(stream_name)
23
+ end
24
+
25
+ def read_all_streams
26
+ Actions::ReadAllStreams.new(@repository).call
27
+ end
28
+
29
+ def subscribe(subscriber, event_types = [])
30
+ event_broker.add_subscriber(subscriber, event_types)
31
+ end
32
+
33
+ private
34
+
35
+ def event_broker
36
+ @event_broker ||= PubSub::Broker.new
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ module RailsEventStore
2
+ WrongExpectedEventVersion = Class.new(StandardError)
3
+ IncorrectStreamData = Class.new(StandardError)
4
+ EventCannotBeSaved = Class.new(StandardError)
5
+ EventNotFound = Class.new(StandardError)
6
+ SubscriberNotExist = Class.new(StandardError)
7
+ MethodNotDefined = Class.new(StandardError)
8
+ end
@@ -0,0 +1,44 @@
1
+ require 'securerandom'
2
+
3
+ module RailsEventStore
4
+ class Event
5
+
6
+ def initialize(event_data)
7
+ @event_type = event_data.fetch(:event_type, event_name)
8
+ @event_id = event_data.fetch(:event_id, generate_id).to_s
9
+ @metadata = event_data.fetch(:metadata, nil)
10
+ @data = event_data.fetch(:data, nil)
11
+ end
12
+
13
+ attr_reader :event_type, :event_id, :metadata, :data
14
+
15
+ def validate!
16
+ [event_type, event_id, data].each do |attribute|
17
+ raise IncorrectStreamData if is_invalid?(attribute)
18
+ end
19
+ end
20
+
21
+ def to_h
22
+ {
23
+ event_type: event_type,
24
+ event_id: event_id,
25
+ metadata: metadata,
26
+ data: data
27
+ }
28
+ end
29
+
30
+ private
31
+
32
+ def is_invalid?(attribute)
33
+ attribute.nil? || attribute.empty?
34
+ end
35
+
36
+ def generate_id
37
+ SecureRandom.uuid
38
+ end
39
+
40
+ def event_name
41
+ self.class.name
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_record'
2
+
3
+ module RailsEventStore
4
+ class EventEntity < ActiveRecord::Base
5
+ self.primary_key = :id
6
+ self.table_name = 'event_store_events'
7
+ serialize :metadata
8
+ serialize :data
9
+ validates_uniqueness_of :event_id
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ require 'rails/generators'
2
+
3
+ module RailsEventStore
4
+ class MigrateGenerator < Rails::Generators::Base
5
+ source_root File.expand_path(File.join(File.dirname(__FILE__), '../generators/templates'))
6
+
7
+ def create_migration
8
+ template "migration_template.rb", "db/migrate/#{timestamp}_create_events_table.rb"
9
+ end
10
+
11
+ private
12
+
13
+ def timestamp
14
+ Time.now.strftime("%Y%m%d%H%M%S")
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ class CreateEventsTable < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ create_table(:event_store_events) do |t|
5
+ t.string :stream, null: false
6
+ t.string :event_type, null: false
7
+ t.string :event_id, null: false
8
+ t.text :metadata
9
+ t.text :data, null: false
10
+ t.timestamps
11
+ end
12
+ add_index :event_store_events, :stream
13
+ add_index :event_store_events, :event_id
14
+ end
15
+
16
+ def self.down
17
+ drop_table :event_store_events
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ module RailsEventStore
2
+ module PubSub
3
+ class Broker
4
+
5
+ def initialize
6
+ @subscribers = {}
7
+ end
8
+
9
+ def add_subscriber(subscriber, event_types)
10
+ raise SubscriberNotExist if subscriber.nil?
11
+ raise MethodNotDefined unless subscriber.methods.include? :handle_event
12
+ subscribe(subscriber, [*event_types])
13
+ end
14
+
15
+ def notify_subscribers(event)
16
+ if subscribers.key? event.event_type
17
+ subscribers[event.event_type].each do |subscriber|
18
+ subscriber.handle_event(event)
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+ attr_reader :subscribers
25
+
26
+ def subscribe(subscriber, event_types)
27
+ event_types.each do |type|
28
+ (subscribers[type] ||= []) << subscriber
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ module RailsEventStore
2
+ module Repositories
3
+ class EventRepository
4
+
5
+ def initialize(adapter = EventEntity)
6
+ @adapter = adapter
7
+ end
8
+ attr_reader :adapter
9
+
10
+ def find(condition)
11
+ adapter.where(condition).first
12
+ end
13
+
14
+ def create(data)
15
+ model = adapter.new(data)
16
+ raise EventCannotBeSaved unless model.valid?
17
+ model.save
18
+ end
19
+
20
+ def delete(condition)
21
+ adapter.destroy_all condition
22
+ end
23
+
24
+ def get_all_events
25
+ adapter.find(:all, order: 'stream').map &method(:map_record)
26
+ end
27
+
28
+ def last_stream_event(stream_name)
29
+ adapter.where(stream: stream_name).last.map &method(:map_record)
30
+ end
31
+
32
+ def load_all_events_forward(stream_name)
33
+ adapter.where(stream: stream_name).order('id ASC').map &method(:map_record)
34
+ end
35
+
36
+ def load_events_batch(stream_name, start_point, count)
37
+ adapter.where('id >= ? AND stream = ?', start_point, stream_name).limit(count).map &method(:map_record)
38
+ end
39
+
40
+ private
41
+
42
+ def map_record(record)
43
+ event_data = {
44
+ stream: record.stream,
45
+ event_type: record.event_type,
46
+ event_id: record.event_id,
47
+ metadata: record.metadata,
48
+ data: record.data
49
+ }
50
+ OpenStruct.new(event_data)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ module RailsEventStore
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails_event_store/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rails_event_store'
8
+ spec.version = RailsEventStore::VERSION
9
+ spec.authors = ['rybex']
10
+ spec.email = ['tomek.rybka@gmail.com']
11
+
12
+ spec.summary = %q{Implementation of Event Store in Ruby}
13
+ spec.description = %q{Implementation of Event Store in Ruby}
14
+ spec.homepage = ''
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.9'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'pry'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'rails', '~> 4.2.1'
26
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_event_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - rybex
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.2.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.2.1
83
+ description: Implementation of Event Store in Ruby
84
+ email:
85
+ - tomek.rybka@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - Gemfile
93
+ - README.md
94
+ - Rakefile
95
+ - lib/rails_event_store.rb
96
+ - lib/rails_event_store/actions/append_event_to_stream.rb
97
+ - lib/rails_event_store/actions/delete_stream_events.rb
98
+ - lib/rails_event_store/actions/read_all_events.rb
99
+ - lib/rails_event_store/actions/read_all_streams.rb
100
+ - lib/rails_event_store/actions/read_events_batch.rb
101
+ - lib/rails_event_store/client.rb
102
+ - lib/rails_event_store/errors.rb
103
+ - lib/rails_event_store/event.rb
104
+ - lib/rails_event_store/event_entity.rb
105
+ - lib/rails_event_store/generators/migrate_generator.rb
106
+ - lib/rails_event_store/generators/templates/migration_template.rb
107
+ - lib/rails_event_store/pub_sub/broker.rb
108
+ - lib/rails_event_store/repositories/event_repository.rb
109
+ - lib/rails_event_store/version.rb
110
+ - rails_event_store.gemspec
111
+ homepage: ''
112
+ licenses: []
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.4.3
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Implementation of Event Store in Ruby
134
+ test_files: []