frp-eventsourcing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +25 -0
- data/README.md +187 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/frp-eventsourcing.gemspec +43 -0
- data/lib/frp-eventsourcing.rb +12 -0
- data/lib/frp-eventsourcing/event.rb +37 -0
- data/lib/frp-eventsourcing/event_repository.rb +95 -0
- data/lib/frp-eventsourcing/generators/migration_generator.rb +19 -0
- data/lib/frp-eventsourcing/generators/templates/migration_template.rb +16 -0
- data/lib/frp-eventsourcing/stream.rb +114 -0
- data/lib/frp-eventsourcing/stream/each.rb +29 -0
- data/lib/frp-eventsourcing/stream/filter.rb +14 -0
- data/lib/frp-eventsourcing/stream/init.rb +24 -0
- data/lib/frp-eventsourcing/stream/map.rb +15 -0
- data/lib/frp-eventsourcing/stream/when.rb +31 -0
- data/lib/frp-eventsourcing/version.rb +3 -0
- data/sources/All_About_Monads.pdf +0 -0
- data/sources/GregoryMeredith.pdf +54530 -72
- data/sources/RetroactiveComputing_Mueller2016.pdf +0 -0
- data/sources/a_survey_on_reactive_programming.pdf +0 -0
- data/sources/aw.implementing.domain-driven.design.0321834577/AW.Implementing.Domain-Driven.Design.0321834577.epub +0 -0
- data/sources/aw.implementing.domain-driven.design.0321834577/AW.Implementing.Domain-Driven.Design.0321834577.mobi +0 -0
- data/sources/conal_elliott_push_pull_frp.pdf +0 -0
- data/sources/cqrs_documents.pdf +0 -0
- data/sources/event_driven_frp.pdf +0 -0
- data/sources/flapjax.pdf +0 -0
- data/sources/functional_reactive_animation.pdf +0 -0
- data/sources/reactive_programming_with_events.pdf +0 -0
- data/sources/reactiveruby.pdf +0 -0
- data/sources/tool-support-for-reactive-programming.pdf +0 -0
- data/sources/xx735.Eric.Evans.Domaindriven.Design.Tackling.Complexity.in.the.Heart.of.Software.pdf +0 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b89e010d2caf9f64fc4a66718e79111e0e0d3a30
|
4
|
+
data.tar.gz: cf6ce8fbc999d409ae9a454cf06c9e843f24fc8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d097c4b0c2813a039ed35815c3ee3d837eb15b7722120617393131e7b5c0ea035473d0e25fdab5034d013aa8ff644f9f6f91b440519b96463b907ff8230ad66
|
7
|
+
data.tar.gz: 7e78cc3362d3b8d8b2003cf8553bf362455ebf85b3b3a01c7b40707fdddded9011b59f54ca3092afd6e05333b1b79960c5ba1ab007623d4a7e226e8282eebaa2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
Copyright © 2017 Žilvinas Kučinskas
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
7
|
+
obtaining a copy of this software and associated documentation
|
8
|
+
files (the “Software”), to deal in the Software without
|
9
|
+
restriction, including without limitation the rights to use,
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the
|
12
|
+
Software is furnished to do so, subject to the following
|
13
|
+
conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/ZilvinasKucinskas/FRP-EventSourcing.svg?branch=master)](https://travis-ci.org/ZilvinasKucinskas/FRP-EventSourcing)
|
2
|
+
|
3
|
+
Will be released soon (2017 June) after testing out in sample application.
|
4
|
+
|
5
|
+
# Frp-Eventsourcing
|
6
|
+
|
7
|
+
EventSourcing describes current state as series of events that occurred in a system. Events hold all information that is needed to recreate current state. This method allows to achieve high volume of transactions, and enables efficient replication. Whereas reactive programming lets implement reactive systems in declarative style, decomposing logic into smaller, easier to understand components. The goal is to create reactive programming program interface, incorporating both principles. Applying reactive programming in event-sourcing systems enables modelling not only instantaneous events, but also have their history. Furthermore, it enables focus on the solvable problem, regardless of low level realization details. Reactive operators enable read model creation without exposing realization details of operations with data storage.
|
8
|
+
|
9
|
+
## Sources to learn more about
|
10
|
+
|
11
|
+
### (Functional) Reactive programming
|
12
|
+
|
13
|
+
* [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)
|
14
|
+
(by [@andrestaltz](https://twitter.com/andrestaltz))
|
15
|
+
* [Lambda Jam 2015 - Conal Elliott - The Essence and Origins of Functional Reactive Programming](https://youtu.be/j3Q32brCUAI)
|
16
|
+
* More [here](https://github.com/ZilvinasKucinskas/FRP-EventSourcing/tree/master/sources)
|
17
|
+
|
18
|
+
### EventSourcing
|
19
|
+
|
20
|
+
* [Martin Fowler - EventSourcing](http://martinfowler.com/eaaDev/EventSourcing.html)
|
21
|
+
* [Greg Young - CQRS documents](https://github.com/ZilvinasKucinskas/FRP-EventSourcing/blob/master/sources/cqrs_documents.pdf)
|
22
|
+
* GetEventStore - The open-source, functional database with Complex Event Processing in JavaScript. [Event Sourcing Basics](http://docs.geteventstore.com/introduction/3.9.0/event-sourcing-basics/)
|
23
|
+
* [Martin Fowler - Retroactive Event](https://martinfowler.com/eaaDev/RetroactiveEvent.html)
|
24
|
+
* [Akka Persistence module uses EventSourcing](http://doc.akka.io/docs/akka/snapshot/scala/persistence.html#event-sourcing)
|
25
|
+
* More [here](https://github.com/ZilvinasKucinskas/FRP-EventSourcing/tree/master/sources)
|
26
|
+
|
27
|
+
## Installation
|
28
|
+
|
29
|
+
Add this line to your application's Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem 'frp-eventsourcing'
|
33
|
+
```
|
34
|
+
|
35
|
+
And then execute:
|
36
|
+
|
37
|
+
$ bundle
|
38
|
+
|
39
|
+
Or install it yourself as:
|
40
|
+
|
41
|
+
$ gem install frp-eventsourcing
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
### Generate EventStore event model
|
46
|
+
|
47
|
+
Use provided task to generate a table to store events in your database.
|
48
|
+
|
49
|
+
```
|
50
|
+
rails generate frp_eventsourcing:migration
|
51
|
+
rake db:migrate
|
52
|
+
```
|
53
|
+
|
54
|
+
### Event definitions
|
55
|
+
|
56
|
+
```
|
57
|
+
# Define events
|
58
|
+
AccountCreated = Class.new(FrpEventsourcing::Event)
|
59
|
+
MoneyDeposited = Class.new(FrpEventsourcing::Event)
|
60
|
+
MoneyWithdrawn = Class.new(FrpEventsourcing::Event)
|
61
|
+
```
|
62
|
+
|
63
|
+
Alternative definition:
|
64
|
+
|
65
|
+
```
|
66
|
+
class AccountCreated < FrpEventsourcing::Event; end
|
67
|
+
class MoneyDeposited < FrpEventsourcing::Event; end
|
68
|
+
class MoneyWithdrawn < FrpEventsourcing::Event; end
|
69
|
+
```
|
70
|
+
|
71
|
+
### Create stream
|
72
|
+
|
73
|
+
We can define stream that is creating read model once in our app. Keep in mind that no database operations are present here.
|
74
|
+
|
75
|
+
```
|
76
|
+
account_stream = FrpEventsourcing::Stream.new(AccountCreated, MoneyDeposited, MoneyWithdrawn).
|
77
|
+
as_persistent_type(Account, %i(account_id)).
|
78
|
+
init(-> (state) { state.balance = 0 }).
|
79
|
+
when(MoneyDeposited, -> (state, event) { state.balance += event[:data][:amount] }).
|
80
|
+
when(MoneyWithdrawn, -> (state, event) { state.balance -= event[:data][:amount] })
|
81
|
+
```
|
82
|
+
|
83
|
+
Instead of passing `lambda` directly, we can also use a variable to save and reuse `lambda`:
|
84
|
+
|
85
|
+
```
|
86
|
+
account_initial_state_change_function = -> (state) { state.balance = 0 }
|
87
|
+
```
|
88
|
+
|
89
|
+
or even use a class that implements `call` method. We can structure our code with some kind of denormalizer for example:
|
90
|
+
|
91
|
+
```
|
92
|
+
class Denormalizers::ReadModelType::InitialState::Account
|
93
|
+
def call(state)
|
94
|
+
state.balance = 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
### Publish events
|
100
|
+
|
101
|
+
We can create an account:
|
102
|
+
|
103
|
+
```
|
104
|
+
stream_name = "account"
|
105
|
+
event = AccountCreated.new(data: {
|
106
|
+
account_id: 'LT121000011101001000'
|
107
|
+
})
|
108
|
+
FrpEventsourcing::EventRepository.new.create(event, stream_name)
|
109
|
+
```
|
110
|
+
|
111
|
+
Transfer some money (100$ for example) to the account:
|
112
|
+
|
113
|
+
```
|
114
|
+
stream_name = "account"
|
115
|
+
event = MoneyDeposited.new(data: {
|
116
|
+
account_id: 'LT121000011101001000',
|
117
|
+
amount: 100
|
118
|
+
})
|
119
|
+
FrpEventsourcing::EventRepository.new.create(event, stream_name)
|
120
|
+
```
|
121
|
+
|
122
|
+
Withdraw some money (25$ for example) from the account:
|
123
|
+
|
124
|
+
```
|
125
|
+
stream_name = "account"
|
126
|
+
event = MoneyWithdrawn.new(data: {
|
127
|
+
account_id: 'LT121000011101001000',
|
128
|
+
amount: 25
|
129
|
+
})
|
130
|
+
FrpEventsourcing::EventRepository.new.create(event, stream_name)
|
131
|
+
```
|
132
|
+
|
133
|
+
Now we can query read model like:
|
134
|
+
|
135
|
+
```
|
136
|
+
account = Account.find_by(account_id: 'LT121000011101001000')
|
137
|
+
puts account.balance # prints 75
|
138
|
+
```
|
139
|
+
|
140
|
+
## Available reactive operators
|
141
|
+
|
142
|
+
* `merge(another_stream)` - merge one stream to another.
|
143
|
+
* `filter(predicate_function)` - if predicate function returns false, event won't get propogated through the chain any more.
|
144
|
+
* `map(transform_function)` - applies transformation function and propogates event through the chain.
|
145
|
+
* `init(initial_state_change_function)` - applies initial state change function for the first event.
|
146
|
+
* `when(event_type, state_change_function)` - if event matches event type, record is being created or loaded, state change function is being applied for the record and transition state saved to database.
|
147
|
+
* `each(state_change_function)` - same as `when` operator, just does not check event type and applies state change function for each event.
|
148
|
+
|
149
|
+
## Implementation
|
150
|
+
|
151
|
+
Reactive operators were implemented using Observer design pattern, object oriented programming principles and introspection.
|
152
|
+
|
153
|
+
Transition state is being solved by applying metaprogramming (introspection, reflection) and using method chaining.
|
154
|
+
|
155
|
+
## Development
|
156
|
+
|
157
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
158
|
+
|
159
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
160
|
+
|
161
|
+
## Possible improvements
|
162
|
+
|
163
|
+
Easy-medium difficulty:
|
164
|
+
|
165
|
+
* Make demo application with UI. Controller should publish events. Event types and streams should be defined in some kind of initializer file for example.
|
166
|
+
* Improve error handling - could be bugs, because it's just prototype version.
|
167
|
+
* Refactor event publishing mechanics. We can borrow optimistic locking from [RailsEventStore](https://github.com/arkency/rails_event_store)
|
168
|
+
* More tests
|
169
|
+
|
170
|
+
Challenging:
|
171
|
+
|
172
|
+
* Make mechanics for recreating read models after changes. We should reapply all associated events.
|
173
|
+
* Use custom events repository + instructions how to change adapter for `FrpEventsourcing::EventRepository`
|
174
|
+
|
175
|
+
## Contributing
|
176
|
+
|
177
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ZilvinasKucinskas/FRP-EventSourcing.
|
178
|
+
|
179
|
+
1. Fork it
|
180
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
181
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
182
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
183
|
+
5. Create new Pull Request
|
184
|
+
|
185
|
+
## License
|
186
|
+
|
187
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "frp-eventsourcing"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'frp-eventsourcing/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'frp-eventsourcing'
|
8
|
+
spec.version = FrpEventsourcing::VERSION
|
9
|
+
spec.authors = ['Zilvinas']
|
10
|
+
spec.email = ['zil.kucinskas@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'A library to do Functional Reactive Programming with EventSourcing in Ruby'
|
13
|
+
spec.description = 'Functional Reactive Programming with EventSourcing in Ruby'
|
14
|
+
spec.homepage = 'https://github.com/ZilvinasKucinskas/FRP-EventSourcing'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
21
|
+
else
|
22
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
|
+
'public gem pushes.'
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = 'exe'
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ['lib']
|
32
|
+
|
33
|
+
spec.add_runtime_dependency 'activerecord', '>= 4.0.0'
|
34
|
+
|
35
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
36
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
37
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
38
|
+
spec.add_development_dependency 'rails', '~> 4.2'
|
39
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
40
|
+
spec.add_development_dependency 'activerecord', '~> 4.0'
|
41
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3'
|
42
|
+
spec.add_development_dependency 'database_cleaner', '~> 1.6'
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'frp-eventsourcing/version'
|
2
|
+
require 'frp-eventsourcing/generators/migration_generator'
|
3
|
+
require 'frp-eventsourcing/event'
|
4
|
+
require 'frp-eventsourcing/event_repository'
|
5
|
+
require 'frp-eventsourcing/stream'
|
6
|
+
|
7
|
+
class EventStoreEvent < ::ActiveRecord::Base
|
8
|
+
self.primary_key = :id
|
9
|
+
|
10
|
+
serialize :metadata
|
11
|
+
serialize :data
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
module FrpEventsourcing
|
5
|
+
class Event
|
6
|
+
extend Observable
|
7
|
+
|
8
|
+
def initialize(event_id: SecureRandom.uuid, metadata: nil, data: nil)
|
9
|
+
@event_id = event_id.to_s
|
10
|
+
@metadata = metadata.to_h
|
11
|
+
@data = data.to_h
|
12
|
+
end
|
13
|
+
attr_reader :event_id, :metadata, :data
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{
|
17
|
+
event_id: event_id,
|
18
|
+
event_type: self.class,
|
19
|
+
metadata: metadata,
|
20
|
+
data: data
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other_event)
|
25
|
+
other_event.instance_of?(self.class) &&
|
26
|
+
other_event.event_id.eql?(event_id) &&
|
27
|
+
other_event.data.eql?(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :eql?, :==
|
31
|
+
|
32
|
+
def emit
|
33
|
+
self.class.changed
|
34
|
+
self.class.notify_observers(self.to_h)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module FrpEventsourcing
|
2
|
+
class EventRepository
|
3
|
+
def initialize(adapter: ::EventStoreEvent)
|
4
|
+
@adapter = adapter
|
5
|
+
end
|
6
|
+
attr_reader :adapter
|
7
|
+
|
8
|
+
def create(event, stream_name)
|
9
|
+
data = event.to_h.merge!(stream: stream_name)
|
10
|
+
adapter.create(data)
|
11
|
+
|
12
|
+
event.emit if event.respond_to?(:emit)
|
13
|
+
|
14
|
+
event
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete_stream(stream_name)
|
18
|
+
condition = {stream: stream_name}
|
19
|
+
adapter.destroy_all condition
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_event?(event_id)
|
23
|
+
adapter.exists?(event_id: event_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def last_stream_event(stream_name)
|
27
|
+
build_event_entity(adapter.where(stream: stream_name).last)
|
28
|
+
end
|
29
|
+
|
30
|
+
def read_events_forward(stream_name, start_event_id, count)
|
31
|
+
stream = adapter.where(stream: stream_name)
|
32
|
+
unless start_event_id.equal?(:head)
|
33
|
+
starting_event = adapter.find_by(event_id: start_event_id)
|
34
|
+
stream = stream.where('id > ?', starting_event)
|
35
|
+
end
|
36
|
+
|
37
|
+
stream.limit(count)
|
38
|
+
.map(&method(:build_event_entity))
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_events_backward(stream_name, start_event_id, count)
|
42
|
+
stream = adapter.where(stream: stream_name)
|
43
|
+
unless start_event_id.equal?(:head)
|
44
|
+
starting_event = adapter.find_by(event_id: start_event_id)
|
45
|
+
stream = stream.where('id < ?', starting_event)
|
46
|
+
end
|
47
|
+
|
48
|
+
stream.order('id DESC').limit(count)
|
49
|
+
.map(&method(:build_event_entity))
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_stream_events_forward(stream_name)
|
53
|
+
adapter.where(stream: stream_name)
|
54
|
+
.map(&method(:build_event_entity))
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_stream_events_backward(stream_name)
|
58
|
+
adapter.where(stream: stream_name).order('id DESC')
|
59
|
+
.map(&method(:build_event_entity))
|
60
|
+
end
|
61
|
+
|
62
|
+
def read_all_streams_forward(start_event_id, count)
|
63
|
+
stream = adapter
|
64
|
+
unless start_event_id.equal?(:head)
|
65
|
+
starting_event = adapter.find_by(event_id: start_event_id)
|
66
|
+
stream = stream.where('id > ?', starting_event)
|
67
|
+
end
|
68
|
+
|
69
|
+
stream.limit(count)
|
70
|
+
.map(&method(:build_event_entity))
|
71
|
+
end
|
72
|
+
|
73
|
+
def read_all_streams_backward(start_event_id, count)
|
74
|
+
stream = adapter
|
75
|
+
unless start_event_id.equal?(:head)
|
76
|
+
starting_event = adapter.find_by(event_id: start_event_id)
|
77
|
+
stream = stream.where('id < ?', starting_event)
|
78
|
+
end
|
79
|
+
|
80
|
+
stream.order('id DESC').limit(count)
|
81
|
+
.map(&method(:build_event_entity))
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def build_event_entity(record)
|
87
|
+
return nil unless record
|
88
|
+
record.event_type.constantize.new(
|
89
|
+
event_id: record.event_id,
|
90
|
+
metadata: record.metadata,
|
91
|
+
data: record.data
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|