
1.3 领域驱动设计的难点
前面阐述的DDD的几个特点可能恰好是DDD的难点所在。
相比数据库表的设计方法,DDD综合了OOAD的优点,继承了逻辑分析方法学的特点,从事物内外部分别入手探索事物的边界,定位事物存在的意义。使用这套方式能够将复杂事物像剥洋葱一样层层分解成一个个小组件,当然,这样做也存在问题,即学习和掌握这套方法有一定的难度。幸运的是,这套方式是人们分析任何问题的通用方式,并不是什么奇技淫巧,学习可能是因缺乏阅历而导致的。
1.3.1 业务策略和业务规则
发现问题空间中的业务策略和业务规则是DDD瞄准的目标,那么什么是业务策略?什么是业务规则?它们和业务流程有什么关系?
首先,为什么要定位业务策略与规则?因为业务策略与规则是领域系统内的第一原则。
特斯拉创始人马斯克使用“第一原则”进行思考,从零开始设计廉价火箭,同时也彻底改变了电动汽车行业。第一原则是一个基本的、不言而喻的命题或假设,不能从任何其他命题或假设推导出来。
在数学和逻辑学中,第一原则是一个公理,公理是不能从该系统中的任何其他公式推导出来的。所有的定理都是从几条公理中推导出来的,因此寻找业务策略和规则就是寻找公理、寻找第一原则。
现在用一个例子来说明。看看下面的三句陈述。
1)所有男人都是人。
2)苏格拉底是个男人。
3)苏格拉底是人。
最后一句可以从前两句中推断出来。在这个系统中,第一原则是前两个句子。
在企业业务领域中,业务策略类似第一句陈述,业务规则类似第二句陈述,而业务流程类似第三句陈述。业务流程不是第一原则,但却是进入业务领域后容易接触和发现的,属于表象部分。
业务策略代表一种企业业务战略,而业务规则属于业务战术,如果自己创业开一家公司,会为这家公司制定一个战略目标,或者说这家公司的商业模式是什么?有了商业模式,才需要探索实现这个战略目标的多种途径,这些可落地执行的途径就是业务规则。
业务规则是业务策略的具体实现,这是两者的区别。业务策略的特点如下。
1)是不具操作性的指令。
2)通常要求员工翻译成具体的业务规则。
3)支持业务目标。
4)由一个或多个业务规则支持。
而业务规则的特点如下。
1)可操作。
2)具体性。
3)可测试。
4)用于支持策略的实现。
例如,租车公司的业务策略,从老板的角度看包括延长车辆的寿命和给租车者购买各种保险(当然是转嫁到租车人身上),这两个简单的业务策略实际概括了租车公司的经营目标,那么业务规则就是贯彻执行这两个业务策略的具体措施,如所有车辆都要三个月保养一次等这些带有具体数字的约束就成了业务规则,很显然,业务规则非常适合软件系统精确实现。
图1-6所示为对领域问题空间的分类划层。

