5|领域建模实践(上):怎样既准确又深刻地理解业务知识?

news2024/10/2 10:26:57

上节课咱们完成了事件风暴,梳理了系统的行为需求。但你可能也发现了,其实还有些微妙的业务概念还没有澄清,这就要靠领域建模来完成了。

建立领域模型是 DDD 的核心。要建好领域建模,需要理论和实践相结合。由于我们的模型有一定的复杂性,所以我把领域建模的实践分成两节课。完成实践以后,我们会再用一节课,从理论层面让你进一步深化对领域建模的理解。

今天这节课,我们先通过租户、组织和员工这几个部分学会基础的建模方法。

领域建模中的一些基本概念

我们先来理清领域建模中的一些基本概念,方便你理解下面的建模实践。领域建模主要有两个目的:

将知识可视化,准确、深刻地反映领域知识,并且在业务和技术人员之间达成一致;

指导系统的设计和编码,也就是说,领域模型应该能够比较容易地转化成数据库模式和代码实现。

而我们建立领域模型,主要是要识别领域对象(domain object),领域对象之间的关系,以及领域对象的关键属性,必要的时候还要将领域对象组织成模块。当然了,还有一些比较深入的内容,我们会在迭代二中再讲。

那么,什么是领域对象呢?我们系统中要处理的各种“事物”就是领域对象。比如说项目、员工、账户等等。这些对象都反映了名词性的概念。

其中,有些名词化了的动词也是领域对象。比如说我们进行了一笔支付操作,并且想把这笔操作记录下来。这时,“支付”也是领域对象。支付本来是动词,但这里实际上是要把一笔支付的信息记录下来,在这里就把“支付”当名词用了。

领域模型是用领域模型图来表达的,通常用 UML 来画。UML 是“统一建模语言”的意思,英文是 Unified Modeling Language,是面向对象建模的国际标准。其中,领域对象用下面这个符号来表示:

这个符号表示“员工”对象。其中第一栏是领域对象的名称,第二栏列出了对象的属性(attribute),姓名、性别都是员工的属性。

严格地说,在 UML 中,这个符号叫做“类”(class)。比如说,张三是员工,李四也是员工,我们可以说,员工指一类事物。这时我们可以用 UML 中的术语说,员工是领域对象的一个类,张三和李四是这个类的实例。在领域建模过程中,我们说领域对象时,有时指类,有时指实例,一般可以通过上下文来区分。

此外,DDD 中将领域对象又分成实体(entity)和值对象(value object)。值对象我们等到第二个迭代再讲,这个迭代我们只关心实体。我们前面说的“员工”“账户”等都是实体。

由类和他们之间的关系组成的图叫做类图,这也是领域建模里用到的最主要的图。

下面我们就开始通过画类图的方式进行领域建模。同样,你扮演架构师,我扮演产品经理。

初步识别实体

我们可以从上节课中识别的领域名词入手,分成几部分来建模。我们先考虑租户、组织和人员。上节课的图是这样的:

首先,你可以先假定每个领域名词都是一个实体,把它们用类的符号画出来。如下图:

这就可以算作一张最简单的类图了。你可能注意到了,这里并没有写属性。其实,在领域建模阶段,我们主要关注的是实体和它们之间的关系。如果实体的名字已经能清晰说明实体的含义,那我们就不需要加属性了。如果名字还不足以充分表达含义,我们可以写几个关键属性,来辅助说明。

另外要注意,我们这里只是简单粗暴地假定了领域名词就是实体。通过后面的分析,我们还会发现,有些名词不是实体,有些要转换成其他形式。

识别“一对一”关联

现在,我们来识别实体之间的关系。先来看看租户和企业。

首先,你问我:“租户和企业这两个概念有没有关系呢?”我回答说:“肯定是有关系的。”于是你可以在它们两个之间画一条线,表示它们之间有关系。

然后,你又问了我两个有关业务的问题。第一个问题是:“一个租户最多可以对应几个企业?”我回答说:“只能对应一个企业。”于是,你在企业那一端写了一个 “1” 来表示。

然后你反过来问第二个问题:“一个企业可以作为几个租户?”我回答说:“一个企业也只能作为一个租户。”于是你在另一边也写上 “1” 。

这时,我们可以说,租户和企业具有一对一的关系。

这里的两个 “1” ,在 UML 中称为多重性(multiplicity)。那么,这种关系整体上呢,在 UML 的术语里叫做“关联”(association)。后面我们都用这种严格的说法,说成一对一关联。

