Events Versioning

Events by their nature represent facts that happened in the past. They should be immutable even if they had wrong values (as we can only roughly guess what should be the missing property value).

However, in practice, it's unavoidable in the living system to not have the event schema migrations. They might come from:

  • bug - eg. typo in the property name, missing event data,
  • new business requirements - eg. besides storing the user email we'd like to be also storing its full name,
  • refactorings - eg. renaming event class, moving to different namespace or assembly,
  • etc.

Depending on the concrete business case we may use a different technique for handling such event migrations.

Namespace Migration

Marten by default tries to find the event class based on the fully qualified assembly name (it's stored in mt_dotnet_type column of mt_events table, read more in events schema documentation. When it is not able to find event type with the same assembly, namespace and type name then it tries to make a lookup for mapping on the event type name (stored in type column of mt_events table).

Such mapping needs to be defined manually:

  • either by registering events with store options Events.AddEventTypes method,
  • or by defining custom mapping with Events.MapEventType method.

For the case of namespace migration, it's enough to use AddEventTypes method as it's generating mapping based on the event type. As an example, change OrderStatusChanged event from:

namespace OldEventNamespace
{
    public class OrderStatusChanged
    {
        public Guid OrderId { get; }
        public int Status { get; }

        public OrderStatusChanged(Guid orderId, int status)
        {
            OrderId = orderId;
            Status = status;
        }
    }
}

snippet source | anchor

to:

namespace NewEventNamespace
{
    public class OrderStatusChanged
    {
        public Guid OrderId { get; }
        public int Status { get; }

        public OrderStatusChanged(Guid orderId, int status)
        {
            OrderId = orderId;
            Status = status;
        }
    }
}

snippet source | anchor

It's enough to register new event type as follows:

var options = new StoreOptions();

options.Events.AddEventTypes(new[] {typeof(NewEventNamespace.OrderStatusChanged)});

var store = new DocumentStore(options);

snippet source | anchor

After that Marten will automatically perform a matching based on the type name (that didn't change) - order_status_changed.

Event Type Name Migration

When the event class type name has changed, Marten does not perform automatic mapping but allows to define a custom one.

To do that you need to use Events.MapEventType method to define the type name for the new event.

Eg. for migrating OrderStatusChanged event into ConfirmedOrderStatusChanged

namespace NewEventNamespace
{
    public class ConfirmedOrderStatusChanged
    {
        public Guid OrderId { get; }
        public int Status { get; }

        public ConfirmedOrderStatusChanged(Guid orderId, int status)
        {
            OrderId = orderId;
            Status = status;
        }
    }
}

snippet source | anchor

it's needed to register mapping using old event type name (order_status_changed) as follows:

var options = new StoreOptions();

options.EventGraph
    .MapEventType<NewEventNamespace.ConfirmedOrderStatusChanged>("order_status_changed");

var store = new DocumentStore(options);

snippet source | anchor

WARNING

In this case, both old OrderStatusChanged and new ConfirmedOrderStatusChanged event type names will get published with the same order_status_changed event type.