图1-6 问题空间分类划层
在这四个层中,业务策略属于顶层,决定了领域方向,是核心竞争力的体现,而业务规则是贯彻执行业务策略的细化层,起承上启下的作用,能够指导运作逻辑层进行实操,是业务流程实现所在,也是软件具体介入的地方,是计算机语言代码运行的层。最后一个监控层对上层进行监控稽核或财务管控,保证资金流、信息流的安全可靠。
理解和发现问题空间中的业务策略和业务规则非常重要,这是产品经理或公司战略的主要核心。举一个例子:如果为Shopify这家公司设计软件,首先需要了解它是干什么的,是不是一家类似京东、淘宝这样的普通电子商务公司。经过了解知道Shopify是一个运行于开店店主、物流系统和最终网购用户之间的平台,通过整合第三方物流公司(称为“第三方物流”)提供仓储和运输服务为商家和用户之间提供价值最大化的服务。Shopify所做的是平台最擅长的部分,即作为价值链中两个模块化部分之间的接口。显然,这个接口是Shopify的核心竞争力和业务策略,具体的业务规则可能落实为为店主提供最方便灵活的网上开店工具和插件;集中采购第三方物流公司的运费,与多个第三方物流进行协商谈判,以便他们的库存能为客户提供更快速廉价的交付,这些业务规则最后都要通过API接口形式为商家提供服务。
制定业务战略是老板的事情,而落实和发现业务规则不只是公司管理人员的职责,也需要产品经理或业务分析师参与其中。这里介绍一下“业务分析”这门学科。业务分析是面向业务的一套分析学科,业务分析作为一种实践,通过战略分析、需求分析以及与利益相关者合作定义业务需求,从而有助于促进组织变革。业务分析需要确定业务问题的解决方案。解决方案通常包括软件系统开发组件,但也可能包括流程改进、组织变更或战略规划和政策制定。执行此任务的人称为业务分析师或BA。
四种类型的业务分析如下。
1)业务识别:识别组织的业务需求和业务机会。
2)业务模型分析:定义组织的政策和市场方法。
3)流程设计:标准化组织的工作流。
4)系统分析:技术系统业务规则和要求的解释(通常在IT内部)。
其中业务模型的分析、流程设计等都属于业务规则的计划和制订,最终落实到软件系统可执行的业务逻辑。
从另外一个方面看,如果从公司商业模式和业务战略角度去发现业务策略和业务规则,就需要专门人员、专门知识和专门工具。而有些公司是一种服务类组织,专门接受客户或上级委托,编排计划、安排任务、跟踪流程,这类公司中的业务策略和业务规则比较容易发现。下面举一个详细例子说明策略和规则的发现以及分类对于提炼领域模型的好处。
这是一个货运车队调度的案例,其需求表达为图1-7所示的用例图。

图1-7 货运车队调度用例图
一级调度员根据客户委托的运输路线图进行行程计划调度;二级调度主要是规划具体车辆的某个行程段,跟踪车辆状态。
如果从业务策略和业务规则两个角度看,客户的要求属于业务策略,指定了运输公司为其服务的目标。客户委托一批货物给运输公司运输,运输要求是从出发地到目的地,这是一种路线上的要求,双方会根据这种运输要求和运输的货物达成协议,确定运输价格,影响运输价格的重要因素是运输路线,路线不同,运输成本和价格肯定不同,因此,这里的运输路线属于一种战略上的策略指定。
一级调度员会根据运输路线编排行程计划与任务。这里可以用普通旅游来类比。一个人想出行旅游,首先需要规划旅游路线(如从广州到北京游玩),第二步是制订行程安排:是直接从广州飞往北京?还是坐高铁先到武汉游玩几天,然后再到北京?如果选择后者,那么整个从广州到北京的行程就被划分为两段,即广州到武汉、武汉到北京。一级调度员的工作目标是确定行程计划与分配行程任务,如果说路线属于策略,那么行程就属于落实策略制订的业务规则,二级调度员再根据行程任务安排一个个具体行程段,这属于业务规则的进一步落实。针对这种运输业务规则建模,制订行程任务是重点,图1-8中用编排任务代表了编排行程任务。

图1-8 货运车队的业务规则建模
在上述行程制订的模型中,突出“编排(行程)任务”作为规则模型中的核心,车队调度(二级调度员)会根据行程任务再划分为一个个行程段,安排具体车辆实施这些行程段,如图1-9所示。

图1-9 货运车队业务规则的实施
在这个行程段实施模型中,每个行程段的“作业”是业务规则的核心,每个行程段作业的分派与管理都得到了实现。行程段作业是用来实现整个行程的一个步骤,整个行程的内容使用图1-9中的“任务单”表示,任务单等同于行程任务单。
由于业务规则涉及具体量化指标,因此图1-9中将到达门店时间以及门店的位置种类都进行了详细的表达。这是完成一个行程段所需的信息,运输行程段包含何时在何地装货,何时在何地卸货,一个装货对应一个卸货。
最后,需要说明一下业务流程和业务规则的关系。
业务流程为了实现业务规则,因此可以通过业务流程去发现业务规则。业务流程是每个企业管理和运作中最复杂的部分,也是进行信息化的主要目标,甚至有各种流程可视化管理工具,分析师只要画画流程图(符合BPMN标准),流程工具软件就可以将BPMN流程图自动生成代码并运行起来。图1-10所示为一张请假的流程图。
图1-10中只是一张普通的请假流程图,有人工参与,而现在BPM(业务流程管理)工具可以将人工流程和自动化流程结合起来。这种BPM工具看上去很方便、强大,但它们的致命问题是将重点放在流程上,流程变化非常灵活,只使用一个灵活工具,不管流程怎么变,都能画出一套漂亮的流程图,却忽视了流程背后的业务规则。流程是为了实现业务规则的,业务规则决定了业务流程,如果业务规则变化了,流程就会重新制订,新的流程有可能需要整合各种资源,包括人工和各种数据资源以及外部API等,涉及太多细节,工作流或BPM工具就非常力不从心,而使用DDD则可以根据新流程对特定环节的上下文进行针对性代码开发,最后再整合集成起来。

