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

首篇分享的是一个基于Mongodb的轻量级领域驱动框架,创作的起源比较杂,首先来自Mongodb,能够直接存储对象。例如:

  1. public class Person
  2. {
  3. public Person(string name)
  4. {
  5. Name = name;
  6. }
  7.  
  8. public ObjectId Id { get; private set; }
  9.  
  10. public string Name { get; private set; }
  11.  
  12. public Person ChangeName(string name)
  13. {
  14. Name = name;
  15. return this;
  16. }
  17. }
  1. var person = new Person("丁丁");
  2. MongoCollection<Person> persons = database.GetCollection<Person>(typeof(Person).Name);
  3. persons.Insert(person);
  4. person.ChangeName("丁丁2");
  5. persons.Save(person);

如上所示,有一个Person的类,创建一个Person实例,插入到mongo里,然后执行Person的方法,将改变了属性的Person实例保存到mongo里,这是最简单的Mongo用法。

那么,有没有可能通过某种方式,让对象的实例自身就具有持久化的能力呢?比如像传统仓储的做法那样,在一个聚合根里注入仓储。比如,把Person改造一下,像这样:

  1. public class Person
  2. {
  3. public Person(string name)
  4. {
  5. persons = CollectionFactory<Person>.GetCollection();
  6. Name = name;
  7. persons.Insert(this);
  8. }
  9.  
  10. MongoCollection<Person> persons;
  11.  
  12. public ObjectId Id { get; private set; }
  13.  
  14. public string Name { get; private set; }
  15.  
  16. public Person ChangeName(string name)
  17. {
  18. Name = name;
  19. persons.Save(this);
  20. return this;
  21. }
  22. }

Person中内置了Mongo集合通过工厂注入的实例,于是Person就可以这么用了:

  1. var person = new Person("丁丁");
  2. person.ChangeName("丁丁2");

好,到这儿,一切都很顺利。不过Person是个信息量很少很简单的对象。如果Person是一个结构非常复杂的对象,每次使用persons.Save(this),是将整个对象更新,非常占用网络流量,这样使用场景就很有限了。有没有什么改进的办法,比如Save(this)变成将有改动的属性更新掉?

Mongo的原生驱动没有提供局部更新的功能,想要实现只有自己写。那么能否监视一个对象的状态改变呢?AOP动态织入好像可以做到。Castle DynamicProxy是很牛逼的东西,可以用它来试试。

首先,改造一下Person,将属性和方法都变成虚的,让它能被Castle所用:

  1. public class Person
  2. {
  3. public Person(string name)
  4. {
  5. Name = name;
  6. }
  7.  
  8. public virtual ObjectId Id { get; private set; }
  9.  
  10. public virtual string Name { get; private set; }
  11.  
  12. public virtual Person ChangeName(string name)
  13. {
  14. Name = name;
  15. return this;
  16. }
  17. }

然后写一个泛型拦截器,在方法执行前对真实对象进行深拷贝,然后在方法执行后将执行前后的对象传入更新委托:

  1. class DataInterceptor<T_AggregateRoot> : StandardInterceptor where T_AggregateRoot : class
  2. {
  3.  
  4. public DataInterceptor(Action<T_AggregateRoot, T_AggregateRoot> updateAction, Action<T_AggregateRoot> deleteAction)
  5. {
  6. this.updateAction = updateAction;
  7. this.deleteAction = deleteAction;
  8. aggregateRootType = typeof(T_AggregateRoot);
  9. }
  10.  
  11. Action<T_AggregateRoot, T_AggregateRoot> updateAction;
  12. Action<T_AggregateRoot> deleteAction;
  13.  
  14. T_AggregateRoot aggregateRoot1;
  15. T_AggregateRoot aggregateRoot2;
  16. Type aggregateRootType;
  17.  
  18. protected override void PreProceed(IInvocation invocation)
  19. {
  20. if (!invocation.Method.Name.StartsWith("get_") && !invocation.Method.Name.StartsWith("set_") && !invocation.Method.Name.Equals("Abadon"))
  21. {
  22. try
  23. {
  24. aggregateRoot1 = NClone.Clone.ObjectGraph((((T_AggregateRoot)invocation.InvocationTarget)));
  25. }
  26. catch (Exception exception)
  27. {
  28. Logger.Exception(exception);
  29. }
  30. }
  31. }
  32.  
  33. protected override void PostProceed(IInvocation invocation)
  34. {
  35. if (!invocation.Method.Name.StartsWith("get_") && !invocation.Method.Name.StartsWith("set_"))
  36. {
  37. aggregateRoot2 = (T_AggregateRoot)invocation.InvocationTarget;
  38. if (invocation.Method.Name.Equals("Abadon"))
  39. {
  40. deleteAction.Invoke(aggregateRoot2);
  41. }
  42. else
  43. {
  44. updateAction.Invoke(aggregateRoot1, aggregateRoot2);
  45. }
  46. }
  47. }
  48. }

