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

简介:

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

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

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

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

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

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

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

        public OperationResponse SendMessage(string title, string content, string senderLoginName, string receiverDisplayName)
        {
            using (IRepositoryContext repositoryContext = new EntityFrameworkRepositoryContext())
            {
                IContactRepository contactRepository = new ContactRepository();
                IMessageRepository messageRepository = new MessageRepository(repositoryContext);
                ISendMessageService sendSiteMessageService = new SendSiteMessageService();

                Contact sender = contactRepository.GetContactByLoginName(senderLoginName);
                if (sender == null)
                {
                    return OperationResponse.Error("抱歉!发送失败!错误:发件人不存在");
                }
                Contact receiver = contactRepository.GetContactByDisplayName(receiverDisplayName);
                if (receiver == null)
                {
                    return OperationResponse.Error("抱歉!发送失败!错误:收件人不存在");
                }
                try
                {
                    Message message = new Message(title, content, sender, receiver);
                    if (messageRepository.GetMessageCountByIP(System.Web.HttpContext.Current.Request.UserHostAddress) > 100)
                    {
                        return OperationResponse.Error("一天内只能发送100条短消息");
                    }
                    if (messageRepository.GetOutboxCountBySender(sender) > 20)
                    {
                        return OperationResponse.Error("1小时内只能向20个不同的用户发送短消息");
                    }
                    if (sendSiteMessageService.SendMessage(message))
                    {
                        messageRepository.Add(message);
                        return OperationResponse.Success("发送成功");
                    }
                    else
                    {
                        return OperationResponse.Error("发送失败");
                    }
                }
                catch (Exception ex)
                {
                    if (ex.GetType().Equals(typeof(ArgumentException)))
                    {
                        return OperationResponse.Error(ex.Message);
                    }
                    CNBlogs.Infrastructure.Logging.Logger.Default.Error("Application_Error: SendMessage", ex);
                    throw ex;
                }
            }
        }

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

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

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

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

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

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

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

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

写在最后

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


本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/3934412.html,如需转载请自行联系原作者

相关文章
|
11月前
新闻发布项目——业务逻辑层(categoryTBService)
新闻发布项目——业务逻辑层(categoryTBService)
29 0
|
11月前
新闻发布项目——业务逻辑层(newsTbService)
新闻发布项目——业务逻辑层(newsTbService)
21 0
|
11月前
新闻发布项目——业务逻辑层(UserService)
新闻发布项目——业务逻辑层(UserService)
32 0
|
11月前
新闻发布项目——业务逻辑层(categoryTBServiceImpl)
新闻发布项目——业务逻辑层(categoryTBServiceImpl)
20 0
|
11月前
新闻发布项目——业务逻辑层(commentServiceImpl)
新闻发布项目——业务逻辑层(commentServiceImpl)
33 0
|
11月前
新闻发布项目——业务逻辑层(newsTbServiceImpl)
新闻发布项目——业务逻辑层(newsTbServiceImpl)
19 0
|
11月前
新闻发布项目——业务逻辑层(commentService)
新闻发布项目——业务逻辑层(commentService)
27 0
|
11月前
新闻发布项目——业务逻辑层(UserServiceImpl)
新闻发布项目——业务逻辑层(UserServiceImpl)
47 0
|
设计模式 SQL 缓存
|
存储 弹性计算 缓存
JavaWeb开发经验谈:业务行为相似DAO接口的统一封装与使用
以对mybatis-plus的封装使用为例,给出一个在适用业务场景下大幅降低代码信息熵的解决方案
JavaWeb开发经验谈:业务行为相似DAO接口的统一封装与使用