混园子也有些年头了,从各个大牛那儿学了很多东西。技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告)。转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开始选一些有价值的开博分享。
首篇分享的是一个基于Mongodb的轻量级领域驱动框架,创作的起源比较杂,首先来自Mongodb,能够直接存储对象。例如:
public class Person { public Person(string name) { Name = name; } public ObjectId Id { get; private set; } public string Name { get; private set; } public Person ChangeName(string name) { Name = name; return this; } }
var person = new Person("丁丁"); MongoCollection<Person> persons = database.GetCollection<Person>(typeof(Person).Name); persons.Insert(person); person.ChangeName("丁丁2"); persons.Save(person);
如上所示,有一个Person的类,创建一个Person实例,插入到mongo里,然后执行Person的方法,将改变了属性的Person实例保存到mongo里,这是最简单的Mongo用法。
那么,有没有可能通过某种方式,让对象的实例自身就具有持久化的能力呢?比如像传统仓储的做法那样,在一个聚合根里注入仓储。比如,把Person改造一下,像这样:
public class Person { public Person(string name) { persons = CollectionFactory<Person>.GetCollection(); Name = name; persons.Insert(this); } MongoCollection<Person> persons; public ObjectId Id { get; private set; } public string Name { get; private set; } public Person ChangeName(string name) { Name = name; persons.Save(this); return this; } }
Person中内置了Mongo集合通过工厂注入的实例,于是Person就可以这么用了:
var person = new Person("丁丁"); person.ChangeName("丁丁2");
好,到这儿,一切都很顺利。不过Person是个信息量很少很简单的对象。如果Person是一个结构非常复杂的对象,每次使用persons.Save(this),是将整个对象更新,非常占用网络流量,这样使用场景就很有限了。有没有什么改进的办法,比如Save(this)变成将有改动的属性更新掉?
Mongo的原生驱动没有提供局部更新的功能,想要实现只有自己写。那么能否监视一个对象的状态改变呢?AOP动态织入好像可以做到。Castle DynamicProxy是很牛逼的东西,可以用它来试试。
首先,改造一下Person,将属性和方法都变成虚的,让它能被Castle所用:
public class Person { public Person(string name) { Name = name; } public virtual ObjectId Id { get; private set; } public virtual string Name { get; private set; } public virtual Person ChangeName(string name) { Name = name; return this; } }
然后写一个泛型拦截器,在方法执行前对真实对象进行深拷贝,然后在方法执行后将执行前后的对象传入更新委托:
class DataInterceptor<T_AggregateRoot> : StandardInterceptor where T_AggregateRoot : class { public DataInterceptor(Action<T_AggregateRoot, T_AggregateRoot> updateAction, Action<T_AggregateRoot> deleteAction) { this.updateAction = updateAction; this.deleteAction = deleteAction; aggregateRootType = typeof(T_AggregateRoot); } Action<T_AggregateRoot, T_AggregateRoot> updateAction; Action<T_AggregateRoot> deleteAction; T_AggregateRoot aggregateRoot1; T_AggregateRoot aggregateRoot2; Type aggregateRootType; protected override void PreProceed(IInvocation invocation) { if (!invocation.Method.Name.StartsWith("get_") && !invocation.Method.Name.StartsWith("set_") && !invocation.Method.Name.Equals("Abadon")) { try { aggregateRoot1 = NClone.Clone.ObjectGraph((((T_AggregateRoot)invocation.InvocationTarget))); } catch (Exception exception) { Logger.Exception(exception); } } } protected override void PostProceed(IInvocation invocation) { if (!invocation.Method.Name.StartsWith("get_") && !invocation.Method.Name.StartsWith("set_")) { aggregateRoot2 = (T_AggregateRoot)invocation.InvocationTarget; if (invocation.Method.Name.Equals("Abadon")) { deleteAction.Invoke(aggregateRoot2); } else { updateAction.Invoke(aggregateRoot1, aggregateRoot2); } } } }
通过对象深比较得到差异,编译成Mongo更新语句执行更新:
/// <summary> /// 局部更新 /// </summary> /// <remarks> /// 比较对象,找到不一致的地方,进行 /// </remarks> /// <param name="aggregateRoot1"></param> /// <param name="aggregateRoot2"></param> /// <returns></returns> internal void Update(T_AggregateRoot aggregateRoot1, T_AggregateRoot aggregateRoot2) { if (aggregateRoot1 == null) return; CompareObjects compareObjs = new CompareObjects(); compareObjs.MaxDifferences = int.MaxValue; //比较私有属性 compareObjs.ComparePrivateProperties = true; compareObjs.Compare(aggregateRoot1, aggregateRoot2); var id = BsonValue.Create(((dynamic)aggregateRoot2).Id); IMongoQuery query = Query.EQ("_id", id); IMongoUpdate updates; List<IMongoUpdate> allChanges = new List<IMongoUpdate>(); List<IMongoUpdate> allChangesForDelete = new List<IMongoUpdate>(); //分别对null值,集合元素的增删改,进行不同的处理 foreach (Difference dif in compareObjs.Differences) { string fieldName = dif.PropertyName.Substring(1); fieldName = fieldName.Replace("[", ".").Replace("]", ""); BsonValue fieldValue = null; //处理数组删除的情况 if (dif.IsDelete) { IMongoUpdate update2 = MongoDB.Driver.Builders.Update.PopLast(fieldName); allChangesForDelete.Add(update2); continue; } //处理null值 if (dif.Object2.Target == null && dif.Object2Value == null) { try { dynamic nullValueLogContent = new ExpandoObject(); nullValueLogContent.AggregateRoot1 = aggregateRoot1; nullValueLogContent.AggregateRoot2 = aggregateRoot2; nullValueLogContent.Differences = compareObjs.Differences; } catch { } fieldValue = BsonNull.Value; IMongoUpdate update2 = MongoDB.Driver.Builders.Update.Set(fieldName, fieldValue); allChanges.Add(update2); continue; } //原始类型或字符串直接使用对象 //对象类型则转为.ToBsonDocument(); if (dif.Object2.Target.GetType().IsPrimitive || dif.Object2.Target.GetType().Equals(typeof(string)) || dif.Object2.Target.GetType().IsEnum) { fieldValue = dif.Object2.Target == null ? BsonValue.Create(dif.OriginObject2) : BsonValue.Create(dif.Object2.Target); } else { //更新整个集合类 if (dif.Object2.Target.GetType().GetInterface(typeof(IDictionary).FullName) != null || dif.Object2.Target.GetType().GetInterface(typeof(IList).FullName) != null) { fieldValue = BsonValue.Create(dif.OriginObject2); } else if (dif.Object2.Target.GetType() == typeof(DateTime)) { fieldValue = dif.Object2.Target == null ? BsonDateTime.Create(dif.OriginObject2) : BsonDateTime.Create(dif.Object2.Target); } else { //处理普通的class类型 //由于这里OriginObject2一定不会被释放(强引用),所以使用dif.Object2.Target或者dif.OriginObject2都可以 fieldValue = BsonValue.Create(dif.Object2.Target.ToBsonDocument()); } } IMongoUpdate update = MongoDB.Driver.Builders.Update.Set(fieldName, fieldValue); allChanges.Add(update); } //有更新才处理 if (allChanges.Count > 0) { updates = MongoDB.Driver.Builders.Update.Combine(allChanges); collection.Update(query, updates); } foreach (IMongoUpdate up in allChangesForDelete) { collection.Update(query, up); } }
写一个类似Collection的泛型类,提供集合类操作,在操作末尾对对象的实例动态织入:
/// <summary> /// 创建代理 /// </summary> /// <param name="aggregateRoot"></param> /// <returns></returns> T_AggregateRoot CreateProxy(T_AggregateRoot aggregateRoot) { var aggregateRootType = aggregateRoot.GetType(); var constructor = aggregateRootType.GetConstructors().OrderBy(c => c.GetParameters().Length).First(); var parameters = constructor.GetParameters().Select(p => default(object)).ToArray(); return (T_AggregateRoot)proxyGenerator.CreateClassProxyWithTarget(aggregateRootType, aggregateRoot, parameters, new DataInterceptor<T_AggregateRoot>(this.Update, this.Remove)); }
最终,这一系列思路的产物就是一个聚合跟集合:
/// <summary> /// 聚合根泛型集合类 /// </summary> public class AggregateRootCollection<T_AggregateRoot> where T_AggregateRoot : class { ... }
然后用法类似这样:
var persons = new AggregateRootCollection<Person>("TestDb"); var personProxy = persons.Add(new Person("丁丁")); personProxy.ChangeName("丁丁2");
第一行实例化聚合跟集合,第二行用Add方法对新的实例进行动态织入返回代理,第三行就是神奇的执行方法后,状态的变化就立刻持久化了。
以上是这个轻量级领域驱动框架的大致介绍,目前还未发布到Github和nuget上,后续会一篇篇的更新它的实现原理。它适用于一些事务性不强的工程,让开发人员所有关注点就在业务逻辑上,告别持久化。