通过对象深比较得到差异,编译成Mongo更新语句执行更新:

  1. /// <summary>
  2. /// 局部更新
  3. /// </summary>
  4. /// <remarks>
  5. /// 比较对象,找到不一致的地方,进行
  6. /// </remarks>
  7. /// <param name="aggregateRoot1"></param>
  8. /// <param name="aggregateRoot2"></param>
  9. /// <returns></returns>
  10. internal void Update(T_AggregateRoot aggregateRoot1, T_AggregateRoot aggregateRoot2)
  11. {
  12. if (aggregateRoot1 == null)
  13. return;
  14.  
  15. CompareObjects compareObjs = new CompareObjects();
  16. compareObjs.MaxDifferences = int.MaxValue;
  17. //比较私有属性
  18. compareObjs.ComparePrivateProperties = true;
  19. compareObjs.Compare(aggregateRoot1, aggregateRoot2);
  20. var id = BsonValue.Create(((dynamic)aggregateRoot2).Id);
  21. IMongoQuery query = Query.EQ("_id", id);
  22. IMongoUpdate updates;
  23. List<IMongoUpdate> allChanges = new List<IMongoUpdate>();
  24. List<IMongoUpdate> allChangesForDelete = new List<IMongoUpdate>();
  25. //分别对null值,集合元素的增删改,进行不同的处理
  26. foreach (Difference dif in compareObjs.Differences)
  27. {
  28. string fieldName = dif.PropertyName.Substring();
  29. fieldName = fieldName.Replace("[", ".").Replace("]", "");
  30. BsonValue fieldValue = null;
  31.  
  32. //处理数组删除的情况
  33. if (dif.IsDelete)
  34. {
  35. IMongoUpdate update2 = MongoDB.Driver.Builders.Update.PopLast(fieldName);
  36. allChangesForDelete.Add(update2);
  37. continue;
  38. }
  39.  
  40. //处理null值
  41. if (dif.Object2.Target == null && dif.Object2Value == null)
  42. {
  43. try
  44. {
  45. dynamic nullValueLogContent = new ExpandoObject();
  46. nullValueLogContent.AggregateRoot1 = aggregateRoot1;
  47. nullValueLogContent.AggregateRoot2 = aggregateRoot2;
  48. nullValueLogContent.Differences = compareObjs.Differences;
  49. }
  50. catch { }
  51. fieldValue = BsonNull.Value;
  52. IMongoUpdate update2 = MongoDB.Driver.Builders.Update.Set(fieldName, fieldValue);
  53. allChanges.Add(update2);
  54. continue;
  55. }
  56.  
  57. //原始类型或字符串直接使用对象
  58. //对象类型则转为.ToBsonDocument();
  59. if (dif.Object2.Target.GetType().IsPrimitive || dif.Object2.Target.GetType().Equals(typeof(string)) ||
  60. dif.Object2.Target.GetType().IsEnum)
  61. {
  62.  
  63. fieldValue = dif.Object2.Target == null ? BsonValue.Create(dif.OriginObject2) : BsonValue.Create(dif.Object2.Target);
  64. }
  65. else
  66. {
  67. //更新整个集合类
  68. if (dif.Object2.Target.GetType().GetInterface(typeof(IDictionary).FullName) != null
  69. || dif.Object2.Target.GetType().GetInterface(typeof(IList).FullName) != null)
  70. {
  71. fieldValue = BsonValue.Create(dif.OriginObject2);
  72. }
  73. else if (dif.Object2.Target.GetType() == typeof(DateTime))
  74. {
  75. fieldValue = dif.Object2.Target == null ? BsonDateTime.Create(dif.OriginObject2) : BsonDateTime.Create(dif.Object2.Target);
  76. }
  77. else
  78. {
  79. //处理普通的class类型
  80. //由于这里OriginObject2一定不会被释放(强引用),所以使用dif.Object2.Target或者dif.OriginObject2都可以
  81. fieldValue = BsonValue.Create(dif.Object2.Target.ToBsonDocument());
  82. }
  83. }
  84.  
  85. IMongoUpdate update = MongoDB.Driver.Builders.Update.Set(fieldName, fieldValue);
  86. allChanges.Add(update);
  87. }
  88.  
  89. //有更新才处理
  90. if (allChanges.Count > )
  91. {
  92. updates = MongoDB.Driver.Builders.Update.Combine(allChanges);
  93. collection.Update(query, updates);
  94. }
  95. foreach (IMongoUpdate up in allChangesForDelete)
  96. {
  97. collection.Update(query, up);
  98. }
  99. }

写一个类似Collection的泛型类,提供集合类操作,在操作末尾对对象的实例动态织入:

  1. /// <summary>
  2. /// 创建代理
  3. /// </summary>
  4. /// <param name="aggregateRoot"></param>
  5. /// <returns></returns>
  6. T_AggregateRoot CreateProxy(T_AggregateRoot aggregateRoot)
  7. {
  8. var aggregateRootType = aggregateRoot.GetType();
  9. var constructor = aggregateRootType.GetConstructors().OrderBy(c => c.GetParameters().Length).First();
  10. var parameters = constructor.GetParameters().Select(p => default(object)).ToArray();
  11. return (T_AggregateRoot)proxyGenerator.CreateClassProxyWithTarget(aggregateRootType, aggregateRoot, parameters, new DataInterceptor<T_AggregateRoot>(this.Update, this.Remove));
  12. }