这时你可能又想到了一个问题:“为什么不把租户和企业合并成一个概念呢?”

在有些情况下,一对一的两个实体确实是可以合并的。这取决于这两个概念的关注点是否相同。

但在我们的需求里,租户关注的是客户和提供云应用的供应商之间的协议,背后隐含的需求可能是云平台要为这个客户分配多少硬件资源、怎样收费、提供哪个级别的备份等需求。假如这不是一个基于 SaaS 的应用,根本不会有租户的概念。

而另一方面,企业是组织结构管理中的一个概念,即使不基于 SaaS,企业这个概念也存在。所以这是两个不同关注点的概念,不应该合并。

识别“一对多”关联

下面我们再分析企业和开发中心的关联关系。这时你同样考虑了两个问题。首先,一个开发中心可以属于多少个企业呢?只能属于一个企业。这和上面的画法相同。

第二个问题是,一个企业可以有多少个开发中心?答案是可以有很多个。这可以在开发中心一端写一个 “*” 来表达。

这时,我们可以说,企业和开发中心具有一对多关联。

接着你用同样的方法画出了开发组,如下图:

现在咱们来考虑部门。你可能会发现,部门这个词其实用得不太准确,因为开发组也可以认为是部门。其实按照这里的需求我们想表达的是财务部、人事部等区别于开发中心和开发组的部门。

然后你又问我了:“在业务上,怎么称呼这种区别于开发中心的部门呢?”我说:“业务上一般叫做直属部门。”于是你在图中增加了直属部门。

然后,你把员工也加上了。

就这个图的含义而言,一个员工可以属于开发组,也可以属于直属部门,好像已经满足了需求,但是仔细想一下,你可能就会发现两个问题。

第一个问题是,如果将来组织层级发生变化,比如说在开发中心和开发组之间又增加了一层开发团队;或者有些开发组不属于任何开发中心,而是直属企业,那么这个模型就要修改了。也就是说,这个模型不容易适应组织层级的变化。

第二个问题是,一个员工其实可以不属于开发组,而只属于开发中心,比如开发中心的主管就是这样。同理,企业总经理也只属于企业本身而不属于任何下属部门。那么为了表达这种关系,我们就要再增加两条表示关联的线。

线越多,图就越杂乱。也就是说,这个模型不够简洁。

那么怎么解决这两个问题呢?我们要对这个模型进行抽象。

进行抽象

你可能已经注意到了,企业、开发中心、开发组、直属部门,其实都是组织结构中的节点而已,从这一点来说,他们是有共性的。

于是,你问我:“既然它们有共性,能否起一个统一的名字呢?也就是说,企业、开发中心、开发组、直属部门,可以统称为什么呢?”我回答说:“在业务上可以统称为组织。”所以你把模型改成下面这个样子:

由于开发中心、开发组等都是组织,所以只画出组织就可以了,模型图变得很简洁。但是你马上就发现,无法区分出一个组织到底是开发组还是开发中心了,也就是归纳了共性,但个性却丢了。

这时你问我:“一个组织是开发中心,另一个是开发组,那么在业务术语上可以说,这两个组织具有不同的‘什么’呢?”我告诉你:“可以说,这两个组织具有不同的组织类别。”

于是你画出下面的模型图。

也就是在这个模型中增加了一个组织类别的实体。企业、开发中心、直属部门等都是组织类别的实例。一个组织类别下可以有多个组织,而一个组织只能属于一个组织类别。比如说,开发组这个组织类别,下面可以有开发一组、开发二组等等很多具体的组织。

为了怕以后读这个模型图的人不理解什么是组织类别,我们还可以加一个注释,用来举例说明有哪些组织类别。在 UML 中,注释用折角的矩形表示,和被注释的实体之间用虚线连接,如下图:

识别“自关联”

然后,你又发现,这个图还不能表示企业、开发中心、开发组等之间的上下级关系。这可以用组织这个实体上的“自关联”来表达。画出来是下面这个样子:

在这个图中,组织实体上有一个自己到自己的一对多关联。这个关联翻译成自然语言可以这么说:一个组织可以有多个组织作为自己的下级;而一个组织只能有一个组织作为自己的上级。这样就表现出了上下级的层级关系。这种一对多的自关联,实际上表达的是一种树形结构。

