基于Mongodb的轻量级领域驱动框架(序)

时间:2022-08-31 12:54:30

混园子也有些年头了,从各个大牛那儿学了很多东西。技术这东西和中国的料理一样,其中技巧和经验,代代相传(这不是舌尖上的中国广告)。转身回头一望,几年来自己也积累了一些东西,五花八门涉猎到各种方向,今日开始选一些有价值的开博分享。

首篇分享的是一个基于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上,后续会一篇篇的更新它的实现原理。它适用于一些事务性不强的工程,让开发人员所有关注点就在业务逻辑上,告别持久化。