写在前面

关于“Repository 仓储,你的归宿究竟在哪?”这个系列,本来是想写个上下篇,但是现在觉得,很有多东西需要明确,我也不知道接下来会写多少篇,所以上一篇的标题就改成了《Repository 仓储,你的归宿究竟在哪?(一)-仓储的概念》,在这篇博文中,主要讲了仓储的概念,并没有探讨有关仓储归宿的任何东西,但你发现,后面评论中的探讨会比博文内容更有价值,这也是我所坚持写博文的目的之一,也就是分享的价值。

上一篇博文评论中,大部分内容是我和 czcz1024 探讨“Specification-规约”,我也不知道怎么会扯到这个话题上了,就像 netfocus 兄最后所说:有点偏离主题了。确实如此,本来我觉得这一篇博文也就这样了,但是最后刘标才给我回复:

我想问下,领域模型里面到底要不要去引用仓储接口呢,好像ddd那个关系图里面是可以调用的,如果实体不引用的话,一些复杂的逻辑在实体里面根本无法实现,或者只能被分割成2部分,一部分在实体,一部分在appcation中,appcation中的一般是查询方法;比如有一个登记的业务方法(其实就是insert了),在登记前需要判断当前年份的金额是否大于登记实体的金额,大于那么可以登记,否则异常,类似这样的业务,判断查询那个到底是在实体里面判断还是在appcation里面判断呢,如果是实体的话,那么仓储接口怎么注入到实体,实体是new出来的????

如果不嫌多,我再贴出一段:

这个问题我已经纠结很久了,还没有看到具体是怎么样实现的,stackoverflow里面也有很多老外在讨论,但也没有最终的答案,大部分人认为没有必要在实体或者聚合中使用仓储(或者注入仓储);我个人认为是一定要的,先不说这样做对不对,我们可以根据项目的实践去看问题,很多业务都需要判断一下是否唯一,面积或者金额之类的是否足够,这些都是需要查询才可以得到值的,如果是放在application去实现,就等于把这些业务转移到了上层,而且随着业务的复杂度增加,这样的实现就会很多,如果放在领域层实现,看起来是很不错的选择,但是实体或者聚合里面怎么去注入仓储接口呢,实体和聚合都是被new出来的,看起来是没有办法注入了,只能在实体里面调用XXX注入类.GetInstance<仓储接口>();这样的话领域层就要依赖某个注入框架了,所以这个仓储问题,不管放哪都不那么完美。

我当时看到这段回复的时候,我觉得我找到知音了,为什么?因为只有实践过,你才会感同身受,大道理说一大堆,不去实践应用,这种问题你不会发现的,更不会去思考怎么解决?

我为什么认死理,非要探讨仓储的归宿?

其实关于这个问题,netfocus 兄看到,应该会非常无语(哈哈),因为早在《设计窘境:来自 Repository 的一丝线索,Domain Model 再重新设计》这篇博文中,我和他就曾探讨过,当然还有之后其他的一些交流,他的意思是:为什么非要纠结在仓储这一块?如果职责划分的比较明确及正确,那该怎么使用就怎么使用,不管是应用层或是领域中,只要符合,那它就是正确的。探讨的问题是“领域服务中能不能使用仓储?”,这个也是我之前一直纠结的地方,其实 netfocus 兄的意思我都懂得,这也是仓储设计的大前提之一,那就是职责或边界划分清楚。

后来 Luminji 兄发表了一篇博文《面向对象架构模式之:领域模型(Domain Model)》,看完博文内容,再看评论,你会觉得这完全没有相干性(还是有一点的),评论中主要探讨的还是仓储的归宿(调用问题),但是到评论结束,还是没有一个准确的结论,为什么?因为大家都没有去实践应用,也就是没有针对一个具体的业务场景进行探讨,比如针对某一个业务用例,把仓储的归宿放在领域服务中,那这个仓储具体该怎么设计实现?怎么调用?怎么配合领域服务完成一个具体的业务用例?应用层的代码又该是怎样的?IOC 容器怎么去注入?。。。虽然是一个“很小”的问题,实践应用过后,你会发现,其实这是一个很大的问题,当然前提是,你要去实践,去应用。

最近,Jesse Liu 兄在小组中发布了一个话题《讨论一下领域驱动设计》,我觉得这种探讨非常棒,因为大家都是针对同一个具体的业务用例,而不是各个不同的业务用例,而且这种探讨会让你学到,别人在这种业务用例下是怎么进行领域驱动设计的?不自觉会纠正你的一些错误观点,当然前提是,你不是偏执的人。

