title |
---|
Event serialization formats |
By default RailsEventStore will use YAML
as a
serialization format. The reason is that YAML
is available out of box
and can serialize and deserialize data types which are not easily
handled in other formats. As an example, JSON
cannot out of box handle
deserializing dates. You get back String
instead of a Date
.
However, if you don't like YAML
or you have different needs you can
choose to use different serializers (even whole mappers).
You can pass a different serializer
as a dependency when instantiating
the client.
Here is an example on how to configure RailsEventStore to serialize
events' data
and metadata
using JSON
.
# config/environments/*.rb
Rails.application.configure do
config.to_prepare do
Rails.configuration.event_store = RailsEventStore::Client.new(
mapper: RubyEventStore::Mappers::Default.new(
serializer: JSON
)
)
end
end
The provided serializer
must respond to load
and dump
.
Bear in mind that serializers have their limitations. For example JSON
would convert symbols to strings and you have to prepare for that when retrieving events.
JSON.load(JSON.dump({foo: :bar}))
=> {"foo"=>"bar"}
One way to approach this is to have your own event adapter, specific for the project you're working on.
class MyEvent < RailsEventStore::Event
def data
ActiveSupport::HashWithIndifferentAccess.new(super)
end
end
OrderPlaced = Class.new(MyEvent)
That shields you from data keys being transformed from symbols into strings. It doesn't do anything with data values though so beware.
event_store.publish(OrderPlaced.new(event_id: 'e34fc19a-a92f-4c21-8932-a10f6fb2602b', data: { foo: :bar }))
event = event_store.read.event('e34fc19a-a92f-4c21-8932-a10f6fb2602b')
event.data[:foo]
# => "bar"
event.data['foo']
# => "bar"
Configuring a different mapper makes it possible to define events however you want and store in them in the database.
You no longer need to use RubyEventStore::Event
(or RailsEventStore::Event
) for events.
Any object can be used as events, provided you tell us how to map it to columns that we store in the DB.
RubyEventStore::Mappers::Default
- works with
RubyEventStore::Event
orRailsEventStore::Event
- constructor takes named arguments:
serializer:
(described above)events_class_remapping:
- which can be used for mapping old event names to new ones after you refactored your codebase.
- works with
RubyEventStore::Mappers::Protobuf
- works with Ruby classes generated by
google-protobuf
gem
- works with Ruby classes generated by
Mapper needs to implement 3 methods:
event_to_serialized_record(domain_event)
- which takes an event and returnsRubyEventStore::SerializedRecord
with given attributes filled out:event_id
(String)data
(String)metadata
(String)event_type
(String)
serialized_record_to_event(record)
- which takesRubyEventStore::SerializedRecord
and converts it to an instance of an event class that was stored.
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: domain_event.metadata.to_msg_pack,
data: domain_event.data.to_msg_pack,
event_type: domain_event.type
)
end
# Deserialize proper object based on
# event_type and data+metadata fields
def serialized_record_to_event(record)
Object.const_get(event_type).new(
event_id: record.event_id,
metadata: MessagePack.unpack(record.metadata),
data: MessagePack.unpack(record.data)
)
end
end
Check out the code of our default mapper and protobuf mapper on github for examples on how to implement mappers.
You can pass a different mapper
as a dependency when instantiating the client.
# config/environments/*.rb
Rails.application.configure do
config.to_prepare do
Rails.configuration.event_store = RailsEventStore::Client.new(
mapper: MyHashToMessagePackMapper.new
)
end
end
Now you should be able to publish your events:
class OrderPlaced < RubyEventStore::Event
end
event_store = Rails.configuration.event_store
event_store.publish(OrderPlaced.new(data: {
'event_id' => SecureRandom.uuid,
'order_id' => 1,
'order_amount' => BigDecimal.new('120.55'),
}), stream_name: 'Order$1')