另外,在这个自关联的两端,有上级和下级两个词。它们在 UML 里称为“角色”(role)。也就是说,在这个关联的 “1” 端的组织充当上级这个角色,在另一端充当下级角色。如果没有这两个角色名称的话,我们就不知道是一个上级有多个下级,还是一个下级有多个上级了。

增加“约束”

另外,为了说明“一个开发中心下面有多个开发组,而不是一个开发组下面有多个开发中心”这个业务规则,我又另外加了一个注释。如下图:

你可能发现了,这个注释和上一个有些区别,它的内容用大括号括起来了。在 UML 中,用大括号括起来的内容称为“约束”(constraint)。和一般性的注释不同,凡是约束,必须在程序中的某个地方进行实现。约束也是一种业务规则,应该加进前面讲过的业务规则表。

不过,细心的你又发现,关于租户还有两个问题没有在这个模型中表达出来。

第一个问题是,没有说明“只有企业才能作为租户,其他类别的组织不能作为租户”;另一个问题是,作为多租户系统,其实每一个实体都应该与租户有一个关联,说明这个实体是属于哪一个租户的,这样才能把不同租户的数据区分开。但是,如果把这些关联都画出来,模型图中的线条就会太多了,变得非常混乱。

于是,你又添加了一个注释和一个约束来说明上面两个问题。有了这个注释,租户和组织上原来的那个关联也可以省去了。如下图:

你看,这个模型既简洁又灵活,解决了我们前面说的员工实体上有多个关联以及组织层级难以扩展这两个问题。

识别“多对多”关联

你问我:“管理员和人事人员其实也是员工是吧?”我说:“是。”但这时你可能会纠结,这两个东西到底是不是实体呢?

于是你又问我:“从业务的角度,可不可以认为管理员和人事人员其实都是员工担任的岗位呢?”我说:“这个理解很正确。”于是,你就画出了下面的图:

这个图表示,一个员工可以担任多个岗位,而一个岗位也可以有多个员工担任。员工和岗位之间是“多对多”关联。我表示,这完全符合我对业务的理解。

更丰富的“多重性”

现在,我们再看一下多重性问题。前面说过,关联两端的 “1” 或者 “*” 称为“多重性”。比如说下图中组织和员工之间就是一对多关联:

你还可以问两个问题,把多重性进一步细化。

第一个问题是:“一个组织可以没有任何员工吗?”我回答:“业务上允许先建立一个组织,但暂时不往里面分配任何员工。”那么,你可以在员工那一端的 “” 前面加上 “0..”,变成 “0..” 。如下图:

这里的 “0..*” 我们也可以这样理解:一个组织最少有 0 个员工,最多可以有很多员工。

类似地,你可以从关联的另一个方向再问第二个问题:“一个员工可以不属于任何组织吗?”我回答:“不行。一个员工必须属于一个组织。”于是,你把另一端改成了 “1..1” 。

这里的 “1..1” 表示,一个员工最少要属于一个组织,最多也只能属于一个组织。

通过上面的方法,多重性的语义就变得更加丰富了。在实际项目中,团队可以自行决定,把多重性识别得粗一点,只写 “1”和 “” ;还是细一点,识别出 “1..1” “0..” 等等。一般来说,如果目的是在短时间内大致了解业务概念,就可以做粗一点;如果是为了指导具体的开发,则可以做细一点。在后面的例子中,我们都按照比较细的方法来做。

丰富了多重性以后,整个模型成为了下面的样子:

现在,我们终于完成了关于租户、组织和员工的领域模型。这节课就先到这,我们下节课再完成领域模型的其他部分。

总结

下面我们总结一下这节课的内容。

我们今天首先解释了用于领域建模的几个基本概念,包括 UML、领域对象(domain object)、实体(entity)、类(class)、实例(instance)等。

然后我们从事件风暴识别出的领域名词出发,开始进行领域建模。首先假定每个领域名词都是一个实体。然后识别实体之间的关联。关联可以分为三种:一对一、一对多和多对多。而这些不同的关联,可以用多重性来表达。

假设有 A 和 B 两类实体,你可以通过问四个问题,来把多重性搞清楚:

一个实体 A 最多可以对应多少个实体 B?

一个实体 A 最少可以对应多少个实体 B?

一个实体 B 最多可以对应多少个实体 A?

一个实体 B 最少可以对应多少个实体 A?

这些问题看起来很“机械”,但对于初学者来说,关联关系是非常容易搞错的。所以如果你还不是很熟练的话,建议你老老实实地问自己这四个问题,一边问,一边把多重性写出来,这样可以保证不出错。

