基於Mongodb的輕量級領域驅動框架(序)

混園子也有些年頭了,從各個大牛那兒學了不少東西。技術這東西和中國的料理同樣,其中技巧和經驗,代代相傳(這不是舌尖上的中國廣告)。轉身回頭一望,幾年來本身也積累了一些東西,五花八門涉獵到各類方向,今日開始選一些有價值的開博分享。數組

首篇分享的是一個基於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上,後續會一篇篇的更新它的實現原理。它適用於一些事務性不強的工程,讓開發人員全部關注點就在業務邏輯上,告別持久化。

相關文章
相關標籤/搜索