以上我所叙述的一些东西,我个人觉得都是停留在理论阶段,就像 Jesse 兄的那个话题,如果针对购物车这个业务用例,接下来的设计会是怎样?因为之前的探讨内容都是职责和边界,其实并没有去实践与应用,如果实践了,你会发现这其中的一些其他问题,“仓储的归宿”,只不过是这些问题的其中之一。

这样的应用层代码,你能接受吗?

言归正题,关于“仓储,你的归宿究竟在哪?”这个问题,这篇博文我想晒一下,我现在应用层的代码,业务场景还是短消息系统,业务用例是发送短消息,代码如下:

  1. public OperationResponse SendMessage(string title, string content, string senderLoginName, string receiverDisplayName)
  2. {
  3. using (IRepositoryContext repositoryContext = new EntityFrameworkRepositoryContext())
  4. {
  5. IContactRepository contactRepository = new ContactRepository();
  6. IMessageRepository messageRepository = new MessageRepository(repositoryContext);
  7. ISendMessageService sendSiteMessageService = new SendSiteMessageService();
  8. Contact sender = contactRepository.GetContactByLoginName(senderLoginName);
  9. if (sender == null)
  10. {
  11. return OperationResponse.Error("抱歉!发送失败!错误:发件人不存在");
  12. }
  13. Contact receiver = contactRepository.GetContactByDisplayName(receiverDisplayName);
  14. if (receiver == null)
  15. {
  16. return OperationResponse.Error("抱歉!发送失败!错误:收件人不存在");
  17. }
  18. try
  19. {
  20. Message message = new Message(title, content, sender, receiver);
  21. if (messageRepository.GetMessageCountByIP(System.Web.HttpContext.Current.Request.UserHostAddress) > 100)
  22. {
  23. return OperationResponse.Error("一天内只能发送100条短消息");
  24. }
  25. if (messageRepository.GetOutboxCountBySender(sender) > 20)
  26. {
  27. return OperationResponse.Error("1小时内只能向20个不同的用户发送短消息");
  28. }
  29. if (sendSiteMessageService.SendMessage(message))
  30. {
  31. messageRepository.Add(message);
  32. return OperationResponse.Success("发送成功");
  33. }
  34. else
  35. {
  36. return OperationResponse.Error("发送失败");
  37. }
  38. }
  39. catch (Exception ex)
  40. {
  41. if (ex.GetType().Equals(typeof(ArgumentException)))
  42. {
  43. return OperationResponse.Error(ex.Message);
  44. }
  45. CNBlogs.Infrastructure.Logging.Logger.Default.Error("Application_Error: SendMessage", ex);
  46. throw ex;
  47. }
  48. }
  49. }

Are you kidding me?没错,你没看错,这就是现在短消息项目中应用层中的一段代码,对于 DDD 的狂热爱好者来说,我觉得他们看到这段代码,肯定会抓狂的。。。

虽然短短几行的代码,但这其中所暴露出来的问题,实在太多了(比如仓储上下文设计、自定义异常处理、仓储的定义等等),其实我觉得你最不能接受的应该是,中间那两个发送消息之前的业务验证:

  1. 一天内只能发送100条短消息。
  2. 1小时内只能向20个不同的用户发送短消息。

这个是属于业务规则,怎么会放在应用层?难道我脑袋锈掉了?当然没有,这个我原来是想放在 SendSiteMessageService 领域服务中的,但是我原来的设计是领域服务中不进行仓储的调用(为了保持领域的纯洁),包含业务用例描述,所以,针对这两个业务验证,是没办法放在领域服务中的,因为这种涉及到到领域对象的读取,而所有的领域对象读取接口都设计在仓储中,领域服务想进行业务验证,又不想进行领域对象读取,你觉得可能吗?

其实这种问题,有两种解决方案:

  1. SendSiteMessageService 领域服务中实现仓储的调用。
  2. 领域对象的读取放在应用层中,获取之后交由领域服务进行验证。

我个人觉得,第二种实现方式只能针对一定的业务场景下,如果在业务验证过程中,又涉及到领域对象的读取,这个实现方式就有点不合理了,而且获取领域对象的操作,其实也是业务的一种体现。

代码设计是一方面,代码重构又是另一方面,后一个过程要比前一个过程困难百倍。