还有一种关联是实体自身上的“自关联”,可以表达由某种实体组成的树状或网状结构。这比一般的关联稍微难理解一些,但熟练以后就容易了。

在建模过程中,我们还可以通过“抽象”,找出领域名词中并没有直接揭示出来的实体。例如把企业、开发中心等抽象成组织和组织类别,把管理员、人事人员等抽象成岗位。这样的模型更能反映出业务的本质,从而更加灵活。通过这种抽象的过程,使模型和业务不仅仅是“形似”,更是“神似”。

我们还可以通过增加注释和约束,使模型中的业务知识更加丰富和准确。其中,约束是一种特殊的注释,它的内容必须以某种形式在代码或数据库中实现。约束也属于我们在之前说的业务规则,需要补充到业务规则表中去。

思考题

  1. 为什么领域建模要由业务人员和技术人员一起协作完成呢?

  2. 通过抽象思维,可以抓住需求的本质,达到“神似”,你可以在遇到过的实际项目中,举出类似的例子吗?

好,今天的课程结束了,有什么问题欢迎在评论区留言,下节课,我们继续进行领域建模的实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1412924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5.【SpringBoot3】文件上传

1. 文件上传到本地 需求分析 在用户更换头像或发布文章时,需要携带一个图片的 url 地址,该 url 地址是当用户访问文件上传接口,将图片上传成功后,服务器返回的地址。所以,后台需要提供一个文件上传接口,用…

Unity2020.3打包ARFoundation问题记录

文章目录 前言一、打包成功后再打包失败(重启工程后)二、URP管线总结 前言 在Unity 2020.3版本中使用ARFoundation进行打包时,遇到一些小问题。本文简要记录了其中一些问题及解决方法,以备不时之需。 一、打包成功后再打包失败&a…

嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM平台编程第四天-Bootloader编写2(物联技术666)

链接:https://pan.baidu.com/s/1eb94AaDM-cIZsbr929Isbw?pwd1688 提取码:1688 上午:bootloader的编写 下午:bootloader 教学内容: 1、vivi vivi有关nandflash MTD分区表控制命令: part show: …

python爬虫采集下载中国知网《出版来源导航》论文文献下载_PDF文档_数据采集知网爬虫论文Python3

时隔一年,很久没更新博客了。今天给大家带来一个python3采集中国知网 :出版来源导航 这个是网址是中国知网的,以下代码仅限于此URL(出版来源导航)采集,知网的其他网页路径采集不一定行,大家可以…

C++:vector容器(memcpy浅拷贝问题、迭代器失效问题)

文章目录 一. vector 的介绍二. vector 的使用1. string 和 vector<char> 的区别2. 为什么 vector 没有 find() 接口 三. vector 的模拟实现1. vector 的基本框架2. memcpy 和 memmove 的浅拷贝问题3. vector 迭代器失效问题4. 模拟代码 一. vector 的介绍 vector 的文档…

mysql调优-Join多种连接方式

简单嵌套循环连接 r为驱动表&#xff0c;s为匹配表&#xff0c;可以看到从r中分别取出每一个记录去匹配s表的列&#xff0c;然 后再合并数据&#xff0c;对s表进行r表的行数次访问&#xff0c;对数据库的开销比较大 索引嵌套循环连接 这个要求非驱动表&#xff08;匹配表s&…

【Python】01快速上手爬虫案例一:搞定豆瓣读书

文章目录 前言一、VSCodePython环境搭建二、爬虫案例一1、爬取第一页数据2、爬取所有页数据3、格式化html数据4、导出excel文件 前言 实战是最好的老师&#xff0c;直接案例操作&#xff0c;快速上手。 案例一&#xff0c;爬取数据&#xff0c;最终效果图&#xff1a; 一、VS…

EMQX 单机及集群搭建

目录 1. 通过 Yum 源安装&#xff08;CentOS7 单机安装&#xff09; 1.1. 通过以下命令配置 EMQX Yum 源&#xff1a; 1.2. 运行以下命令安装 EMQX&#xff1a; 1.3. 运行以下命令启动 EMQX&#xff1a; 1.4. 访问 http://192.168.88.130:18083&#xff0c;默认用户名: adm…

【斯坦福计网CS144项目】Lab2 实现一个简单的 TCP 接收类

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

数据结构(C语言版)代码实现(四)——静态单链表的部分代码实现

