Event Up-Conversion with saving Event-Class name

NEventStore 3.2.0.0

As far as I found out, NEventStore requires that old types of events have to be stored around for conversion events. In order for their deserialization to be correct in the future, they must have a unique name . It is recommended to name it as EventEVENT_VERSION .

Is there a way to avoid EventV1 , EventV2 , ..., EventVN clutter up your domain model and just use Event ?
What are your strategies?

+4
source share
1 answer

The question was long, long ago, the answer was missing ...

In the discussion mentioned in the comments, I came up with - I would say - an elegant solution:

Do not save type name, but identifier (versions)

The identifier is set by the attribute at the class level, i.e.

 namespace CurrentEvents { [Versioned("EventSomethingHappened", 0)] // still version 0 public class EventSomethingHappened { ... } } 

This identifier must be serialized in / next to the payload. In serialized form
"Some.Name.Space.EventSomethingHappened" β†’ "EventSomethingHappened | 0"

If a different version of this event is required, the current version is copied in an "obsolete" assembly or simply in a different namespace and renamed (type-name) to "EventSomethingHappenedV0", but the Versioned attribute remains untouched (in this copy)

 namespace LegacyEvents { [Versioned("EventSomethingHappened", 0)] // still version 0 public class EventSomethingHappenedV0 { ... } } 

In the new version (ibid., Under the same name), only part of the attribute version is increased. What is it!

 namespace CurrentEvents { [Versioned("EventSomethingHappened", 1)] // new version 1 public class EventSomethingHappened { ... } } 

Json.NET supports binding, which displays type identifiers of types and vice versa. Here is the binder ready for production:

 public class VersionedSerializationBinder : DefaultSerializationBinder { private Dictionary<string, Type> _getImplementationLookup = new Dictionary<string, Type>(); private static Type[] _versionedEvents = null; protected static Type[] VersionedEvents { get { if (_versionedEvents == null) _versionedEvents = AppDomain.CurrentDomain.GetAssemblies() .Where(x => x.IsDynamic == false) .SelectMany(x => x.GetExportedTypes() .Where(y => y.IsAbstract == false && y.IsInterface == false)) .Where(x => x.GetCustomAttributes(typeof(VersionedAttribute), false).Any()) .ToArray(); return _versionedEvents; } } public VersionedSerializationBinder() { } private VersionedAttribute GetVersionInformation(Type type) { var attr = type.GetCustomAttributes(typeof(VersionedAttribute), false).Cast<VersionedAttribute>().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); typeName = versionInfo.Identifier + "|" + versionInfo.Revision; } else { base.BindToName(serializedType, out assemblyName, out typeName); } assemblyName = null; } private VersionedAttribute GetVersionInformation(string serializedInfo) { var strs = serializedInfo.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); if (strs.Length != 2) return null; return new VersionedAttribute(strs[0], strs[1]); } public override Type BindToType(string assemblyName, string typeName) { if (typeName.Contains('|')) { var type = GetImplementation(GetVersionInformation(typeName)); if (type == null) throw new InvalidOperationException(string.Format("VersionedEventSerializationBinder: No implementation found for type identifier '{0}'", typeName)); return type; } else { var versionInfo = GetVersionInformation(typeName + "|0"); if (versionInfo != null) { var type = GetImplementation(versionInfo); if (type != null) return type; // else: continue as it is a normal serialized object... } } // resolve assembly name if not in serialized info if (string.IsNullOrEmpty(assemblyName)) { Type type; if (typeName.TryFindType(out type)) { assemblyName = type.Assembly.GetName().Name; } } return base.BindToType(assemblyName, typeName); } private Type GetImplementation(VersionedAttribute attribute) { Type eventType = null; if (_getImplementationLookup.TryGetValue(attribute.Identifier + "|" + attribute.Revision, out eventType) == false) { var events = VersionedEvents .Where(x => { return x.GetCustomAttributes(typeof(VersionedAttribute), false) .Cast<VersionedAttribute>() .Where(y => y.Revision == attribute.Revision && y.Identifier == attribute.Identifier) .Any(); }) .ToArray(); if (events.Length == 0) { eventType = null; } else if (events.Length == 1) { eventType = events[0]; } else { throw new InvalidOperationException( string.Format("VersionedEventSerializationBinder: Multiple types have the same VersionedEvent attribute '{0}|{1}':\n{2}", attribute.Identifier, attribute.Revision, string.Join(", ", events.Select(x => x.FullName)))); } _getImplementationLookup[attribute.Identifier + "|" + attribute.Revision] = eventType; } return eventType; } } 

... and Versioned -attribute

 [AttributeUsage(AttributeTargets.Class)] public class VersionedAttribute : Attribute { public string Revision { get; set; } public string Identifier { get; set; } public VersionedAttribute(string identifier, string revision = "0") { this.Identifier = identifier; this.Revision = revision; } public VersionedAttribute(string identifier, long revision) { this.Identifier = identifier; this.Revision = revision.ToString(); } } 

Finally, use standard binding like this

 JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, Binder = new VersionedSerializationBinder() }); 

For a full implementation of Json.NET ISerialize see (a bit dated): https://gist.github.com/warappa/6388270

+4
source

Source: https://habr.com/ru/post/1490221/


All Articles