基于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领域驱动理解
在理解领域驱动的时候,网上很多大谈理论的文章,这种对于初学者不是太容易接受.根据我自己的学习经历,建议按照如下几个步骤学习: 粗略的看一遍领域驱动的理论,做到心中有形,知道领域驱动是什么,解决什么问题 ...
随机推荐
- Mono for Android (2)-- Android应用程序初认识
一:日志记录 先添加using Android.Util; 在该命名控件下有log类 Log.Info("HA", "End onCreate"); //记录消 ...
- SQL Server 2008 没有可用于 charge_sys_Log.LDF 的编辑器
因为上网问题重新装了系统.今天在整 SQL Server 的时候出现了这样一个问题. 因为之前装 SQL Server 的时候没有遇到过这种情况,感觉很新奇.所以详细的记录一下,希望对别人能有一定 ...
- “我爱淘”第二冲刺阶段Scrum站立会议2
完成任务: 对发布页面优化了一下,并将登陆的功能实现了一点,就是还没有实现注册的功能 . 计划任务: 在客户端实现分类功能,通过学院的分类查看书籍. 遇到问题: 自动将数据库的内容返回到客户端.
- 团队作业php
<?php$kouwei=$_GET["select"];$daxiao=$_GET["RadioGroup1"];$peiliao=$_GET[&quo ...
- windows 2008 下C#调用office组件访问拒绝的解决方法(failed due to the following error: 80070005 拒绝访问)
"组件服务"- >"计算机"- >"我的电脑"- >"DCOM配置"->找到word->属 ...
- 前端之JavaScript第一天学习(1)-JavaScript 简介
javaScript 是世界上最流行的编程语言. 这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. JavaScript 是脚本语言 JavaSc ...
- PHP中如何连接数据库基本语句
只是后端修改页面,不需要在前端显示的可以删除原有代码只输入<?php 开始编写语言即可,后面的?>也可以省略 //造一个连接$connect = @mysql_connect(" ...
- MyEclipse2015 编写js报 'Calculating completion proposals..' has encountered a problem.
前言:编写js(按点后)弹出这个鬼东西,百度不到..估计是破解有问题.只有换版本了. 版本:MyEclipse 2015 stable 1.0 详细错误信息 解决:换成2.0版本
- USACO 4.1.2 栅栏的木料
这个讲的超好....一定要看...然后看我代码就好懂啦... http://blog.csdn.net/ta201314/article/details/41287567 各种优化确实非常好....搜 ...
- CNAME
CNAME指别名记录也被称为规范名字.这种记录允许您将多个名字映射到同一台计算机. 通常用于同时提供WWW和MAIL服务的计算机.例如,有一台计算机名为“host.mydomain.com”(A记录) ...