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.客户经理是可以更换的,如果要保留更换历史,模型图应该怎样改呢?
好,今天的课程结束了,有什么问题欢迎在评论区留言。下节课,我们再从理论上深入理解一下“什么是模型”以及“什么是模型驱动设计”。