1. 传统建模与DDD建模
1.1. E-R数据建模&面向对象建模
- E-R数据建模
ER数据建模法是在接收到需求以后直接开始数据表ER模型的设计、实体表和表关系的设计。ER模型往往依赖于数据库技术,甚至与后者非常紧密地耦合在一起,虽然带来了效率的提升,但是高效率不代表高质量,而软件高质量却能带来高效率。
ER数据建模虽然也有一套分析设计方法论,但是由于过于注重数据库技术而忽视了业务上下文。CRUD是“增删查改”的简称,如果使用CRUD用语替代业务用语,例如使用“创建订单”替代“下单”,使用“创建帖子”替代“发帖”,使用“创建发票”替代“开票”等,虽然也容易让人明白,但是“下单”等用语才是真正的业务术语、业务行话,是这个行业内每个人都知晓的。作为软件系统不是去遵循这些用语习惯,而是进行转换改造,按照自己的理解生造出一些词,会潜移默化地将业务需求引导到不同方向。
- 面向对象建模
将业务行为放入“对象或类”中,这样更能精确反映领域概念,保证业务规则的真正逻辑一致地实现,这是面向对象分析和设计(OOAD)的主要优点之一。
但是,开发人员往往是按照产品设计人员进行业务目标的“简单翻译”,因此可能在实现过程中产生绕路和不必要的复杂性,甚至走错方向,南辕北辙
二者区别:
E-R往往是一种数据表的呈现,本质上是数据结构;而对象和数据结构两者的区别之一就在于对数据的操作是主动还是被动——对象是主动操作数据,而数据结构的数据是被动操作,这一区别使得两种方式下的分析设计思路和编程范式完全不同。
从设计角度看,业务领域中的业务对象是定义业务行为的一种结构;数据表模式是定义业务数据的结构。它们一个注重业务的行为,另一个注重业务的数据,着重点不同,导致设计要求不同,也正是出于不同的设计要求才有对象和数据结构这两种不同的实现方式。
1.2. 领域驱动设计
- DDD是解决复杂问题的方法论
DDD是专门解决复杂性的方法论,是复杂问题的解决之道。首先,当需求规模比较大时,需求内部之间可能会发生矛盾,有些矛盾隐藏得非常深,可能只能通过代码实践才会被发现,但是这种代价非常高。当然,使用DDD建模并不是为了将整个系统预先设计出来,而只是让一些有丰富领域知识和逻辑思考能力的人通过头脑风暴发现系统的复杂核心所在。 PS:DDD中的事件风暴(EventStorming)建模倡导将脑袋大开的产品经理和严谨求证的程序员召集在一起进行头脑风暴。(后文会详细描述如何进行事件风暴)
复杂性无法消除,问题空间的复杂性是天然存在的,一个大型系统肯定比小型系统复杂得多。那么人们对这种客观存在的复杂性就无能为力了吗?当然不是,人们理解复杂性有自己的一套分析方法,如分门别类,逐步、有层次地分解。DDD关注的重点就是如何将复杂的问题空间通过逻辑分析解析出来,如同解析方程一样,从原来的混乱无序变得有条理、有层次,这样经过梳理的复杂性才是DDD需要的结果。由于分析结果变得有层次,相互隔离、松耦合,就能分派不同的团队专门处理各个问题的子域或有界上下文,分而治之。
- DDD的副作用-可变状态管理(需要通过领域事件合成当前状态)
DDD实施中最大的副作用是可变状态的管理。DDD聚合根代表一个有界上下文的复杂核心概念,其复杂性来自聚合根实体的状态是经常可变的,例如订单的状态从支付变为发货,这种变化决定了整个系统的成败——如果一个订单没有支付,但是商家发货了,这样是不合理的。DDD虽然通过边界划分和状态封装固定了复杂的可变状态,但是如果不结合函数式编程,这种可变状态产生的全局影响是很难消除的。如果一个新手程序员不小心改变了订单状态,这个订单就变得无从追查,损失的是客户利益,而采取事件溯源等方式,直接记录发生的领域事件,并不改变状态,所有需要订单当前状态的有界上下文自己通过遍历这些领域事件来合成当前状态,这样的状态既是实时的又是准确的,也是可以追溯的。
- DDD与传统设计建模方式的差异(回归业务本身,关注动词而不是名词,合理边界划分)
DDD解决了传统面向对象分析和设计的割裂状态。面向对象分析的结果会被设计细节干扰,导致严重偏离分析方向,DDD是一种“一竿子插到底”的方法,分析的结果必须经过设计细节验证。事件风暴倡导的是首先提取领域中发生的事件,从非常细节的动作入手来分析需求,而传统的面向对象分析方法则是主语名词法,首先寻找领域中有哪些实体名词,这种分析方法其实受到了ER数据模型的影响。主语名词法的严重问题是,如果人们无法发现主语名词,就只能替代以想象中的“上帝”了。
DDD分析方法的核心:从细节动词入手发现有界上下文和聚合,以逻辑一致性为边界划分依据,对动作实现分门别类地划分。
笔者理解,传统设计建模割裂了业务和具体的工程实现,可能在工程实现时带来额外的复杂性,同时可能影响业务往不同方向去。而DDD通过“事件风暴”连接业务和工程实现,一开始高屋建瓴地设计一个稳定的结构(DDD中的上下文概念或聚合概念),各个结构之间有准确的边界划分,彼此之间松耦合。最终做到业务完全的实现,工程准确描述业务,组织架构清晰,尽可能减少跨组协作(跨组协作成本高)。
2. 领域驱动设计的特点
2.1. 发现和理解问题
DDD提出了面向业务领域的软件设计,也就是以业务为驱动的设计
先要瞄准业务需求中的问题。问题空间是解决问题的目标所在,有了问题空间,才能提出解决方案,而解决方案的提出有赖于人们对问题知识的理解。
笔者理解,要理解业务需求的问题与解决方案。
2.2. 领域即边界
领域这个词语本身就带有“边界”的概念。
领域的边界有时是模糊的,它是与该事物所处的上下文相关的:比如在科学这个上下文中,番茄属于水果;在烹饪这个上下文中,番茄属于蔬菜。
两种发现边界的方式:一种是通过客观事物的自然边界来发现,也就是事物自身(内部)的强烈结构特征所显示的边界;还有一种是通过客观事物所处的(外部的)上下文环境发现其边界。
总结:领域即边界,边界靠分类,分类需要从内外部入手。
2.3. 解决复杂性(Complicated)
解决复杂性的两种方法是:拆解成松耦合的组件+使用容易让人明白的套路表达出来。
- DDD如何实现:
DDD通过引入“领域或子域”以及“有界上下文”来划分边界,边界一旦划分好,拆解的第一步就能完成;其次,DDD引入各种模式名词,比如聚合、实体、值对象、工厂、仓储、领域事件,让知晓这些模式的人能够一下子定位到功能对应实现的组件。
- 如何判断业务是否复杂
- 系统是否有类似于CRUD的接口,是否由领域专家以CRUD术语描述?如果是,则代表简单。
- 业务逻辑是否围绕输入验证?如果业务规则只是对输入进行验证,没有自己独特的业务规则验证,则属于简单。
- 有复杂的算法和计算吗?很显然,,如果有,就属于复杂了。
- 是否有应该执行的业务规则和不变量?拥有系统自己的业务规则,这种业务规则是为了实现业务战略的,并且通过复杂的流程来保证,很显然比较复杂。
- 是否有复杂的If…else判断?结果代码的条件复杂度是什么?它有许多不同的执行方案吗?如果是,则属于复杂;如果这种判断影响全局,那就属于更复杂了。
这里补充一个Complex和Complicated的区别的文章: https://medium.com/vincent-chen/complex-and-complicated語意有何差別-b6fae7f8519b
2.4. 新的数据结构设计方式
DDD中的领域事件集合等同于ER中的明细表,明细表(事件)是造成主表(聚合根)变动的原因。
例如,如果个人账户余额初始是100元,今天进账30元,出账消费20元,那么个人账户的余额就是100+3020=110元,状态值从100元变成了110元。从ER数据库角度看:进账30元和出账20元属于进出明细,从DDD领域事件角度看,它们属于发生的两次事件,发生了进账30元事件和出账20元事件。
2.5. 需要注重产品的程序员(The Product-Minded Software Engineer)
Uber工程师GergelyOrosz认为注重产品的程序员应具有9个特征。
- 积极参与产品构想/意见。
- 对业务、用户行为和有关数据感兴趣。
- 具有好奇心和对“为什么”的浓厚兴趣。
- 是较强的沟通者,与非工程师保持良好关系。
- 预先提供产品/工程权衡,因为他们对产品“为什么”以及工程方面有深刻的了解,所以他们可以提出很少有人可以提出的建议。
- 边缘案例的务实处理。
- 注重快速的产品验证。
- 当某个功能的性能比预期的差时,他们会很好地了解不匹配的位置,并希望找到在产品计划和实际结果之间出现差距的根本原因,就像调试代码库中难以重现的错误一样。
- 通过反复学习来增强对产品的直觉。
- 为项目提供产品/工程权衡。
- 要求产品经理经常提供反馈。
3. 领域驱动设计的难点
3.1. 业务策略和业务规则
现在用一个例子来说明。看看下面的三句陈述。
- 所有男人都是人。
- 苏格拉底是个男人。
- 苏格拉底是人。最后一句可以从前两句中推断出来。
在企业业务领域中,业务策略类似第一句陈述,业务规则类似第二句陈述,而业务流程类似第三句陈述。
业务策略代表一种企业业务战略,而业务规则属于业务战术,如果自己创业开一家公司,会为这家公司制定一个战略目标,或者说这家公司的商业模式是什么?有了商业模式,才需要探索实现这个战略目标的多种途径,这些可落地执行的途径就是业务规则。
业务规则是业务策略的具体实现。
业务策略的特点如下:
- 是不具操作性的指令。
- 通常要求员工翻译成具体的业务规则。
- 支持业务目标。
- 由一个或多个业务规则支持。
业务规则的特点如下:
- 可操作。
- 具体性。
- 可测试。
- 用于支持策略的实现。
业务流程为了实现业务规则,因此可以通过业务流程去发现业务规则。业务流程是每个企业管理和运作中最复杂的部分,也是进行信息化的主要目标,甚至有各种流程可视化管理工具,分析师只要画画流程图(符合BPMN标准),流程工具软件就可以将BPMN流程图自动生成代码并运行起来。
3.2. 统一语言和有界上下文
技术人员和产品经历之间可能会存在沟通上的属于差异,需要统一业务术语。
统一业务术语不是编制一张词汇表就可以了,术语名词还取决于不同上下文,如上面的行李数量有可能在装载人员系统里就改为了“行李箱”,类似地方方言,这些都和特定上下文有关。也可能是这种复杂性导致人们的语言无法真正统一,如果发生这种不一致,就需要进行上下文中词语的翻译映射,将其明显标注出来。
3.3. 领域模型的提炼
提炼概念或模型遵循从事物内外部入手的原则,需要明确问题空间,发现其中的复杂性,将最复杂的部分圈起来形成核心攻关领域,发现统一语言和其所处的有界上下文,根据这两者提炼出领域模型。
一般业务规则是通过业务流程显现出来的,那么只要检查上下文中是否有业务流程就能触摸到业务规则。
可以用反问法发现业务规则:如果没有这些流程、事件、步骤是不是也可以?领域专家会解释如果没有它们会产生什么后果(规则是用来防范坏的结果的),这样就离业务规则越来越近了。
明确业务规则以后,可以说已经深入领域的复杂性核心,这时就可以提炼出概念模型了。提炼模型的要点如下。
- 简单准确。模型是对真实事物的简化理解,注意力需要集中在内部要点上。
- 通用统一。语言必须足够通用,应该是业务人员的务实术语,能够提供人们在谈论系统时可以使用的统一语言。
- 逻辑严格。逻辑上必须足够严格,从而可以成为编写代码的基础。