基于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领域驱动理解
在理解领域驱动的时候,网上很多大谈理论的文章,这种对于初学者不是太容易接受.根据我自己的学习经历,建议按照如下几个步骤学习: 粗略的看一遍领域驱动的理论,做到心中有形,知道领域驱动是什么,解决什么问题 ...
随机推荐
- Android HttpClient GET或者POST请求基本使用方法(转)
在Android开发中我们经常会用到网络连接功能与服务器进行数据的交互,为此Android的SDK提供了Apache的HttpClient来方便我们使用各种Http服务.这里只介绍如何使用HttpCl ...
- java数据结构和算法------插入排序
package iYou.neugle.sort; public class Insert_sort { public static void InsertSort(double[] array) { ...
- mysql使用二进制日志恢复数据
一.恢复到某个二进制文件 1.开启二进制日志 在mysqld的配置节点下添加如下配置 log-bin="E:/Mysql57BinLog/binlog"(windows下的路径,l ...
- Daily Scrum1--团队项目分工及估计时间
团队项目分工及估计时间 PM(黄剑锟): 任务一:监督进度,将每一天完成的任务总结,在各个部分进行协调与帮助.(贯穿整个项目周期) 任务二:提高搜索反应时间,优化搜索算法.(估计时间8小时) 程序设计 ...
- 四则运算2扩展---c++
题目:让程序能接受用户输入答案,并判定对错.最后给出总共对/错 的数量. 一.设计思想 1.存储用户输入答案,与正确答案比较得出总共对错数量. 二.源程序代码 #include<iostream ...
- 使用OutputDebugString输出调试信息
在编写控制台程序的时候我们经常会使用printf输出调试信息,使我们了解程序的状态,方便调试,但是当编写非控制台程序的时候这种方法就行不通了,那我们应该怎么办?上网查了一些方法,大致就如下几种 使用L ...
- Liferay 7 portlet中所有能在@Component中修改的属性
"com.liferay.portlet.action-timeout", "com.liferay.portlet.active", "com.li ...
- ueditor:原谅我这一生不羁放纵爱独特
客户指明道姓需要使用百度编辑器,好吧,虽然自从李彦宏把一个好好的千千静听搞得节操尽碎之后,我就对百度的东西毫无好感,但是客户是上帝嘛,不就一个文本编辑器嘛,弄之,始料未及的是,就是这样一个简单的文本编 ...
- Hibernate中启用日志
Problem How do you determine what SQL query is being executed by Hibernate? How can you see the Hibe ...
- DOM中事件绑定补充方法
先将上一篇文章中提到的为元素增加事件的方法和移除事件的方法拿过来: <span style="font-size:18px;">//跨浏览器添加事件 function ...