knowledge-base

Event Versioning

Why?

To avoid big bang releases.

Rules

For most systems, a simple human-readable format such as json is fine for handling messages. Most production systems use mapping with either XML or json. Mapping with weak-schema will also remove many of the versioning problems associated with type-based strong schema discussed in the previous chapter.

There are scenarios where, either for performance reasons or in order to enable data to “pass through” without being understood, a wrapper may be a better solution than a mapping, but it will require more code. This methodology is, however, more common with messages, where an enrichment process is occurring, such as in a document-based system.

Overall though, there are very few reasons why it may be considered for the type-based mechanism. Once brought out of your immediate system and put into a serialized form, the type system does very little. The upcasting system is often difficult to maintain amongst consumers; even with a centralized schema repository, it requires another service. In most scenarios where messages are being passed to/from disparate services, late-binding tends to be a better option.

General Tips

Mapping Events Between Versions

You can do the mapping logic of an event between versions (e.g. OrderPlaced v1 and v2) on one of these two layers in your stack:

Versioning Approaches

# Name Layer Communication Direction
1 Strong Schema - Basic Type Based Versioning Application Uni-directional
2 Weak Schema - Mapping Application Uni-directional
3 Weak Schema - Wrapper Application Uni-directional
4 Negotiation Application Bi-directional
5 Copy and Replace Data Not Applicable

1. Strong Schema - Basic Type Based Versioning

Description

Pro

Con

2. Weak Schema - Mapping

Description

Pro

Con

3. Weak Schema - Wrapper

public class WalletCreatedEvent {
    private JObject _json;

    public WalletCreatedEvent(string json){
        _json = JObject.parse(json);
    }

    public Guid Id { 
        get {return Guid.Parse(_json["id"]);}
        set {_json["id"] = value.ToString();}
    }

    public string Name { 
        get {return _json["name"];}
        set {_json["name"] = value;}
    }   
}

Description

Pro

Con

4. Negotiation

Description

Pro

Con

5. Copy And Replace

… todo

Description

Pro

Con

General Concerns

Versioning Of Behavior

If you find yourself putting branching logic or calculation logic in a projection, especially if it is based on time, you are probably missing logic in the creation of that event. A good example is the calculation of an invoice with Tax. The tax value might change over time. Make sure the event lists the tax value that was used, cause this value might change over time in the code. It may be worth including a description field even if only a string that describes the type of calculation made.

Exterior Calls

Exterior calls are seldom idempotent. Try to model the event post external call to contain all the information necessary so that downstream wise, you can replay with the result of the exterior call (e.g. PaymentSucceededEvent). Note that some information is just not allowed to be stored (credit card details).

A related problem to this happens when projections start doing lookups to other projections. If you find a projection making calls to external services or to other projections it will be a problem to replay later. As example there could be a customer table managed by a CustomerProjection and an orders table managed by an OrdersProjection. Instead of having the OrdersProjection listen to customer events the decision was made to have it lookup in the CustomerProjection what the current Customer Name is to copy into the orders table. This can save time and duplication in the OrdersProjection code.

Changing Semantic Meaning

One important aspect of versioning is that semantic meaning cannot change between versions of software. There is no good way for a downstream consumer to understand a semantic meaning change. An example would be that the value for the temperature field went from Celsius to Fahrenheit.

Snapshots

If you make snapshots for read efficiency, consider to still keep the original events, in case snapshots need to be regenerated or evolve over time.

In general, you can delete the old snapshots and then regenerate all snapshots based on updated logic/domain. If you need it side by side (an old an new version of a snapshot), regenerate the new snapshots and make sure you can handle both snapshot models in production. At a later stage the older snapshot model can be deprecated.

Avoid “and”

If an event has “and” in the name (e.g. TicketPaidForAndIssued) consider to split into separate events. There is no need for a 1:1 relationship to “event received > process > output single event”. Sometimes there are no output events necessary or multiple might.

Resources