First of all I invite you to read my previous post on How to support changes in your software. It will give you a good heads up on where I’m going with this post.
Having a strategy to support change is a good practice but sometimes it fails on the first change because, well you know, it’s not that easy. I’m a big fan of document database and NoSQL. Mongo DB is a good match for me because it’s easy to install on Windows and use from C# application. I won’t go in to detail on how to create a .NET Application that connect to Mongo. The are plenty of site out there (here, here, here and here) that can help you with that. Document databases are often referred to as schema less database but it’s not completely true. There is a schema, it’s just not as rigid and well defined as traditional databases. The schema used Mongo is based on Json format. For example look at the following Json document. (Only the event payload is shown here and in all other sample)
{ "Name" : "Logitech wireless mouse", "Price" : "29.99$" }
This document can be the definition of a event in an Event Store. The C# class to handle this event should be:
public class ProductAddedEvent : DomainEvent { public string Name { get; set; } public string Price { get; set; } }
The missing fields are manage by the DomainEventbase class. This is a really simple object to illustrate the concept.
Now we realize that we made an error in the definition of the Price field. We want to convert it into a double type to be able to make some calculation with it. In an event sourcing system the events are immutable, they must never be changed. The past is written in stone. So we need to work pretty much like functional programming to evolve our event to a new format. The first thing we need to do is to tag each evolution with a version number. Because all our event derived from the DomainEvent class that is easy. First add a base type to DomainEvent to hold the versioning concerns :
[Serializable] public abstract class DomainEvent : Versionable { // ... }
Now take alook at the Versionable class :
[Serializable] public abstract class Versionable { private int? _version; public int Version { get { return _version.GetValueOrDefault(CurrentVersion); } set { _version = value; } } protected internal virtual int CurrentVersion { get { return 1; } } }
This will set any new DomainEvent to version 1 by default and provide the ability to change it in the future. The Version field will be serialized and save in Mongo DB. Here is the C# class that define the event payload :
public class FooEvent : DomainEvent { public string Name { get; set; } public string Value { get; set; } }
The serialized version of that class will look like this :
{ "Version" : 1, "Name" : "Foo", "Value" : "3.00$" }
Now that we can know which version of the document we are processing we can start thinking about changing the C# class. The goal is to preserve all needed data from one version to the other. Depending on the type of modification different technique can be used. Our first change will be to change the Value field type from string to double. The only good way to do that is to use Mongo BSonSerializer attribute.
public class FooEvent : DomainEvent { public string Name { get; set; } [BSonSerializer(typeof(AmountToDoubleSerializer)] public double Value { get; set; } } public class AmountToDoubleSerializer : BsonBaseSerializer { public override object Deserialize( BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options) { VerifyTypes(nominalType, actualType, typeof(Double)); BsonType bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) { case BsonType.String: string readString = bsonReader.ReadString(); double value = Double.Parse(readString.Replace("$", ""), CultureInfo.InvariantCulture); return value; case BsonType.Double: return bsonReader.ReadDouble(); default: string message = string.Format("Cannot deserialize BsonString from BsonType {0}.", bsonType); throw new FileFormatException(message); } } public override void Serialize( BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options) { if (value == null) { throw new ArgumentNullException("value"); } bsonWriter.WriteDouble((Double)value); } }
The AmountToDoubleSerializer will be able to convert string type to double but also to read and save properties that are already in double format. This will allow for our system to read past events as well as new ones.
In the next post I will show how can we do other transformation such as adding, removing and renaming a property.