保持事件类名称的事件向上转换

时间:2013-07-08 07:29:24

标签: neventstore

NEventStore 3.2.0.0

据我所知,NEventStore要求 事件类型必须保持以进行事件上转换。
为了使他们在将来正确反序列化,他们必须具有唯一名称。建议将其称为EventEVENT_VERSION

有没有办法避免EventV1EventV2,...,EventVN 混乱您的域模型,只是继续使用{ {1}}
你有什么策略?

1 个答案:

答案 0 :(得分:5)

In a question long, long time ago, an answer was missing... In the discussion referred in the comments, I came up with an - I would say - elegant solution: Don't save the type-name but an (versioned) identifier The identifier is set by an attribute on class-level, i.e. namespace CurrentEvents { [Versioned("EventSomethingHappened", 0)] // still version 0 public class EventSomethingHappened { ... } } This identifier should get serialized in/beside the payload. In serialized form "Some.Name.Space.EventSomethingHappened" -> "EventSomethingHappened|0" When another version of this event is required, the current version is copied in an "legacy" assembly or just in another 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 (at the same place, under the same name) just the version-part of the attribute gets incremented. And that's it! namespace CurrentEvents { [Versioned("EventSomethingHappened", 1)] // new version 1 public class EventSomethingHappened { ... } } Json.NET supports binders which maps type-identifiers to types and back. Here is a production-ready binder: 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 the 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(); } } At last use the versioned binder like this JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, Binder = new VersionedSerializationBinder() }); For a full Json.NET ISerialize-implementation see (an little outdated) gist here: https://gist.github.com/warappa/6388270