写在最后

这篇博文,我不希望写的太长,核心内容就是那段应用层中的代码,我知道兄台你已经发现问题了,那就请兄台大声的说出来吧。

领域驱动设计中,我再列一下有关仓储的一些探讨博文:

Repository 仓储,你的归宿究竟在哪?(二)-这样的应用层代码,你能接受吗?的更多相关文章

  1. Repository 仓储,你的归宿究竟在哪?(三)-SELECT 某某某。。。

    写在前面 首先,本篇博文主要包含两个主题: 领域服务中使用仓储 SELECT 某某某(有点晕?请看下面.) 上一篇:Repository 仓储,你的归宿究竟在哪?(二)-这样的应用层代码,你能接受吗? ...

  2. Repository 仓储,你的归宿究竟在哪?(一)-仓储的概念

    写在前面 写这篇博文的灵感来自<如何开始DDD(完)>,很感谢young.han兄这几天的坚持,陆陆续续写了几篇有关于领域驱动设计的博文,让园中再次刮了一阵"DDD探讨风&quo ...

  3. Repository 仓储,你的归宿究竟在哪?(上)

    Repository 仓储,你的归宿究竟在哪?(上) 写在前面 写这篇博文的灵感来自<如何开始DDD(完)>,很感谢young.han兄这几天的坚持,陆陆续续写了几篇有关于领域驱动设计的博 ...

  4. Repository 仓储

    Repository 仓储 写在前面 首先,本篇博文主要包含两个主题: 领域服务中使用仓储 SELECT 某某某(有点晕?请看下面.) 上一篇:Repository 仓储,你的归宿究竟在哪?(二)-这 ...

  5. Repository仓储 UnitofWork

    Repository仓储 UnitofWork 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 本章我们来创建仓储类Repository 并且引入 UnitOfWork 我对 ...

  6. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理三 (二十一)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  7. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理四 (二十二)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  8. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理五 (二十三)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  9. abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理六(二十四)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

随机推荐

  1. AOJ 0033 Ball【DFS】

    有一个筒,从A口可以放球,放进去的球可通过挡板DE使其掉进B管或C管里,现有带1-10标号的球按给定顺序从A口放入,问是否有一种控制挡板的策略可以使B管和C管中的球从下往上标号递增. 输入: 第一行输 ...

  2. mysql 列类型

  3. [算法总结]partition (quicksort)

    private int partition(int[] nums, int lo, int hi) { if (lo >= hi) { return lo; } int i = lo; int ...

  4. 转:工具类之SpannableStringUtils(相信你会爱上它)

    这个工具类真是构思了良久才设计出来,采用了建造者模式,然后你们就可以用链式调用了,talk is cheap, let me show the demo. demo code 有没有心动一下哈,下面就 ...

  5. Linux学习笔记(8)-exec族函数

    昨天学习了Linux下的进程创建,创建一个进程的方法极为简单,只需要调用fork函数就可以创建出一个进程,但是-- 介绍fork()函数的时候提到,在创建进程后,子进程与父进程有相同的代码空间,执行的 ...

  6. 将Unreal4打包后的工程嵌入到Qt或者桌面中

    #include "widget.h" #include "ui_widget.h" #include "windows.h" #inclu ...

  7. Docker学习笔记第一章:补充

    只记得学习后面的命令,忘记整理一些概念性的东西了,只能做个补充了=.= Docker虽然也是一种虚拟技术,但是不同于虚拟机的概念.Docker是一种以容器为主的技术,容器运行不需要模拟层(emulat ...

  8. 配置python环境变量(转)

    默认情况下,在windows下安装python之后,系统并不会自动添加相应的环境变量.此时不能在命令行直接使用python命令. 1.首先需要在系统中注册python环境变量:假设python的安装路 ...

  9. 用Node.js发送邮件

    本文讲的是用Node.js通过一个开启smtp的已有的邮箱账号发送邮件,而不是如何创建一个邮件服务器 开启smtp服务 首先要去要使用的邮箱中设置开启smtp,才能正常发送邮件 这边以163邮箱为例 ...

  10. FTP命令 - Size的问题

    今天发现一个服务从某一个外接系统(Linux)FTP取到的文件大小和下载后的文件大小总是不一致. 开始以为是程序那里出错了.但是找来找去发现不了原因.后来用FTP工具上去执行SIZE命令,终于发现返回 ...