Skip to content

Events upgrade

Andrey Leskov edited this page Oct 27, 2016 · 4 revisions

Motivation

Aggregate are changing with time, and issued events can have field renames, additions, or even been renamed. As domain event will never change in persistence, it is critical to be able to make it new versions.

Domain events design recommendations

Always prefer to keep your events as much simple as possible, using properties only with primitive types or collections. There is a rule of thumb - domain event must contain only information, that is used in its Apply method in aggregate. All other information should not be stored in event. Following this conventions will make you events and update process simple and convenient.

If you want to update an event, please do following:

Copy event class to new event type with suffix _V.

For example BalanceChangedEvent will be copied to BalanceChangedEvent_V2. All code must be identical, and origin event will be updated, and copy will stay forever for deserialization and update. Full type name with assembly of event must not be changed.

Change all usages of old event to new one.

Check aggregates, sagas, projection builders. Old version of event should be known only by event adapter.

Create event adapter as a implementation of DomainEventAdapter<TFrom, TTo>

Inside this class you will create new version of event based on information from old one. We can use any kind of dependency injection here, if need. But in most times it will be trivial update.

class BalanceChangedDomainEventAdapter1 : DomainEventAdapter<BalanceChangedEvent_V0, BalanceChangedEvent_V1>
{
    public override IEnumerable<BalanceChangedEvent_V1> ConvertEvent(BalanceChangedEvent_V0 evt)
    {
        yield return new BalanceChangedEvent_V1(evt.AmplifiedAmountChange, evt.SourceId);
    }
}

Register adapter in GridDomainNode.EventAdaptersCatalog

Event catalog contains all adapters available in Node, and You can have only one adapter per TFrom type. Register several adapter as chain, V1->V2, V2->V3 and so on to do several updates for old events.

...
   GridNode.EventAdaptersCatalog.Register(new BalanceChangedDomainEventAdapter1());
...

Remember to copy all required fields, including saga id. You can find examples here

###Changing classes shared between different events

In some cases we need to update a class that is shared between events, for example:

class EventA : DomainEvent
{
    public IOrder Order { get; }
} 

and class EventB : DomainEvent { public IOrder Order { get; } }

If we change IOrder, by default we have to create new versions for all participants : IOrder_V2, EventA_V2, EventB_V2 It cases a lot of boilerplate code. More important that we can forgot IOrder is used in some other events and don't change them. For this case GridDomain has advanced events upgrade possibility. You can create a special "partial" adapter inherited from ObjectAdapter class:

class OrderAdapter : ObjectAdapter<IOrder, IOrder_V2>
{
    public override IOrder_V2 Convert(IOrder value)
    {
        return new Order_V2(value);
    }
}

And register it in DomainEventAdapters static class :

 DomainEventAdapters.Register(new OrderAdapter())

##Under the hood

EventAdaptersCatalog will be attached to aggregate load pipeline and it will process all incoming messages. If no adapter for will be found for income event, it is returned as is. Overwise adapter will be called and lookup will be started again by result event type. Order of registration does not matter, as lookup is performed only by type of event. This adapters are called after deserialization of object

Using of ObjectAdapter are different - it is called as part of object deserialization process, and it is serializer - specific. GridDomain uses Json to store events, and ObjectAdapter is inherited from JsonConverter. We can create you custom implementations of JsonConverter and register it in DomainEventAdapters. So it is possible to do almost anything with your objects, but only on deserialization.

Setting for json serializer can be found in DomainEventSerialization.GetDefaultSettings() In future it will be configurable from client side.