图1-10 请假流程图
业务流程是表面的、显式的,而业务规则是隐含在业务流程中的,都是业务策略的具体实现。业务流程中会掺杂管理人员的思路,它是通过人工流程的管理来完成业务规则,当业务规则通过软件信息系统自动实现时,人工流程对业务流程的干扰因素必须排除。这种干扰因素表现为使用管理术语替代业务领域术语,如运输货运车队系统中,制作计划大表是作业流程中的重要步骤,但是计划大表是什么没有描述清楚,这是从管理流程角度命名的。其实这个计划大表是行程计划大表,编排任务是编排行程任务,不是抽象的运输任务,但是运输货运车队系统内部人员听到“编排任务”都会意识到是行程任务,也可能会从自己的角度理解,例如,司机听到“编排任务”意识到自己有出车任务,但是他不一定会抽象出准确的“行程任务”来详细说明它;一级调度员听到“编排任务”意识到自己需要安排这些任务,他也不一定会命名其为“行程”;经理等管理人员听到“编排任务”会想到这些任务是否会落实下去,效率如何,成本如何,他关注的重点也不是业务领域本身。这三种角色都出于自己的岗位角色对业务领域有自己的认识及偏见,当这三种角色和技术人员或产品设计人员一起进行头脑风暴时,大家才会意识到,需要将“编排任务”明确为“编排行程任务”,“行程”这个概念是汇合三种角色的“一孔之见”而形成的真正的“大象”。
因此,需要对现有的业务流程进行综合全面的分析,从中挖掘出领域本质,发现业务规则,界定领域的边界。在对业务流程的综合分析中,涉及业务行话或业务术语等统一语言,如何从这些行话或术语中挖掘领域本质,是DDD建模的重要一步。
1.3.2 统一语言与有界上下文
技术人员与产品经理之间经常存在鸿沟,例如,某产品经理转达客户的需求:将手机的背景颜色自动调成与手机壳颜色一致。客户的这条需求应该算是一条业务策略,产品经理没有将业务策略细化为业务规则,也就是可执行、可量化的规则。首先需要解决手机里的程序是自动感知手机壳的颜色,还是让用户自己输入手机壳颜色?这些都需要明确,如果不明确,直接做一个传话筒,就可能引起程序员的不满。
这种矛盾可能来自两个方面:公司人员对业务策略和业务规则无法分清,导致角色定位不清;不同部门之间的沟通方式有问题,没有找到一种统一的沟通语言。
前面谈到技术人员在技术团队内部的交流中,更习惯于使用技术级别(如字符串、整数、Map、List、循环等)的术语,而不是使用业务术语,而在产品团队或分析师团队甚至在管理层团队,大家都是使用业务术语进行交流的,那么两种团队之间就可能形成互不理解或经常误解的尴尬。
例如,在机场信息管理系统中,值机柜台的屏幕可能会显示旅客的“行李数量”,而登记门口的另一个屏幕可能会显示“行李数”,行李装载人员使用的计算机可能会显示“行李箱”,这些其实都是“行李数量”。
当然,统一业务术语不是编制一张词汇表就可以了,术语名词还取决于不同上下文,如上面的行李数量有可能在装载人员系统里就改为了“行李箱”,类似地方方言,这些都和特定上下文有关。也可能是这种复杂性导致人们的语言无法真正统一,如果发生这种不一致,就需要进行上下文中词语的翻译映射,将其明显标注出来。
所谓有界上下文(Bounded Context,BC)实际就是有边界或有界限的上下文,上下文可以理解为环境背景。如果说统一语言表达的是事物名称,是事物内部结构特征的凝聚表达,那么有界上下文就是事物所处的外部环境背景。前面反复强调了这种认识事物的方法论:领域即边界,边界靠分类,分类须从事物内外部入手。日常生活中分析任何事物其实都遵循这样从两个角度入手的规律,提炼概念或领域模型也是从这两个角度入手才能全面。
领域、有界上下文和统一语言的关系如图1-11所示。中间椭圆形表示有界上下文,存在于领域这个边界内。这个有界上下文的名称是“销售”,那么“销售”就是这个上下文的一个统一语言表达。当然,销售上下文中可能涉及“商品”这个词语,而在另外一个商品管理上下文中,也存在“商品”这个统一语言,这两个语言表达处于不同的上下文,所以它们的意义是不同的。