目录 参考材料、格式 头文件SLinkList.h 库、宏定义、函数类型声明 线性表的静态单链表存储结构 按值查找 初始化静态链表 分配空间 回收空间 打印已用链表中的元素 求集合(A-B)U(B-A)中的元素&#xff08;重点介绍&#xff09; 调试过程 修改报错与警告 调试 完整…

何恺明 ResNet 引用量正式破20万!!!

注: 本文转自微信公众号 BravoAI (专注AI资讯和技术分享), 原文网址: 何恺明 ResNet 引用量正式破20万!!!, 扫码关注公众号 谷歌学术显示, 截止到 2024年1月26日, 何凯明 ResNet 一文引用量正式突破 20W!!! 更为惊人的是, 从论文发表到今天, 不过7年!!!‍‍‍‍‍‍‍‍‍‍‍‍…

6轴机器人运动正解-逆解控制【1】——三种控制位姿的方式

概览&#xff1a; 通过运动学正解控制机器人运动通过运动学逆解控制机器人运动一个简单的物体搬运&#xff08;沿轨迹运动&#xff09; 后续会陆续更新&#xff08;本例仅供学习交流用&#xff09; 一、6轴机器人 二、运动正解控制 通过修改各个轴的角度&#xff0c;实现末…

演示黄金票据,使用普通账户导入黄金票据创建域管理员

前提域搭建好了&#xff0c;域名是lin.com 首先我进入的是本机的用户不是域用户 我要是用本地用户&#xff0c;本地用户拿的票告诉我们这个TGS服务说我是域管账户administrator&#xff08;需要拿到域用户的哈希&#xff09; 此时进入到预控主机中&#xff08;人家是正儿八经…

Google Chrome RCE漏洞 CVE-2020-6507 和 CVE-2024-0517 流程分析

本文深入研究了两个在 Google Chrome 的 V8 JavaScript 引擎中发现的漏洞&#xff0c;分别是 CVE-2020-6507 和 CVE-2024-0517。这两个漏洞都涉及 V8 引擎的堆损坏问题&#xff0c;允许远程代码执行。通过EXP HTML部分的内存操作、垃圾回收等流程方式实施利用攻击。 CVE-2020-…

计算机网络 第6章(应用层)

系列文章目录 计算机网络 第1章&#xff08;概述&#xff09; 计算机网络 第2章&#xff08;物理层&#xff09; 计算机网络 第3章&#xff08;数据链路层&#xff09; 计算机网络 第4章&#xff08;网络层&#xff09; 计算机网络 第5章&#xff08;运输层&#xff09; 计算机…

MySQL十部曲之一:MySQL概述及手册说明

文章目录 数据库、数据库管理系统以及SQL之间的关系关系型数据库与非关系型数据库手册语法约定 数据库、数据库管理系统以及SQL之间的关系 名称说明数据库&#xff08;Database&#xff09;即存储数据的仓库&#xff0c;其本质是一个文件系统。它保存了一系列有组织的数据。数…

【第四天】蓝桥杯备战

题 1、求和2、天数3、最大缝隙 1、求和 https://www.lanqiao.cn/problems/1442/learning/ 解法&#xff1a;字符串方法的应用 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public static void main(String[] args) {Scann…

xshell无法连接linux,查询本机ip时出现<NO-CARRIER,BROADCAST,MULTICAST,UP>

在用xshell连接虚拟机VMware中的linux时&#xff0c;发现昨天还能连通的&#xff0c;今天连接不了了 我寻思应该是网卡配置出问题了&#xff0c;就去终端ip addr试了一下&#xff0c;果然发现问题&#xff0c;ip 查看网卡ens33就发现出现ens33:<NO-CARRIER,BROADCAST,MULTI…

操作符详解(上)

目录 操作符的分类 二进制和进制转换 2进制转10进制 10进制转2进制数字 2进制转8进制 2进制转16进制 原码、反码、补码 移位操作符 左移操作符 右移操作符 位操作符&#xff1a;&、|、^、~ 单目操作符 逗号表达式 操作符的分类 • 算术操作符&#xff1a; …

无法获得dpkg前端锁、Linux之E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它?(解决方法)

无法获得dpkg前端锁的解决方法 sudo rm /var/lib/dpkg/lock sudo rm /var/lib/dpkg/lock-frontend sudo rm /var/cache/apt/archives/lock 输入以上三个命令即可解除占用。解除后&#xff0c;继续运行apt命令&#xff0c;已经顺利运行了。解除前端锁后&#xff0c;Linux之E: 无…