Skip to content

Instantly share code, notes, and snippets.

@warappa
Last active December 22, 2015 00:19
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save warappa/6388270 to your computer and use it in GitHub Desktop.
Save warappa/6388270 to your computer and use it in GitHub Desktop.
A possible way of doing event-versioning with NEventStore and a custom Json.Net NEventStore-serializer.
public class EventStorageBootStrap
{
public static void BootStrap()
{
...
var eventStore = Wireup.Init()
...
.UsingNewtonsoftJsonSerialization(new VersionedEventSerializationBinder())
...
.Build();
...
}
}
// 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;
}
}
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));
}
}
// 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;
}
}
[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