06 领域建模实践(下):领域建模还有什么其他技巧?
你好,我是钟敬。
上节课咱们介绍了领域建模的一些概念,也一起完成了有关租户、组织和员工的领域建模。今天这一讲,我们继续对项目管理、人员分配和工时登记部分进行建模。
在完成领域模型的过程中,我们还会对“多对多关联”进行更深入的学习,一起识别“操作”,并且划分模块,最后还会完善业务规则并建立词汇表。掌握了这些技巧,你就可以尝试运用领域建模解决一些实际问题了。
深入理解“多对多”关联
和上节课一样,我们从项目管理部分的领域名词开始建模。同样,你是架构师,我是产品经理。
我们先假定每个名词都是一个实体,然后再把这些实体一个个加到已有的模型图里。初步识别出的实体有下面这些:
处理“客户”
我们首先来处理客户实体。按照需求,每个客户有一个客户经理负责。你和我确认了一下:“客户经理也是员工对吧?”我说:“是的。”于是你把客户添加到了模型里,如下图:
这里你把客户经理当作了一个角色,员工和客户的关联翻译成自然语言就是“一个员工可以充当多个客户的客户经理,而一个客户有且仅有一个员工作为他的客户经理”。
处理“合同”
完成了对客户的建模,我们开始处理合同实体。根据需求,“每个合同都有一个销售人员负责”,于是,你画出了下面的模型图:
你有些疑惑:“客户经理是不是也是销售人员呢?”我说:“是的。”于是你意识到:“销售人员应该也是员工的一种岗位,并且在与客户的关联中,充当了客户经理这个角色。那么,类似地,销售人员又充当合同的什么角色呢?”我说:“就叫合同经理吧。”
于是,你修改了销售人员这个角色的名字,并且在岗位的注释中增加了销售人员。此外,你又增加了“只有销售人员才能作客户经理” 以及“只有销售人员才能作合同经理”这两条业务规则。
现在的模型图如下:
处理“项目”
接着,你又用类似的方法,增加了项目实体:
处理“为项目分配员工”
现在我们考虑“为项目分配员工”和“员工退出项目”这两个需求。
首先,我在图里面的员工和项目之间增加了一个表示“员工被分配到项目上”的关联,同时把“一个员工预计的投入百分比不能大于百分之百”的规则也标注出来。
这时候,员工和项目之间就有了两个不同的关联。原来那个一对多关联表达的是“员工充当项目经理”的关系。
另外,我们新增了一个关联,表示分配员工的关系。一个员工可以被分配到多个项目上,一个项目上又可以有多个员工,因此两者是“多对多”关联。在这个关联的员工一端,写上了项目成员这个角色。也就是员工充当了项目的项目成员。
在这里我们可以看到,两个实体之间,可以有多个关联。不同的关联代表不同含义,数量关系也可以不同,可以用角色名来区分。
然后你问我:“这里有一个关于投入百分比的规则,那么投入百分比这个属性应该记录在哪个实体上呢?”这时候我们发现了一个尴尬的问题,这个百分比既不能记录在员工实体上,也不能记录在项目实体上。
这是因为,只有员工和项目建立了项目成员这一关联的时候,记录投入百分比才是有意义的,也就是说,这个属性应该记录在这个多对多关联上。那么这在UML中怎么表达呢?
于是,我把模型改成了下面的样子:
这时候,原来的项目成员角色演变成了一个独立的实体,来表达员工和项目之间的多对多关联。预计投入百分比也就自然放在这个实体里面了。
注意,这时候,员工和项目成员之间的关联是一对多,项目和项目成员之间也是一对多。然后我总结道:“任何多对多关联,总能用类似的方法,通过引入一个表示关联的实体,拆成两个一对多的关联。”
接着,你又注意到一个问题,一个员工可以分配到一个项目上,然后退出项目,然后再一次分配到这个项目上,所以,我们就应该用时间段来记录员工参与项目的历史。
考虑到时间因素,有关投入百分比的业务规则可以进一步明确为“在任何时刻,一个人的预计投入百分比总和不能大于百分之百。”此外,从逻辑上,还可以识别一条规则一个员工在同一个项目上的时间段不能重叠。
于是你这样改进了模型:
处理“登记工时”
接下来,为了处理报工时的需求,你又增加了工时记录实体,以及相关的业务规则:
同样,我们可以把工时记录看作员工和项目之间的一种多对多关系。
识别“操作”
经过上面的梳理,我们已经把剩余的模型梳理得差不多了。但是,在事件风暴中咱们还遗留了一个问题:怎么处理客户经理上级、销售人员上级和项目经理上级这几个概念?现在看来,它们不太像是独立的实体,而更像是某种关系。
经过讨论我们发现,客户经理、销售人员、项目经理,无非都是员工,所以他们的上级,其实都可以归纳为取员工上级这个操作。然后我们讨论出了取员工上级的逻辑:
第一,如果员工不是所属组织的负责人,那么这个组织的负责人就是员工的上级;
第二,如果员工就是所属组织的负责人,那么这个组织的上级组织的负责人就是这个员工的上级。
你看,在上述逻辑中,我们又引入了一个组织负责人的概念。可以认为,负责人是员工在与组织的关联中充当的一种角色。
基于这些理解,我把模型进一步完善了一下:
在这个模型中,有几个要注意的地方:
第一,为了表达组织的负责人,我在员工和组织之间又增加了一个一对多关联,表示“一个员工可以不作任何组织的负责人,也可以作多个组织的负责人;而一个组织可以暂时没有负责人,但最多只能有一个负责人。”
现在,组织和员工之间就有两种不同的关联了,分别用了成员和负责人两种角色来区分。
第二,我们在员工实体上增加了取上级这个操作。操作(operation)在UML里也叫方法(method)。对象的属性是静态的值,而操作是动态的逻辑。在UML中,操作用“操作名+括号”的方式表示,括号中可以写参数。
第三,我们用一个注解说明了取上级这个操作的业务逻辑。
现在,我们的领域模型已经覆盖了事件风暴中发现的所有领域概念,但是,你是不是觉得这个图已经有点乱,不太容易理解了呢?
划分模块
之所以这个图不容易理解,是因为很多实体和关联混杂在一起,形成了一个“蜘蛛网”。但人的认知能力是有限的,面对这样一张复杂的对象网络,就产生了认知过载(cognitive overload)。
解决这一问题的方法就是“模块化”。也就是说,把模型中的业务概念组织成若干高内聚的模块(module),而模块之间尽量低耦合。
在UML中,可以用包来表示模块。包的符号是下面这样:
包的内部可以包含实体,也可以包含另外的包。在UML中,任何需要对元素进行分组的场合,都可以使用包。经过一番讨论,我们采用如下方式划分模块:
也就是说,我们把模型分成了4个模块,分别是租户管理、组织管理、项目管理和工时管理。是不是感觉清晰多了?
有了模块,我们就可以从两个层面理解模型。
第一个是宏观层面。宏观层面只关心模型中有哪些模块,以及模块间的依赖关系,不关心模块内部的细节。为了达到这个目的,我们可以画出更宏观的包图。像下面这样:
这里我们又引入了一个新的符号,就是带箭头的虚线。这在UML中表示依赖(dependency)关系,箭头由依赖方指向被依赖方。
从图中我们看到:
首先,所有模块都依赖租户管理。这是因为,为了将不同租户的数据区别开来,每个模块中的实体都要说明自己是哪个租户的;
其次,项目管理依赖组织管理。这是因为项目经理、项目成员等概念都依赖于组织管理中的员工;
还有,工时管理既依赖组织管理又依赖项目管理。这是因为工时管理要说明(组织管理中的)哪个员工在(项目管理中的)哪个项目上投入了多少工时。
我们要注意,依赖关系和前面讲的关联关系是不一样的。关联表示的是数据上的导航关系。例如,当我们说组织和员工之间具有一对多关联的时候,就意味着,由组织可以找到下面的员工,由员工也可以找到所属的组织。
依赖表示的意思更为广泛。如果A、B两个元素,有了A才能有B,那么就可以说B依赖于A。
我们可以认为关联是一种特殊的依赖关系,但依赖还有别的形式,比如说项目管理模块依赖于组织管理模块,两者之间未必有直接的数据导航关系,可能只是项目管理调用组织管理,获得需要的数据。
理解模型的第二个层面是微观层面,也就是深入到模块内部,了解实体和关联等等的细节。
通过这种分而治之的方法,我们可以在一定程度上管理复杂性,解决认知过载的问题。如果模型更加复杂,那么还要进一步采用限界上下文来处理,这个会在第三个迭代再介绍。
现在,领域模型基本完成了。为了完善开发过程,我们还要再进行两个实践:一个是完善业务规则,另一个是建立词汇表。
完善业务规则
我们在做事件风暴时就开始识别业务规则了,在领域建模中又识别出了更多的规则,所以现在我们需要把这些规则补充到业务规则表之中,如下表:
其中,从R006开始,是我们领域建模阶段新识别出的领域规则。
建立词汇表
接着我们来建立词汇表,也就是把事件风暴和领域模型中重要的词汇列成表。为什么要建立词汇表呢?主要是有两个作用。
首先,我们需要通过词汇表来规范领域模型中的词汇。同一个词,可能会在领域模型中出现多次,时间久了,就可能不一致,因此需要进一步规范。
第二,是可以用于后续编程中的命名。按照DDD的要求,程序中的各种命名也需要统一,并且需要与领域模型中保持一致。我们会在词汇表中列出英文全称和缩写,以达到这个目的。
词汇表是保证统一语言的重要手段。
目前为止的重要词汇,都列到下面这张词汇表里了。
总结
好,这节课的主要内容就讲完了,下面来总结一下。
今天我们在上节课的基础上,继续对项目管理和工时管理进行领域建模。在这个过程中进一步深化了对“多对多”关联的理解:我们可以通过引入一个表示关联的实体,将一个多对多关联拆成两个一对多关联。
我们还通过取员工上级的逻辑,学习了为实体添加操作。当模型变得比较复杂时,可以把模型划分成多个模块,使模型更容易理解。
在建立完领域模型之后,我们还进行了两项实践:完善业务规则和建立词汇表。很多朋友过去并不重视这两个实践,然而,它们对落实DDD实践,具有很重要的作用,一定要引起重视。
最后还要强调一点,领域建模易学难精,需要不断地练习才能熟练掌握。希望你能拿自己的实际项目练习一下。
思考题
1.今天我们讲过,一个多对多关联可以拆成两个一对多关联,那么你觉得什么时候应该拆,什么时候不拆呢?
2.客户经理是可以更换的,如果要保留更换历史,模型图应该怎样改呢?
好,今天的课程结束了,有什么问题欢迎在评论区留言。下节课,我们再从理论上深入理解一下“什么是模型”以及“什么是模型驱动设计”。
- Jxin 👍(5) 💬(2)
1.我的观点是多对多全都可以拆,只是有时候没必要拆。以四色角度,部分ppt 描述型模型可以不拆(ddd角度,部分值对象),因为只是承接一些不变的数据,拆与不拆既不影响代码实现,也不影响展示。为什么是部分,因为有些多对多,本来在业务的认知就有个概念把它们拆了,那么与业务认知保持一致就好。 2.客户经理更换这个,日志倒还好,追加操作日志能实现。但我觉得这里想表达的应该不是这个难题, 而是客户经理的关联关系在其他场景被引用的难题。假如我要考核某个员工一年时间内作为客户经理维系的客户数量,以及产生的订单,如果客户经理关连关系会变,那么如果年底该员工所有的客户都划给了其他人,那他关联出的数据就是0(历史数据因为关联关系变动而受牵连)。有两种做法,第一种,所有使用场景记录关系快照,比如产生订单,订单上不能只记录客户,还要记录当时的客户经理;第二种,限制修改关联关系。对关联关系建模,同一个时段只能有一个生效的关联关系,关联关系只能新建和作废,不能修改。如此一来,所有引用场景只需要带上当时使用的操作时间,就能捞到对应时间生效的关联关系,从而找到“当时的客户经理”。 3.完善业务规则和建立词汇表,好主意,下来试试。 我自己的话,业务规则主要靠故事卡(主要还是个人精力有限)。 词汇表,建模画图时中英全写(还是单独一张词汇表灵活,能多加些纬度信息)。
2022-12-27 - leesper 👍(8) 💬(2)
抓虫时间到: (1)第7幅图(处理“为项目分配员工”——这行文字下面那幅图),图中员工类图上白色部分用于填写属性和方法那里出现了“项目经理”四个字,应该删除 (2)第10幅图(处理“登记工时”——这行文字下面那幅图),图中最下面圆圈圈住的部分,业务规则中第一条“员工只能在被分配的项目上报工时”和第二条“只能在被分配的项目上报工时”重复了,应该删掉其中一条 是人就会有失误,没有故意为难编辑同志的意思。一是,这只是我的学习习惯——带着问题学习。任何让我觉得心生疑窦的地方,我都要思考一下是我理解错了还是材料有问题,这样我印象才深刻。 二是,ThoughtWorks老师讲的课都太过精彩,我不想错过钟老师的任何一个文字,因此读的比较仔细。 思考题: 1. 当我需要快速建模,了解业务知识的时候,可以先不拆多对多关联;而当我需要深入细节,用于指导开发的时候,就要拆分了,目的决定手段 2. 客户经理是可以更换的,意味着一个客户可能被多个客户经理服务过,一个客户经理服务过多个客户,他们是多对多关系,如果要保留更换历史,就要把多对多关系拆成两个一对多关系,并记录在“客户服务历史”这个实体上,且同样地,服务时间段不能有重叠
2022-12-17 - 赵晏龙 👍(11) 💬(1)
写一篇课程治好了我多年的UML恐惧症
2022-12-17 - aoe 👍(5) 💬(1)
划分模块很精彩,确实更容易理解了。 感叹模块是怎么划分出来的! DDD 像写小说,细节错综复杂,但又逃不出大框架。 一个会 DDD 的程序员好像是导演、作家,想象力丰富!
2022-12-20 - 阿昕 👍(3) 💬(1)
1.多对多关系存在业务规则时,需要拆分出来一个承载的实体,变成两个一对多的关系; 2.增加一个客户经理的实体,修改之后的模型图:https://boardmix.cn/app/share?token=GR6wYS0X3IRCHXMqi7vgLwGUF_JZb_jotnRvLrCo8glJXQErpLRs2XY6d3jPZxHnxMyzrMb_9nVr2OV8VSR2_5eevpKeor0MC80Mny8EOGU=&inviteCode=kjG6ak
2023-01-09 - 狂躁程序猿 👍(3) 💬(1)
钟老师,有些不太理解,这个过程不应该是识别Entity、VO,再划分领域的过程吗?为什么用类图、包图来解释,感觉这本不是DDD的本意
2022-12-20 - Jaising 👍(2) 💬(2)
还在犹豫要不要入手的同学可以看过来,就领域建模这两节值回票价! 看钟老师的专栏忍不住想记点什么,最小闭环的领域建模结束了,做个小结请钟老师指正下: 领域驱动设计的核心是领域建模,领域建模创造性地解决了软件分析与设计割裂的历史遗留问题,通过领域模型关联业务需求与软件实现,达成需求与交付的一致性,通过可视化模型快速消化业务知识,建立统一语言,简化认知统一认知指导开发,为业务人员与技术人员搭建起可沟通与监督的桥梁。 继续做个两千字小笔记粗浅地谈谈领域建模学习与实践:https://juejin.cn/post/7186685036127748155 实践中对事件风暴又有了新的想法,更新下笔记:https://juejin.cn/post/7182111319967924282
2023-01-10 - blusจุ๊บ 👍(1) 💬(1)
划分模块是在定聚合还是定限界上下文,这儿的模块概念怎么理解,和代码落地有对应关系吗?还是只是简单圈定一个边界而已,还是这个模块就是聚合或限界上下文
2023-07-29 - GeekPoplar 👍(1) 💬(1)
搜了一下,"n/a"是“not applicable”的意思,在词汇表里出现"n/a"意味着那一部分的词汇不需要设置简称。
2022-12-18 - 存在 👍(0) 💬(1)
想问下老师这里划分模块的依据是什么,工时管理为什么要单独划分一个包,而不是放到项目管理里
2024-08-25 - J.Smile 👍(0) 💬(1)
看了一遍评论区,也没有看到有人提出这个问题: 项目成员的"投入百分比"属性,只是代表了在一个项目上投入的百分比,而如果想满足这条业务规则,应该要把这个成员投入到多个项目的百分比加起来不超过100%。 -------- 老师,是这么理解吗?还是说这个百分比仅仅指的是在一个项目上项目成员所投入的百分比呢?
2024-07-02 - Geek_fe41a4 👍(0) 💬(1)
package和module在具体规划应用程序架构的时候该怎么选择
2024-02-19 - Breakthrough 👍(0) 💬(1)
钟老师,识别出词汇表和业务规则是为了后面做开发的时候确定字段和约束条件吗?这是必须要做梳理的事情还是可以跳过的步骤
2023-07-20 - 末日,成欢 👍(0) 💬(1)
老师,为什么工时记录没有和项目成员关联在一起,而是和成员关联在一起
2023-06-12 - yoon404 👍(0) 💬(1)
依赖关系是不是有区分直接依赖和间接依赖?
2023-02-10