A possible way of doing event-versioning with NEventStore and a custom Json.Net NEventStore-serializer.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class EventStorageBootStrap | |
{ | |
public static void BootStrap() | |
{ | |
... | |
var eventStore = Wireup.Init() | |
... | |
.UsingNewtonsoftJsonSerialization(new VersionedEventSerializationBinder()) | |
... | |
.Build(); | |
... | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// the up-to-date version of your event | |
[VersionedEvent("TeamMemberChangedUsername", 1)] | |
public class TeamMemberChangedUsername : Event | |
{ | |
public readonly string Username; | |
public readonly DateTime Date = DateTime.Now; | |
public readonly string Comment = ""; | |
public TeamMemberChangedUsername(Guid aggregateId, string username, DateTime date, string comment) | |
: base(aggregateId) | |
{ | |
if (date == default(DateTime) || | |
date == DateTime.MinValue) | |
date = DateTime.Now; | |
this.Username = username; | |
this.Date = date; | |
this.Comment = comment; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class NewtonsoftJsonSerializer : ISerialize | |
{ | |
public NewtonsoftJsonSerializer(SerializationBinder binder = null, IEnumerable<JsonConverter> converters = null, params Type[] knownTypes) | |
{ | |
var settings = new JsonSerializerSettings() | |
{ | |
Converters = (converters ?? Enumerable.Empty<JsonConverter>()).Concat(new []{new GuidConverter()}).ToList(), | |
DefaultValueHandling = DefaultValueHandling.Ignore, | |
ConstructorHandling = ConstructorHandling.Default | |
}; | |
untypedSerializer = JsonSerializer.Create(settings); | |
untypedSerializer.TypeNameHandling = TypeNameHandling.Auto; | |
untypedSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; | |
untypedSerializer.NullValueHandling = NullValueHandling.Ignore; | |
untypedSerializer.TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple; | |
typedSerializer = JsonSerializer.Create(settings); | |
typedSerializer.TypeNameHandling = TypeNameHandling.All; | |
typedSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; | |
typedSerializer.NullValueHandling = NullValueHandling.Ignore; | |
typedSerializer.TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple; | |
// important: set new binder here! | |
untypedSerializer.Binder = | |
typedSerializer.Binder = binder; | |
if (knownTypes != null && knownTypes.Length == 0) | |
{ | |
knownTypes = null; | |
} | |
Type[] types = knownTypes; | |
this.knownTypes = ((types != null) ? ((IEnumerable<Type>)types) : this.knownTypes); | |
} | |
private readonly JsonSerializer untypedSerializer; | |
private readonly JsonSerializer typedSerializer; | |
private readonly IEnumerable<Type> knownTypes = new Type[] | |
{ | |
typeof(List<EventMessage>), | |
typeof(Dictionary<string, object>) | |
}; | |
public virtual void Serialize<T>(Stream output, T graph) | |
{ | |
using (StreamWriter streamWriter = new StreamWriter(output, Encoding.UTF8)) | |
{ | |
this.Serialize(new JsonTextWriter(streamWriter), graph); | |
} | |
} | |
protected virtual void Serialize(JsonWriter writer, object graph) | |
{ | |
try | |
{ | |
this.GetSerializer(graph.GetType()).Serialize(writer, graph); | |
} | |
finally | |
{ | |
if (writer != null) | |
{ | |
((IDisposable)writer).Dispose(); | |
} | |
} | |
} | |
public virtual T Deserialize<T>(Stream input) | |
{ | |
T result; | |
using (StreamReader streamReader = new StreamReader(input, Encoding.UTF8)) | |
{ | |
result = this.Deserialize<T>(new JsonTextReader(streamReader)); | |
} | |
return result; | |
} | |
protected virtual T Deserialize<T>(JsonReader reader) | |
{ | |
Type type = typeof(T); | |
T result; | |
try | |
{ | |
result = (T)((object)this.GetSerializer(type).Deserialize(reader, type)); | |
} | |
finally | |
{ | |
if (reader != null) | |
{ | |
((IDisposable)reader).Dispose(); | |
} | |
} | |
return result; | |
} | |
protected virtual JsonSerializer GetSerializer(Type typeToSerialize) | |
{ | |
if (this.knownTypes.Contains(typeToSerialize)) | |
{ | |
return this.untypedSerializer; | |
} | |
return this.typedSerializer; | |
} | |
} | |
public static class WireupExtensions | |
{ | |
public static SerializationWireup UsingNewtonsoftJsonSerialization(this PersistenceWireup wireup, SerializationBinder binder, params Type[] knownTypes) | |
{ | |
return wireup.UsingCustomSerialization(new NewtonsoftJsonSerializer(binder, new JsonConverter[] { | |
}, knownTypes)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// in an old assembly which contains all the old events ever existed | |
[VersionedEvent("TeamMemberChangedUsername", 0)] | |
public class TeamMemberChangedUsernameV1 : Event | |
{ | |
public readonly string Username; | |
public readonly DateTime Date = DateTime.Now; | |
public TeamMemberChangedUsernameV1(Guid aggregateId, string username, DateTime date) | |
: base(aggregateId) | |
{ | |
if (date == default(DateTime) || | |
date == DateTime.MinValue) | |
date = DateTime.Now; | |
this.Username = username; | |
this.Date = date; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[AttributeUsage(AttributeTargets.Class)] | |
public class VersionedEventAttribute : Attribute | |
{ | |
public int Version { get; set; } | |
public string Identifier { get; set; } | |
public VersionedEventAttribute(string identifier, int version = 0) | |
{ | |
this.Version = version; | |
this.Identifier = identifier; | |
} | |
} | |
public class VersionedEventSerializationBinder : DefaultSerializationBinder | |
{ | |
private VersionedEventAttribute GetVersionInformation(Type type) | |
{ | |
var attr = type.GetCustomAttributes(typeof(VersionedEventAttribute), false).Cast<VersionedEventAttribute>().FirstOrDefault(); | |
return attr; | |
} | |
public override void BindToName(Type serializedType, out string assemblyName, out string typeName) | |
{ | |
var versionInfo = GetVersionInformation(serializedType); | |
if (versionInfo != null) | |
{ | |
var impl = GetImplementation(versionInfo); | |
assemblyName = null; | |
typeName = versionInfo.Identifier + "|" + versionInfo.Version; | |
} | |
else | |
{ | |
base.BindToName(serializedType, out assemblyName, out typeName); | |
} | |
} | |
private VersionedEventAttribute GetVersionInformation(string serializedInfo) | |
{ | |
var strs = serializedInfo.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); | |
return new VersionedEventAttribute(strs[0], int.Parse(strs[1])); | |
} | |
public override Type BindToType(string assemblyName, string typeName) | |
{ | |
if (typeName.Contains('|')) | |
{ | |
var type = GetImplementation(GetVersionInformation(typeName)); | |
return type; | |
} | |
return base.BindToType(assemblyName, typeName); | |
} | |
private Type GetImplementation(VersionedEventAttribute attribute) | |
{ | |
var types = AppDomain.CurrentDomain.GetAssemblies() | |
.Where(x => x.IsDynamic == false) | |
.SelectMany(x => x.GetExportedTypes() | |
.Where(y => y.IsAbstract == false && | |
y.IsInterface == false)); | |
var versionedEvents = types | |
.Where(x => x.GetCustomAttributes(typeof(VersionedEventAttribute), false).Any()); | |
return versionedEvents.Where(x => | |
{ | |
var attributes = x.GetCustomAttributes(typeof(VersionedEventAttribute), false).Cast<VersionedEventAttribute>(); | |
if (attributes.Where(y => y.Version == attribute.Version && | |
y.Identifier == attribute.Identifier) | |
.Any()) | |
return true; | |
return false; | |
}) | |
.FirstOrDefault(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment