DDD系列文章第6篇:领域建模那些事

全文约7600字,预计阅读时间16分钟。

导读

前面介绍了如何拆解战略,从价值落实到产品的业务场景和流程,再拆分出每个业务功能。划分子领域和BC进一步降低了这个拆解过程的难度。本篇继续介绍如何描述清楚BC内的业务功能,也就是DDD领域模型范畴。为什么说领域模型很重要,领域模型是什么?如何建模?

01

模型和领域模型

前面讲战略设计时从产品价值分析一直拆解业务需求分析,从业务流程图、服务蓝图一直拆解到用户用例,为了缩小每次分析的范围,降低复杂度,需要加入子领域和BC的识别和边界划分。领域模型是战术设计的一部分,将会对BC内所涉及的业务逻辑在合适的粒度上做提炼建模。既然是建模需要先来了解一下模型的定义。

1.1 什么是模型?

模型是个很泛泛的词,随处可见。既存在于有形的物理世界,也出现在无形的精神心理层面。比如:

  1. 工业生产行业有模具,通过模具可以标准化、大批量生产一样尺寸大小的成型物品。模具是种实体的模型。
  2. 房地楼盘有沙盘模型,通过它你能知道小区的基本情况包括周边道路、大门位置、楼栋布局、绿化情况等。
  3. 地理地图、交通地图、城市地铁图也是模型,通过地图你能了解不同区域的地理位置,连接路线。
  4. 成长性和固定性思维等心智模型也是模型,帮助你解释行为背后的逻辑,总结过去的经验,指导将来的行为。
  5. 为人处事原则也是模型,通过它以少数不变的方法应对不断变化的周遭。
  6. 甚至自我介绍也是一种模型,从姓名年龄、专业特长、社会经历、兴趣爱好等方面让别人了解你。

结合上面的几个例子再来看下百度百科对模型的定义

模型是通过主观意识借助实体或者虚拟表现,构成客观阐述形态结构的一种表达目的的物件(物件并不等于物体,不局限于实体与虚拟、不限于平面与立体)

现在我们大致能得出模型的几个特点

  1. 模型是对现实世界或者虚拟世界里发生的事物逻辑的一种表达方式,要表达清楚要解决什么问题。
  2. 模型表达是由表及里,表达事物逻辑的本质,是抽象、分类、归纳的结果
  3. 模型表达的目的是为了简化对事物的理解,方便人与人之间的沟通。

1.2 什么是领域模型?

领域模型也是一种模型,只是表达的对象不同。领域模型是DDD里提出的一种概念,用来描述特定领域要解决的业务逻辑。领域模型是软件开发相关人员(如产品、研发、运营等)用来表达产品业务逻辑的一种方式。搞清楚什么是业务逻辑是理解领域模型的前提。

什么是业务逻辑?

业务逻辑是业务人员、产品经理、研发人员之间日常沟通的主要内容。包括业务交互场景,业务流程,业务功能点,业务规则。描述业务逻辑往往就像写一篇记叙文,记叙文有六要素(时间,地点,人物,事情的起因,经过,结果),业务逻辑的描述也是围绕这六个要素。比如营销SaaS产品里线索创建的所有业务逻辑都可以拆解到场景、流程、用户用例、功能点、具体规则或步骤中表达出来,如下图所示。

业务逻辑的拆解其实就是团队提高业务理解达成共识的过程,为了让这个过程更加高效和可复制,其拆解方法和拆解结果需要通过某种形式固化下来。DDD里的领域模型就是一种很好的表达形式,DDD里还提出了多种领域模型来适配不同粒度的业务逻辑。因此领域模型就是业务逻辑的抽象

有哪些领域模型?

DDD里能承载业务逻辑的领域模型有:实体、值对象、聚合(特殊的一种实体)、领域服务、应用服务、领域事件。

02

为什么要领域建模?

为什么要领域建模?领域模型对业务逻辑的分析和提炼是必需的吗?如果逻辑足够复杂那么建模是很有必要的。前面讲到自我介绍其实也是一种建模

  • 要上幼儿园的小孩为了让老师了解自己时只要说叫什么名字,几岁了就可以,可能需要凑点爸爸妈妈的信息来丰富内容。由于其自身经历少,要介绍的就是些非常基本的信息。
  • 要进入职场的新人为了让用人单位了解自己时需要按照简历的模板,详细写下个人基本信息、教育经历、获奖情况、实习经历、兴趣爱好。要介绍多个层面的信息。
  • 要结婚的人为了让对方了解自己仅靠工作简历是远远不够了,还需要花很多的时间谈恋爱,互相熟悉对方。整个过程除了要『介绍』个人基本信息和职场信息,还会涉及家庭其他成员信息,财产信息,性格信息等等,可谓是360度无死角地做了一次自我介绍。

工作简历就是职场自我介绍的一种建模,用人单位是主导方和需求方,简历格式和内容都是按照用人单位希望看到的来准备,一提到工作大家就是它是为了解决什么问题,因此建模首先是围绕问题的。工作简历不是自传,其内容是需要提炼并结构化表达出来,工作简历格式不会经常变,因此建模是抽象提炼的过程,建模结果需要比较稳定和结构化。一提到工作简历大家就知道大概包含哪些内容,需要怎么写,因此模型是高效沟通的工具,能大大降低大家对同一个问题的心智理解,甚至能成为一个常识知识。

现实的软件开发流程中,产品经理眼里的业务逻辑大多是场景、流程、功能、规则。研发人员眼里的业务逻辑大多是:系统模块、类(对象)、类属性、方法,数据库表和字段。经常发现大家讨论了半天其实说的不是同一个东西。如何跨越这个鸿沟?如何能够让这两种思维模式不一样的群体能够更有效地协作完成业务系统的开发?这就是领域建模要解决的问题。领域建模就是为了让产品和研发能从问题出发,提炼业务逻辑并结构化表达出来,并成为日常沟通的基础,达到了模型(设计成果)和代码(实现成果)的绑定

特定软件系统有很多『著名』的领域模型,这个领域里的人一听到它就知道要解决哪类问题,怎么解决的。

  1. 权限设计系统的RBAC模型,四个字母就概括了权限设计要解决的问题和设计方案包含的要素。
  2. 可观测系统里的Log、Trace和Metrics领域模型,非常经典的概括了如何表达系统的可观测性。
  3. 电商领域商品系统里的SKU、SPU模型,提到它就知道建模商品时需要考虑商品展示和售卖等不同维度。
  4. 云原生服务网格架构下的SideCar模型,概括了服务网格目前最通用的一种落地实现形式。

03

领域模型怎么做?

虽然领域建模是DDD的战术设计步骤,但其实从战略设计阶段的业务流程拆解就已经开始了,也就是说业务逻辑是领域建模的主要输入。另一方面业务逻辑和模型也有不一样的地方。一则业务逻辑偏重流程、角色互动、和功能点,偏动态的行为。领域模型偏静态的数据,主要描述数据和数据关系。二则业务逻辑的拆解一般自顶向下,粒度从大到小。建模过程往往跟搭积木类似,把每个小零件做成小积木,再把小积木搭成大积木,是一种从小往上,粒度由小到到大。如何弥补这两者的距离?从业务逻辑梳理过渡到模型是建模方法主要考虑的地方。这也是传统的面向数据库表设计这类纯数据模型驱动设计方法的缺陷所在,无法有效衔接上业务逻辑拆解分析的结果。

3.1 领域建模方法和步骤介绍

领域建模方法大体有两大类,第一类就是用户用例建模法。第二类是事件建模法。传统软件开发流程里需求分析有用户用例,基于用户用例发现领域概念是自然而然的做法。DDD社区里更流行的是事件建模法,因为事件能更好弥补行为和数据的差异。一则事件有比较强的时间属性,能对应上实际业务流程往往带有明显时间节点性的行为。流程往往伴随开始、推进、流转、再推进、结束等状态行为变化。二则事件的触发和处理需要参考数据和规则,这些数据大多来自配置数据和过程中产生的业务数据。

3.1.1 用例建模法

用户用例往往用一个动词加一个名词的表述,因此也有叫作动名词法,其大致步骤是:

  • 整理用例,包含角色及角色要达到的目标。
  • 提取用例中的名词和概念作为『实体』的候选,讨论清楚实体的含义和范围。
  • 围绕实体建模,模型里包含需要达成业务目标的属性和方法。明确实体之间的联系。
  • 建模完成后形成团队的统一语言。

用例建模法比较简单,适合对业务比较熟悉,业务逻辑相对简单的场景。

3.1.2 事件风暴法

事件风暴法是DDD比较推行的,事件流很程度上反映了现实业务流程,事件就跟讲故事一样,能够非常好地调动起现场人员的参与积极性。基于领域事件发生的时间线,能够把事件的前因后果逐步挖掘出来。事件风暴用类似记叙文六要素一样通过几个概念来清楚描述一件事:领域事件、命令、角色、决策规则、领域名词。

事件风暴的步骤

  • 通过头脑风暴方式寻找出领域事件,注意不是所有的用例都对应着事件,比如页面查询展示场景就不是。
  • 根据事件找出触发它的角色,可能是用户,也可能是系统
  • 根据事件找出触发它的命令,可能来自角色,也可能来自系统比如定时任务,消息订阅等
  • 通过事件找出触发的规则和条件,命令触发后需要做出的策略,比如支付能成功必须有绑定好的支付账户
  • 通过事件找出事件发生后产生的结果即领域名词,往往是业务数据的新增和修改
  • 根据当前事件找出它的前因后果,是不是之前发生了什么前序事件才有了当前事件,当前事件发生后会引发什么后续业务
  • 重复上述步骤找出整个业务流程相关的事件流

举个例子

最原始的需求往往就一两句,比如这个场景:销售员通过中间号电话拨打自动跟进一条线索。这个场景需要拆解出多个业务流程(线索创建、与运营商系统打通、系统内绑定中间号、线索跟进支持中间号,标签建设等等),每个流程涉及到多个功能点。

事件风暴建模法能把相关业务流程的功能点串联起来,并以事件的因果逻辑做到查漏补缺。因此事件风暴里需要打破沙锅问到底,不停地发问并回答,让所有参与的人都能够知道业务逻辑的来龙去脉。比如:

  • 拨打电话和线索状态改变是什么关系?这就反推出系统需要能够分析拨打电话的话单。通过判断电话的拨通状态,谈话内容等修改线索的状态。
  • 如何能让销售员第一时间跟进线索呢?这就要求在线索创建后系统能自动通知到销售员。
  • 该通知哪个销售员呢?这就需要设定规则进行线索的自动流转分配。

发散之后如何收敛

事件风暴的过程往往会太过发散而收不拢,从而陷入分析瘫痪。因此事件风暴的成功很大程度上取决于如何收敛,这也要求主持人或者领域专家能够发挥重要作用。但另一方面可借鉴的收敛原则语焉不详,因此实际情况中大家上手容易,但用好的不多。这里也总结几条个人经验。

  1. 用户交互是一条时间线,但它不等同于领域事件流。是否为领域事件一般以是否改变了领域对象的状态作为判断依据。查询数据这类用户交互场景就不属于领域事件。
  2. 紧扣因果关系。领域事件流紧密联系着业务逻辑,通过像『查户口』的方式是把事件的因果关系进行追根溯源,以此来推敲业务逻辑的严密性。比如修改订单事件必然需要先查询到已创建好的订单。
  3. 紧扣事件要素(领域事件、命令、角色、决策规则、领域名词),不相关内容不要发散。
  4. 优先关注happy-path即正向路径,一是提高大家的熟练度,二是避免太过发散收不拢导致看不到分析结果。
  5. 每次分析的业务流程尽量在一个子领域内,最好在一个BC内展开。
  6. 事件风暴不是一蹴而就,需要保持迭代进化。

