Skip to content

Latest commit

 

History

History
155 lines (119 loc) · 5.11 KB

mapping_serialization.html.md

File metadata and controls

155 lines (119 loc) · 5.11 KB
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).

Configuring a different serializer

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

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.

Available mappers

  • RubyEventStore::Mappers::Default
    • works with RubyEventStore::Event or RailsEventStore::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.
  • RubyEventStore::Mappers::Protobuf
    • works with Ruby classes generated by google-protobuf gem

Custom mapper

Mapper needs to implement 3 methods:

  • event_to_serialized_record(domain_event) - which takes an event and returns RubyEventStore::SerializedRecord with given attributes filled out:
    • event_id (String)
    • data (String)
    • metadata (String)
    • event_type (String)
  • serialized_record_to_event(record) - which takes RubyEventStore::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')