图1-11 领域、有界上下文和统一语言的关系
有界上下文这个DDD中的难点和复杂点会在后面章节专门讨论。
1.3.3 领域模型的提炼
上节谈到了在不同上下文中语言的表达可能不同,这就为提炼模型带来了难度。在系统开发中,模型可以表现为Excel图表、流程图、UML符号、Java或C#的类、数据表结构等。注意这些都是模型的表现形式,模型的内容是捕捉对问题空间的基本理解,并提炼出一组特定的概念。
提炼概念或模型遵循从事物内外部入手的原则,需要明确问题空间,发现其中的复杂性,将最复杂的部分圈起来形成核心攻关领域,发现统一语言和其所处的有界上下文,根据这两者提炼出领域模型。例如,上节提及的“销售上下文”,“销售”是这个上下文的统一术语,那么就可以提炼出一个“销售”模型。下一步是分析这个模型内部有什么、有什么结构特征、有没有业务规则隐藏其中,这些都是向事物内部深挖分析的步骤和方法。
一般业务规则是通过业务流程显现出来的,那么只要检查上下文中是否有业务流程就能触摸到业务规则。当拜访销售部门或与领域专家进行事件风暴会议后发现,销售上下文中发生的事件有:签合同;查询库存是否有货;发货或安排生产;跟踪应收账款。
表面的业务流程通常隐藏着业务规则,这里的业务规则是什么呢?
可以用反问法发现业务规则:如果没有这些流程、事件、步骤是不是也可以?领域专家会解释如果没有它们会产生什么后果(规则是用来防范坏的结果的),这样就离业务规则越来越近了。
通过这些事件会发现几个重要环节:签合同、发货、生产、收款。合同是起始点,然后根据产品的库存数量决定是发货还是安排生产计划,同时要制订好收款计划。业务规则必须做到详细量化,因此发货的时间地点必须明确,安排生产计划时生产完成时间也必须明确,这里涉及生产任务单下发到工厂或车间等的具体时间。
明确业务规则以后,可以说已经深入领域的复杂性核心,这时就可以提炼出概念模型了。提炼模型的要点如下。
1)简单准确。模型是对真实事物的简化理解,注意力需要集中在内部要点上。
2)通用统一。语言必须足够通用,应该是业务人员的务实术语,能够提供人们在谈论系统时可以使用的统一语言。
3)逻辑严格。逻辑上必须足够严格,从而可以成为编写代码的基础。
下面对这些要点进行详细说明。首先以人(Person)为案例,这里的统一语言名称为“人”,它的定义也考虑其所处的上下文,如果在工作单位上下文里,它的内部属性可以进行如下抽象。

在这个设计中,删除了一个人可能拥有的大量属性和行为,将其减少为与职场有关的三个基本属性。省略细节会使系统变得简单,可以带来很大的好处:准确性、简明扼要、纲举目张。
在“人”的领域中,“人”是一个复杂的交互实体,但在领域模型中,一个人是具有工号、姓名、职位和拥有变更职位能力的模型。当使用“人”这个词时,就涵盖了这些属性特征和能力行为,虽然属性与行为的数量不够全面,失去了一些丰富性,但获得了精确度。
提炼领域模型很难,因为对其所需的严格理解比大多数人想象的要深刻,为什么呢?因为提炼的领域模型会变成代码在计算机系统中反复执行,这样就会丧失像人工作业中针对特殊情况做灵活处理的机会,这种特殊处理是因为有人在现场,发现不符合业务规则的例外时解决即可。以自行车为例,如果要设计一个骑自行车的机器人,那么对骑行的理解需要比大多数自行车运动员等专家要更深刻——自行车如何能不偏斜而跌倒?自行车手会根据感觉进行平衡,但是机器人需要分析出各种力学参数。
这个骑车机器人的故事告诉我们,如果拜访领域专家的负责人,就会发现并没有现成的模型,也不能要求领域专家提供所需要的所有答案,而是要与领域专家一起分析问题来建立模型,这是一个迭代过程,需要探索许多可能的模型,并选择一个适合解决当前问题的模型。
同时,模型语言必须通用,是大家约定俗成的,使模型在描述系统时能够成为无处不在的统一语言,如在用户界面、文档、需求或用户故事、代码以及数据库表的讨论中使用相同的术语。
再回头看看销售上下文的例子。“销售”这个词语已经是销售上下文的统一语言,不会是“推销”“卖东西”等其他词语,提炼出的销售模型详细定义了“销售”这个术语,销售模型如下。