其他注意事项

  1. 命名:紧扣业务,不参杂技术元素,警惕使用泛泛的词汇,尽可能地消除命名的义性。从已发生视角命名事件(名词+动词过去式),命令命名采用动词+名词。
  2. 要以可视化方式把事件风暴过程和结果记录下来,不同元素用不同的颜色区分表示。
  3. 不要太在意工具和形式。没有白板条件的时候退而求其次也可在线方式进行,使用任何画图软件都可以。

3.1.3 四色建模法

四色建模发展自Peter Coad的《彩色UML建模》,旨在把所有的模型对象抽象为四种,并在模型图里用四种不同的颜色标记出来。

  1. 时刻-时间段原型(Moment-Interval Archetype,MI):这类对象对应领域事件,用来记录某个时刻或某段时间内管理和运营数据,用粉色表示。比如订单系统里,订单就是典型的MI对象。
  2. 参与方-地点-物品原型(Part-Place-Thing Archetype,PPT):业务流程中的参与者,可以是人、物、地点。用绿色表示。
  3. 角色原型(Role Archetype):PPT对象在参与MI过程中,扮演的角色,用黄色表示。
  4. 描述原型(Description Archetype):对PPT对象的更详细描述,用蓝色表示。

四色建模步骤

  1. 确定系统的关键业务流程,分解出关键步骤。
  2. 这些关键步骤是否需要可追溯数据,缺失了这些数据会不会影响运营管理?需要可追溯的数据正是MI对象。比如线索创建会产生线索记录,如果线索记录不可追溯,后续的销售跟进、线索转化怎么办?这就意味着线索数据必须作为MI对象存在系统里。
  3. 识别出上述MI对象涉及到的参与方、地点、物品。比如上述线索对象需要有线索的创建人、所在的线索池、所归属的销售员、线索联系人信息。这些信息就是PPT对象。
  4. 根据上一步识别出来的MI对象和PPT对象,找出它们之间的关系,抽取出来的角色就是Role对象。比如只有管理员才能分配线索给销售员,管理员属于线索池范畴,因此线索池就是线索的一个角色模型。
  5. PPT对象往往还需要更多的补充信息,比如联系人信息这个PPT对象还有地址、联系方式等补充信息,这些补充信息就是描述对象。

个人对四色建模法还不是很熟练,但它的底层思想很值得参考,即从企业运营出发,围绕息息相关的关键数据(可能是现金往来的记录,也可能是企业运营核心数据资产)展开业务流程分析。业务流程是为了满足业务系统的目标,业务系统的目标是要支撑企业的日常运营、商业决策。而运营管理过程中每个动作都会留下痕迹。

3.2 领域模型设计

前面建模方法得到的领域事件各要素需要通过不同的领域模型来承载。

3.2.1 业务数据视角建模:聚合、实体、值对象设计

建模方法得到的领域名词和规则,需要进一步定位为值对象、实体和聚合。但聚合是个虚拟概念,本质上还是实体,因此所有业务数据的载体要么是实体,要么是值对象。建议的设计优先级是先设计值对象,再设计实体,最后是聚合设计。

实体和值对象设计原则

  1. 是否具备唯一标识是实体和对象的本质区别。实体一旦创建会有后续状态变化,每个变化都需要通过唯一标识来追踪。
  2. 什么时候该设计为值对象?有自我验证逻辑的属性(也叫字段),这些属性的验证逻辑如果放到实体里,会导致职责不够清晰,实体代码容易膨胀。比如邮箱属性它有自己的格式校验逻辑。有自我计算逻辑,比如金额属性包含数字和币种,币种的换算逻辑建议放到金额值对象里。其他情况不要设计为值对象,当做实体的普通属性。
  3. 值对象可以单独存储为一张表吗?可以,如果够简单就随实体表一起保存,如果复杂可以有单独的表存储。存储几张表跟领域模型数量不直接相关。
  4. 能够自给自足完成业务逻辑(计算或校验)的行为应该设计为实体或值对象的方法。

