了解一下 DDD 领域驱动设计
为什么需要领域驱动
在一个项目的生命周期中,随着项目需求的变动,我们需要不断地改变、扩展项目中软件的功能。在之前的文章《如何阻止软件退化》我们了解到,如果在不改变原有功能框架的前提下,对功能进行直接的添加,那么软件会虽然功能的增加很快就被腐化。这是因为,我们在最开始进行框架设计的时候,是根据最初提出的客观世界规律来设计的,而并没有根据新的、更细节的客观规律进行设计。而为了避免这种腐化,我们需要在进行新的功能开发前,先对原有功能根据新的需求进行结构上的调整,然后在这个基础上在进行新功能的编写,也就是我们的前文的“两步走”。
但是这个结论看似正确,但其实只是一个理想化的结论。因为这个结论是有假设前提的,它的前提是:
我们了解,并清楚前后需求中的客观规律的关联性。
为了说明这个假设,我们以前文中的支付作为例子。我们再回顾一下前后几次的变更:
针对支付价格增加了钱款计算的分支逻辑
针对人员信息增加了人员身份获取逻辑
针对支付价格增加了支付路由判断逻辑
我们能从新增的能力中清晰地判断出前后的关联关系,但并不是所有的改动都有这样清晰的关联关系。当进行了一次变更、两次三次变更的时候关系可能还比较明确,但如果经历了 30 次、40 次变更之后。究竟这个调整内容还是之前的关联逻辑吗(忒修斯之项目)。如果逻辑的边界变得模糊,我们又如何保证对于框架的调整是符合客观逻辑的呢?
那么,领域驱动就是为了解决这个问题而提出的。
软件是为了还原客观世界的逻辑规律,而产品需求则是提炼后的部分客观世界的规律。尽管客观世界逻辑太过于复杂无法直接实现,但是我们却可以直接参考客观世界的规律,以验证项目中逻辑的正确性,从而判断是否和产品需求要求一致。而为了可以和客观世界进行参考,就需要项目中的模型与真实世界一一对应,那么:
我们称这种项目与客观世界中一一对应的模型就是领域。
如果直接参考真实世界的客观规律,所以每一次调整完毕框架逻辑后,可以直接根据客观世界进行验证,而不用比较项目调整前后的关联性。所以:
我们需要领域驱动,因为他帮我们解决了大量迭代后项目逻辑框架与客观世界逻辑失真(由于累积的调整误差)的问题。
领域的映射
我们需要将客观世界中的逻辑映射到软件项目中,那么他们必然需要一种映射的逻辑:
客观世界的物体 -> 软件项目的对象
客观世界的行为 -> 软件项目的方法
客观世界的关系 -> 软件项目的关联
而这些客观规律的映射,就组成了我们所说的领域模型。(我们可以从中看出,领域模型是面向对象的又一次升级。)
当我们有了一个领域模型之后,因为领域模型所描述的是客观世界规律到软件中,所以我们可以根据领域模型的变更来指导程序设计。
所以,当我们有一个新的需求变更的时候,第一步就变成了先将需求通过领域模型分析。如果领域模型原本原本的客观规律不具备这种能力,则先维护领域模型(例如新增属性、新增行为)。在完成了领域模型的维护后,就可以根据维护后的领域模型来指导软件程序的变更。从而让我们整体软件的设计质量得到提高。
有人可能会说,自己在进行开发的时候也是根据这样的思路进行开发的呀,那我平时就是在用领域模型设计吗?其实,领域模型设计这种解除耦合的思想会与我们在进行开发时的抽象设计不谋而合。但主要的区别在于,领域模型设计因为出发点是客观世界,所以保证了客观直接和需求的一致性,这个基础上指导程序开发,就保证了软件和需求的一致性。而开发人员直接根据需求进行的软件抽象,无法保证这种一致性,所以当多次功能调整后,就会出现软件与客观规律产生失真,软件产生了很多自己的“规则”,而直接的结果就是用户觉得软件“不好用”。
支付的领域模型设计
还是之前的例子,没办法,就这个例子熟悉。
拿出来最开始的伪代码:
查询人员计算付款信息返回支付用路由信息
这个支付流程整体看起来就不是很难,那么那的需求描述可能是这个样子的:
用户进行下单,调用支付功能。
查询用户的信息比如名称、地址。
计算商品待支付金额
生成订单
调用第三方支付进行准备让用户支付
在需求中可能直接描述了一个订单的支付流程,而如果直接根据需求进行开发的话,就是咱们的第一版本的伪代码了。还是上文中所说的内容,这样的方式不是不可以,但是他的问题就在于对于后续不断迭代的过程是有挑战性的。
那么如果采用领域模型的设计是什么样的流程呢?根据上一小节的描述,我们应该先针对需求进行分析,然后根据需求中的内容进行领域建模。
那么从需求中我们可以看到:
根据需求中的名词,我们发现有订单、用户、用户地址、订单明细、商品
而虽然没有明说,但是我们根据客观规律以及产品沟通可以得到的关系有:一个订单对应一个用户、一个用户对应多个地址、一个订单对应一个地址、一个订单对应多个订单明细以及一个订单明细对应一个商品。
而根据支付的流程,我们还发现订单需要有下单、付款等功能,而用户需要有增删改等功能。
那么根据以上的分析,我们可以得到以下比较简单的模型:
那么根据这个领域模型,我们就可以将模型的行为抽出出来形成方法,而将方法的聚合组合为服务。而原来模型中的属性就可以转化为对应的值对象。也就可以形成了如下的设计:
我们可以看到,根据领域模型抽取之后的服务、对象的组合就比较像我们平时进行设计的值对象与服务的关系了。
支付的领域模型变更
那么有了第一版本的设计之后,我们得到了一部分完整的功能代码。那么我们现在有了新的需求:加入付款时的折扣功能:优惠价、限时折扣、原价。
根据前文的描述,我们应该先将需求的调整在领域模型上进行实现。那么针对付款的这个动作自然是在订单的付款()里进行调整。但是如果直接在付款的方法中进行调整的话,就变成了前文中的“问题设计”了。
在进行领域设计之前,我们首先要判断的是受到影响的元素之间的关系:“付款”与“折扣”。简单来看,折扣是在付款流程中执行的,所以折扣似乎应该放到付款里。但是依据单一职责原则(一个职责就是软件变化的原因),进行后续不同折扣功能以及付款功能调整的时候他们不应该互相影响,所以他们应该被拆解为不同的类。那么基于这个思想我们就可以将领域模型调整为以下形式(只画了订单部分的):
那么根据该模型进行项目的设计:
我们可以看到,对应在领域建模中的折扣接口,就指导了我们在项目设计的时候将其以“策略模式”进行实现,从而在增加不同折扣功能的同时,也保证各个模块满足单一职责原则。其他的需求变更本文就暂不展开。
最后
领域驱动设计是一种设计的思想,即便在不了解这种思想的时候,开发同学得出的方案也可能与其相同。但是领域驱动设计的意义在于,提供了一种“标准”的思维模式,让你可以将思考的流程标准化,而不是同时考虑过多问题。以本文中的例子为例,就是先考虑模型间的关系,再考虑针对模型的服务实现。当然,领域驱动设计在微服务
设计、数据库设计等方面也有不可忽视的指导性支撑,会在后续的文章中进行讨论。
版权声明: 本文为 InfoQ 作者【蜜糖的代码注释】的原创文章。
原文链接:【http://xie.infoq.cn/article/f764d2722bb98c80651687f1e】。文章转载请联系作者。
评论