这个模型从名称到内部特征属性和能力都属于统一语言的范畴,销售单号、销售日期以及其他相关属性行为其实定义了“销售”这个词语的含义,这个定义仅基于这里的上下文,有可能换一家公司,“销售”的定义又不同了。
最后,模型必须严格,如果模型不严格而包含了模糊性,那么系统的一部分可能一会儿表现为一种形式,一会儿又表现为另一种形式。前面案例提到的“行李数量”不能有时称为“行李数”,有时称为“行李箱”,模型会落实为代码,术语名称不同,代码的变量名就不同,这些似是而非的不同名称难以让人区分。
为了让模型严格,还要将业务策略转变为业务规则或流程,甚至划分为一个个小的逻辑自治单位。以前面的租车公司为案例,在模型中无法表达租车公司的业务策略(必须通过延长车辆的寿命获取最大价值),而只能表达具体的业务规则:所有车辆都要三个月保养一次,轮胎50000公里更换一次。模型如下:

模型严格性还在于领域边界不能过于宽泛,过于宽泛的系统容易出错,可能导致安全漏洞,正如前面提到的ERP案例,不能希望在生产制造领域以外的地方还能使用ERP,也不能构造一个可灵活定制各种属性的通用对象系统或数据表管理系统。领域边界越宽泛,精确性就越不够,使用起来就越不方便,还需要将自己的专业词语翻译到这样的通用系统上——如果想配置ERP系统来处理客户服务领域的投诉,需要做一些非直观的抽象:“客服人员可以看作一台生产设备,关于客服的投诉可以看作原材料,在投诉处理和反馈过程中由客服人员这台机器不断完善。”
以上讨论了模型提炼的三个要点:简单准确、通用统一、逻辑严格。这三者的关系有时可能会存在矛盾,细节越多越精确越严格,但是细节多了可能不够通用,如同规章制度,如果规定越细,执行起来反而不方便,但是也不能制定得大而化之,以至于无法严格执行。
提炼模型需要建模人员具有较强的逻辑思考能力,而逻辑能力的培养有多种途径:数学、计算机编程、从事律师辩护等,并不是理科背景的人在逻辑思考上一定会比文科背景的人强,而是在于多年的专业思考历练。
只要逻辑思考能力还不够,就需要有挑战自己思维习惯的勇气。大脑总是希望尽量减少能量消耗,因为大脑已经占据了整个身体所用能量的15%。这意味着,它不想一遍又一遍地思考所有事情。因此,大脑会创造捷径。当第一次有问题时,会借鉴其他人的经验,或者使用第一原则来得出结论,无论哪种方式,大脑都会记住这个结论,下次就无须再次思考,从上次的结论开始即可。随着时间的推移,大脑会越来越少地从第一原则开始思考,而更多地从结论开始,当基于一个结论推导出另外一个结论时,好像大脑的经验越来越丰富了,但实际上这套结论可能会过时,因为第一原则这个“根”在不同场景或上下文时就可能不同。
在模型构建中还要注重模型内部固有的张力关系,根据固有的张力能够从现实创造一系列强大模型。虽然大而逼真的模型为用户提供了丰富的细节和高水平的精确度,但由于它们的复杂性,其构建、跟踪和审计方面也更具挑战性。另一方面,小而健壮的模型通常更容易建立、遵循和审计,但缺乏决策所需的精确度。最好的模型可以协调这些对立的力量,从而使输入和输出尽可能简单,同时仍然为决策提供足够的细节。