Ruby Event Store - use without Rails
… and check why 5600+ Rails engineers read also this
Ruby Event Store - use without Rails
Ruby Event Store v0.27 is here with some nice improvements. Let’s have a quick look.
Using RES without Rails
We’ve always built our ecosystem of gems with the intention of not being coupled to Rails. So the majority of features are implemented in ruby_event_store
gem and a few other features such as async handlers integrated with ActiveJob are in rails_event_store
. Every dependency is taken in a constructor and can be swapped to something different.
Until now we had one coupling which prevented you from using ruby_event_store
easily. The rails_event_store_active_record
gem (which provides an implementation for a repository to save events) depended on rails
because it provided a migration generator to create necessary tables for storing events.
rails_event_store_active_record
now integrates with rails
optionally and can work without it. So you can use ruby_event_store
together with rails_event_store_active_record
without rails
. Here is how:
Add ruby_event_store
too your Gemfile
:
source 'https://rubygems.org'
gem 'activerecord'
gem 'ruby_event_store'
gem 'rails_event_store_active_record'
# And one of:
gem 'sqlite3'
gem 'pg'
gem 'mysql2'
As you are not using rails and its generators, please create required database tables which are equivalent to what our migration would do in whatever way you manage DB schema in your project.
You can now use RES in your app.
require 'active_record'
require 'rails_event_store_active_record'
require 'ruby_event_store'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
class OrderPlaced < RubyEventStore::Event
end
event_store = RubyEventStore::Client.new(
repository: RailsEventStoreActiveRecord::EventRepository.new
)
event_store.publish_event(OrderPlaced.new(data: {
order_id: 1,
customer_id: 47271,
amount: BigDecimal.new("20.00"),
}),
stream_name: "Order-1",
)
Protobuf & Custom event mappers
Since the beginning events had to inherit from RubyEventStore::Event
. That is no longer the case, however. Now, any object can be used as events, provided you tell us how to map it to columns that we store in the database. To do that you can implement a custom mapper.
This new ruby_event_store
version comes with RubyEventStore::Mappers::Protobuf
mapper which you can use to store messages generated with google-protobuf
gem.
Add RES and protobuf to your app’s Gemfile:
gem 'google-protobuf'
gem 'rails_event_store' # or `ruby_event_store`
Configure protobuf mapper:
Rails.application.configure do
config.to_prepare do
Rails.configuration.event_store = RailsEventStore::Client.new(
repository: RailsEventStoreActiveRecord::EventRepository.new(
mapper: RubyEventStore::Mappers::Protobuf.new
)
)
end
end
Define your events in protobuf file format i.e.: events.proto3
syntax = "proto3";
package my_app;
message OrderPlaced {
string event_id = 1;
string order_id = 2;
int32 customer_id = 3;
}
and generate the Ruby classes:
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: events.proto3
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "my_app.OrderPlaced" do
optional :event_id, :string, 1
optional :order_id, :string, 2
optional :customer_id, :int32, 3
end
end
module MyApp
OrderPlaced = Google::Protobuf::DescriptorPool.
generated_pool.
lookup("my_app.OrderPlaced").
msgclass
end
You can now use those structures when publishing events with Rails/Ruby Event Store
event_store = Rails.configuration.event_store
event = MyApp::OrderPlaced.new(
event_id: "f90b8848-e478-47fe-9b4a-9f2a1d53622b",
customer_id: 123,
order_id: "K3THNX9",
)
event_store.publish_event(event, stream_name: "Order-K3THNX9")
The work is not yet finished. We are still working on enabling this feature for async handlers but we think this a good start.
We believe this will make much easier to use RES and exchange events between multiple applications and/or micro-services.
You can now serialize your events however you want: protbuf, messagepack, Apache Avro, BSON, Thrift, Cap’n Proto. It’s up to you. You just need to implement a custom mapper with 3 methods.
Here is an example of how you could serialize your events with messagepack.
Implement the mapper:
require 'msgpack'
class MyHashToMessagePackMapper
def event_to_serialized_record(domain_event)
# Use data (and metadata if applicable) fields
# to store serialized representation
# of your domain event
SerializedRecord.new(
event_id: domain_event.fetch('event_id'),
metadata: "",
data: domain_event.to_msg_pack,
event_type: domain_event.fetch('event_type')
)
end
# Deserialize proper object based on
# event_type and data+metadata fields
def serialized_record_to_event(record)
MessagePack.unpack(record.data)
end
def add_metadata(event, key, value)
event[key.to_s] = value
end
end
Pass it as a dependency:
Rails.application.configure do
config.to_prepare do
Rails.configuration.event_store = RailsEventStore::Client.new(
repository: RailsEventStoreActiveRecord::EventRepository.new(
mapper: MyHashToMessagePackMapper.new
)
)
end
end
and now you can publish an event:
Rails.configuration.event_store.publish_event({
'event_id' => SecureRandom.uuid,
'order_id' => 'K3THNX9',
'event_type' => 'OrderPlaced',
'order_amount' => BigDecimal.new('120.55'),
}, stream_name: 'Order$K3THNX9')
Documentation
- http://railseventstore.org/docs/protobuf/
- http://railseventstore.org/docs/mapping_serialization/
- http://railseventstore.org/docs/without_rails/
- https://github.com/RailsEventStore/rails_event_store/releases/tag/v0.27.0
Read more
If you enjoyed that story, subscribe to our newsletter. We share our everyday struggles and solutions for building maintainable Rails apps which don’t surprise you.
Also worth reading:
- Rails Event Store - better APIs coming - How we delivered nice APIs in Ruby/Rails EventStore in version 0.26 recently.
- When DDD clicked for me - It took me quite a time to grasp the concepts from DDD community and apply them in our Rails projects. This is a story of one of such “aha” moments.
- Application Services - 10 common doubts answered - You might have heard about the Domain-Driven Design approach to building applications. In this approach, there is this horizontal layer called Application Service. But what does it do?