Mappers
Mapper is defined as a pipeline of transformations that transforms domain event object into record and back from record to domain event. Mappers are useful when you have to encrypt your events data, or you’d like to access events using both strings nad symbols.
Available mappers
There is set of available mappers that you can use out of the box. They work with certain types of events, described in the table below.
Compatible with \ Mapper | Default |
Protobuf |
Null |
Encryption |
---|---|---|---|---|
RubyEventStore::Event |
✅ | ❌ | ✅ | ✅ |
RailsEventStore::Event |
✅ | ❌ | ✅ | ✅ |
RubyEventStore::Proto |
❌ | ✅ | ❌ | ❌ |
RubyEventStore::Mappers::Default
- Default mapper for
RubyEventStore::Client
andRailsEventStore::Client
- Transforms an event into a record (and back). Additionally it symbolizes metadata keys
- Provide
events_class_remapping
optional constructor parameter, which is a hash, to map old event names to new ones after you refactored your codebase
RubyEventStore::Mappers::Protobuf
- Works with Ruby classes generated by
google-protobuf
gem
RubyEventStore::Mappers::NullMapper
- Performs no transformations. It's useful in tests
RubyEventStore::Mappers::EncryptionMapper
- Encrypts event's data
- Useful for GDPR
- You can read more about
EncryptionMapper
in the GDPR section - constructor takes following arguments:
key_repository
- which is responsible for providing encryption keys used to encrypt/descrypt payload by encryption transformationforgotten_data
(named argument) - describes how the data will be presented when the encryption key is forgotten. By default it isFORGOTTEN_DATA
. You can change the default by passing desired value into constructor. For exampleForgottenData.new("Key is forgotten")
.serializer
(named argument) - specifies the serialization/deserialization format of encrypted data. The default isYAML
format.
Custom mapper
Mapper is defined as a pipeline of transformations that transforms domain event object into record and back from record
to domain event. I.e. Default
is implemented as a set of 2 transformations. Transformation works
on Record
objects. Such Record
is given to transformation methods (it is expected that load
& dump
methods are implemented)
and it is result of any transformation performed. Except transformations, each PipelineMapper
needs to be given 1 edge
transformation:
- to transform domain event to/from record (
default
RubyEventStore::Mappers::Transformation::DomainEvent
)
Extended implementation of Default with explicit default arguments:
module RubyEventStore
module Mappers
class Default < PipelineMapper
def initialize(events_class_remapping: {})
super(Pipeline.new(
Transformation::EventClassRemapper.new(events_class_remapping),
Transformation::SymbolizeMetadataKeys.new,
to_domain_event: Transformation::DomainEvent.new
))
end
end
end
end
When you define new custom mapper you could use mapper PipelineMapper as base class and provide your transformations pipeline.
Each transformation must implement 2 methods: dump(record)
& load(record)
. Both methods take
a RubyEventStore::Record
and return new instance of it, transformed. Transformation shall not modify given record and
shall return new instance as a result.
If you use different class to represent domain event you shall also define edge transformation for your pipeline definition.
Example of custom mapper build based on mappers pipeline:
require 'msgpack'
class MessagePackSerialization
def dump(record)
RubyEventStore::Record.new(
event_id: record.event_id,
metadata: record.metadata.to_msg_pack,
data: record.data.to_msg_pack,
event_type: record.event_type,
timestamp: record.timestamp,
valid_at: record.valid_at
)
end
def load(record)
RubyEventStore::Record.new(
event_id: record.event_id,
metadata: MessagePack.unpack(record.metadata),
data: MessagePack.unpack(record.data),
event_type: record.event_type,
timestamp: record.timestamp,
valid_at: record.valid_at
)
end
end
class MyHashToMessagePackMapper < RubyEventStore::Mappers::PipelineMapper
def initialize
super(RubyEventStore::Mappers::Pipeline.new(
MessagePackSerialization.new
))
end
end
You could build your own transformations or use existing ones combined with the ones build by you. We strongly encourage you to share your transformations as a part of RES-contrib.
Check out the code of our mappers and transformations on github for examples on how to implement mappers & transformations.
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')