聚合设计原则

  1. 【独立性】只有聚合根才是访问聚合边界的唯一入口
  2. 【完整性】尽量保持聚合领域概念的完整性
  3. 【访问原则】聚合之间应通过聚合根的身份标识进行引用,聚合内部的实体间可以通过对象引用
  4. 其他聚合需要加载一个聚合时(往往通过领域服务/应用服务协调),必须通过目标聚合的资源库,并返回聚合的完整内容,但资源库可以有支持多种加载条件的接口。
  5. 新增一个聚合必须调用根实体必要的校验和领域逻辑,并传递给资源库一个完整聚合
  6. 更新一个聚合必须调用根实体必要的校验和领域逻辑,并传递给资源库一个完整聚合。资源库的实现层面可以做到局部更新
  7. 【聚合协作】同一个BC的聚合间协作可以采用本地事务保持一致性,跨BC(即跨微服务)的聚合协作通过基于消息机制的最终一致性
  8. 【聚合与资源库】不要在聚合中访问资源库
  9. 【聚合粒度】在上述规则满足的前提下,聚合设计尽量小,小聚合带来小的事务粒度、有更好的性能。一开始可以把每个实体当作一个聚合,通过判断聚合是否足够完整和实体是否有独立性读和写的需求来合并实体,比如实体A和实体B生命周期一致,同时A没有被外界独立访问的需求,则合并A和B为一个聚合。如果实体A有被独立访问的需求,则A作为单独聚合。

3.2.2 业务行为视角建模:聚合、领域服务、应用服务设计

所有业务行为的载体要么是聚合(即实体或值对象),要么是领域服务,要么是应用服务。

领域服务和应用服务设计原则

  1. 不属于实体或值对象中的领域行为放到领域服务,比如线索创建时的查重功能,因为需要跟数据库的全部线索电话号码进行查重,这不属于线索聚合的行为。
  2. 看起来领域服务和应用服务都能做编排功能,原则上是应用服务不应该包含领域层的逻辑,那怎么判断什么是领域层的业务逻辑?这个问题没有标准答案,设计时不用太条条框框。一个简单的判断标准:这段逻辑是否与本BC的主要职责直接有关?如果相关的话则应放在领域服务。
  3. 领域服务和应用服务能编排资源库、聚合、适配接口。注意聚合里实体和值对象不是编排单元。

3.3 怎么衡量建模质量

首先建模很考验对业务的理解和设计水平,因此不太好衡量模型质量。如果模型建好后能够在团队内持续使用起来并能得到效率的提升那就是好的。下面仅列举一些现象或者味道来侧面反映建模的质量:

  1. 产研要共同参与。既增加了对模型的认同感,也形成了领域的统一语言。单方面建出来的模型容易腐烂不容易演进。
  2. 模型是统一语言的载体,是能传播起来的。模型里的元素如实体、规则等都需要有对应的统一语言描述,并在成为团队内部日常沟通的语言基础。模型成为了产研讨论需求的载体,研发讨论代码的依据。
  3. 模型降低了理解门槛。新需求的开发能够建立在良好基础上进行演进,不至于每次开发新功能都要重新学习业务逻辑和代码逻辑,研发通过代码就能轻松看懂系统的业务逻辑。
  4. 模型沉淀了团队的领域知识,团队成员有业务经验的增长,团队新人能够快速上手。

04

结语

本篇阐述了领域建模的定义、方法和作用。如何能够让软件开发团队内不同工种的人在面对复杂的业务时能够共同理解好业务并高效协作?领域模型是DDD给的一个答案。通过领域模型来分析和表达业务逻辑,这种模型思维是一种比较好的答案。领域模型让线上系统、日常交流、文档和代码这四者能够协同演进。后续讲述跟架构和代码有关的内容。

感谢阅读,本篇首发于微信公众号:非写不可 - DDD系列文章第6篇:领域建模那些事

举报/反馈
posted @ 2022-09-19 16:57  arrowolf  阅读(319)  评论(0)    收藏  举报