最终,这一系列思路的产物就是一个聚合跟集合:

  1. /// <summary>
  2. /// 聚合根泛型集合类
  3. /// </summary>
  4. public class AggregateRootCollection<T_AggregateRoot> where T_AggregateRoot : class
  5. {
  6. ...
  7. }

然后用法类似这样:

  1. var persons = new AggregateRootCollection<Person>("TestDb");
  2. var personProxy = persons.Add(new Person("丁丁"));
  3. personProxy.ChangeName("丁丁2");

第一行实例化聚合跟集合,第二行用Add方法对新的实例进行动态织入返回代理,第三行就是神奇的执行方法后,状态的变化就立刻持久化了。

以上是这个轻量级领域驱动框架的大致介绍,目前还未发布到Github和nuget上,后续会一篇篇的更新它的实现原理。它适用于一些事务性不强的工程,让开发人员所有关注点就在业务逻辑上,告别持久化。

基于Mongodb的轻量级领域驱动框架(序)的更多相关文章

  1. 基于事件驱动的DDD领域驱动设计框架分享(附源代码)

    原文:基于事件驱动的DDD领域驱动设计框架分享(附源代码) 补充:现在再回过头来看这篇文章,感觉当初自己偏激了,呵呵.不过没有以前的我,怎么会有现在的我和现在的enode框架呢?发现自己进步了真好! ...

  2. Lind.DDD敏捷领域驱动框架~Lind.DDD各层介绍

    回到目录 Lind.DDD项目主要面向敏捷,快速开发,领域驱动等,对于它的分层也是能合并的合并,比之前大叔的框架分层更粗糙一些,或者说更大胆一些,在开发人员使用上,可能会感觉更方便了,更益使用了,这就 ...

  3. Lind.DDD敏捷领域驱动框架~介绍

    回到占占推荐博客索引 最近觉得自己的框架过于复杂,在实现开发使用中有些不爽,自己的朋友们也经常和我说,框架太麻烦了,要引用的类库太多:之前架构之所以这样设计,完全出于对职责分离和代码附复用的考虑,主要 ...

  4. .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

    .net core +codefirst(.net core 基础入门,适合这方面的小白阅读)   前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...

  5. 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

    前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已. 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂. 不过,这些文章对于那些初学者而 ...

  6. 基于ABP落地领域驱动设计-00.目录和小结

    <实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...

  7. (转)EntityFramework之领域驱动设计实践

    EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...

  8. EntityFramework之领域驱动设计实践

    EntityFramework之领域驱动设计实践 - 前言 EntityFramework之领域驱动设计实践 (一):从DataTable到EntityObject EntityFramework之领 ...

  9. DDD领域驱动理解

    在理解领域驱动的时候,网上很多大谈理论的文章,这种对于初学者不是太容易接受.根据我自己的学习经历,建议按照如下几个步骤学习: 粗略的看一遍领域驱动的理论,做到心中有形,知道领域驱动是什么,解决什么问题 ...

随机推荐

  1. Swift function how to return nil

    这两天在学习Stanford出品的iOS7的课程,这个课程去年也看过,但是看到第3课就不行了,满篇的OC,把人都搞晕了.这段时间因为要写个iOS的App,正好赶上了Swift问世,所以趁着这股劲继续学 ...

  2. scjp考试准备 - 5 - 重载和重写

    如下代码,在所指示的位置插入代码能够正常编译: class Alpha{ public void bar(int... x){}; public void bar(int x){}; } public ...

  3. netstat用法

    netstat - 显示网络连接,路由表,接口状态,伪装连接,网络链路信息和组播成员组. 总 netstat [address_family_options] [--tcp|-t] [--udp|-u ...

  4. Android实现Button事件的处理

    Android实现Button事件的处理 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 代码实现 首先是最基本的线性布局,给每个控件设立id值,以供代 ...

  5. vimium

    安装在chrome上的一个插件,可以实现chrome无鼠标无键盘操作. 事实上vimium就是提供了一系列的快捷键列表,所以只要熟悉了这些快捷键就可以方便使用了. 要查看快捷键列表,打开chrome, ...

  6. struts2标签详解

    struts2标签讲解 要使用Struts2的标签,只需要在JSP页面添加如下一行定义即可:<%@ taglib prefix="s" uri="/struts-t ...

  7. 如何使用css和jquery控制文章标题字数?

    如何使用css控制文章标题字数? 最佳答案 控制文章标题字数,不是动态网页的专利,如果静态页面使用CSS样式,也可以实现相同的效果! 看这个例子,你们可以复制到记事本保存为HTML文件看效果! < ...

  8. quartz2D简单使用

    quartz2D绘图 1:上下文:context,这个翻译不好理解,其实翻译环境更好一点,就是给了你一个画板,你看不到而已 在: CGContextRef ctx = UIGraphicsGetCur ...

  9. 项目中用到的js日期函数

    <script type="text/javascript">    //替换字符串      function Replace(str, from, to) {    ...

  10. 【Binary Tree Zigzag Level Order Traversal】cpp

    题目: Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from lef ...