混園子也有些年頭了,從各個大牛那兒學了不少東西。技術這東西和中國的料理同樣,其中技巧和經驗,代代相傳(這不是舌尖上的中國廣告)。轉身回頭一望,幾年來本身也積累了一些東西,五花八門涉獵到各類方向,今日開始選一些有價值的開博分享。數組
首篇分享的是一個基於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改造一下,像這樣:ide
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就能夠這麼用了:ui
var person = new Person("丁丁"); person.ChangeName("丁丁2");
好,到這兒,一切都很順利。不過Person是個信息量不多很簡單的對象。若是Person是一個結構很是複雜的對象,每次使用persons.Save(this),是將整個對象更新,很是佔用網絡流量,這樣使用場景就頗有限了。有沒有什麼改進的辦法,好比Save(this)變成將有改動的屬性更新掉?this
Mongo的原生驅動沒有提供局部更新的功能,想要實現只有本身寫。那麼可否監視一個對象的狀態改變呢?AOP動態織入好像能夠作到。Castle DynamicProxy是很牛逼的東西,能夠用它來試試。spa
首先,改造一下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; } }
而後寫一個泛型攔截器,在方法執行前對真實對象進行深拷貝,而後在方法執行後將執行先後的對象傳入更新委託:code
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上,後續會一篇篇的更新它的實現原理。它適用於一些事務性不強的工程,讓開發人員全部關注點就在業務邏輯上,告別持久化。