基于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改造一下,像这样:
- 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();
- 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 > )
- {
- 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上,后续会一篇篇的更新它的实现原理。它适用于一些事务性不强的工程,让开发人员所有关注点就在业务逻辑上,告别持久化。
基于Mongodb的轻量级领域驱动框架(序)的更多相关文章
- 基于事件驱动的DDD领域驱动设计框架分享(附源代码)
原文:基于事件驱动的DDD领域驱动设计框架分享(附源代码) 补充:现在再回过头来看这篇文章,感觉当初自己偏激了,呵呵.不过没有以前的我,怎么会有现在的我和现在的enode框架呢?发现自己进步了真好! ...
- Lind.DDD敏捷领域驱动框架~Lind.DDD各层介绍
回到目录 Lind.DDD项目主要面向敏捷,快速开发,领域驱动等,对于它的分层也是能合并的合并,比之前大叔的框架分层更粗糙一些,或者说更大胆一些,在开发人员使用上,可能会感觉更方便了,更益使用了,这就 ...
- Lind.DDD敏捷领域驱动框架~介绍
回到占占推荐博客索引 最近觉得自己的框架过于复杂,在实现开发使用中有些不爽,自己的朋友们也经常和我说,框架太麻烦了,要引用的类库太多:之前架构之所以这样设计,完全出于对职责分离和代码附复用的考虑,主要 ...
- .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
.net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...
- 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇
前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已. 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂. 不过,这些文章对于那些初学者而 ...
- 基于ABP落地领域驱动设计-00.目录和小结
<实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...
- (转)EntityFramework之领域驱动设计实践
EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...
- EntityFramework之领域驱动设计实践
EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...
- DDD领域驱动理解
在理解领域驱动的时候,网上很多大谈理论的文章,这种对于初学者不是太容易接受.根据我自己的学习经历,建议按照如下几个步骤学习: 粗略的看一遍领域驱动的理论,做到心中有形,知道领域驱动是什么,解决什么问题 ...
随机推荐
- Swift function how to return nil
这两天在学习Stanford出品的iOS7的课程,这个课程去年也看过,但是看到第3课就不行了,满篇的OC,把人都搞晕了.这段时间因为要写个iOS的App,正好赶上了Swift问世,所以趁着这股劲继续学 ...
- scjp考试准备 - 5 - 重载和重写
如下代码,在所指示的位置插入代码能够正常编译: class Alpha{ public void bar(int... x){}; public void bar(int x){}; } public ...
- netstat用法
netstat - 显示网络连接,路由表,接口状态,伪装连接,网络链路信息和组播成员组. 总 netstat [address_family_options] [--tcp|-t] [--udp|-u ...
- Android实现Button事件的处理
Android实现Button事件的处理 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 代码实现 首先是最基本的线性布局,给每个控件设立id值,以供代 ...
- vimium
安装在chrome上的一个插件,可以实现chrome无鼠标无键盘操作. 事实上vimium就是提供了一系列的快捷键列表,所以只要熟悉了这些快捷键就可以方便使用了. 要查看快捷键列表,打开chrome, ...
- struts2标签详解
struts2标签讲解 要使用Struts2的标签,只需要在JSP页面添加如下一行定义即可:<%@ taglib prefix="s" uri="/struts-t ...
- 如何使用css和jquery控制文章标题字数?
如何使用css控制文章标题字数? 最佳答案 控制文章标题字数,不是动态网页的专利,如果静态页面使用CSS样式,也可以实现相同的效果! 看这个例子,你们可以复制到记事本保存为HTML文件看效果! < ...
- quartz2D简单使用
quartz2D绘图 1:上下文:context,这个翻译不好理解,其实翻译环境更好一点,就是给了你一个画板,你看不到而已 在: CGContextRef ctx = UIGraphicsGetCur ...
- 项目中用到的js日期函数
<script type="text/javascript"> //替换字符串 function Replace(str, from, to) { ...
- 【Binary Tree Zigzag Level Order Traversal】cpp
题目: Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from lef ...