前言
上一篇:从领域驱动到模型驱动中我们讨论到,领域驱动设计的核心思想是保持业务-模型-代码的一致性,模型作为沟通业务和代码的工具,至关重要,今天这篇文章就来讨论DDD中建模的一些思考和方法。
什么是建模
虽然看到这篇文章的读者都是IT从业人员,大家都知道建模是怎么回事,但我还是想先对建模这件事讨论几句,我理解的建模,是对业务中数据流的合适描述。业务是公司的经营活动,但是在软件工程范畴中,当业务人员找到开发说要开发个系统来管理业务的时候,其本质是需要一个能管理业务中数据的系统。因为软件系统唯一能处理的只有数据,它不可能做到把货物从A运到B类似的事情,但它可以通过把“货物从A运到B”这件事情通过数据的方式传递给合适人去完成。因此,我们构建软件系统时唯一关注的是如何处理业务中的数据。业务中的数据具有历史性,即其会随着业务的流转而变化,衍生出新的数据。将业务全周期中的所有数据视为一个集合,不同的业务阶段、业务操作、业务视角都是关注集合中部分数据并筛选出来形成一个子集。例如电商场景中,一个完整的订单流程包含用户浏览商品、下单、付款、物流等阶段,包含商品名称、用户地址、付款渠道、物流明细等数据。但在不同的业务阶段,不同的业务角色只关注部分数据,但因为各参与方都使用同一套系统(同一套数据),因此系统还要关注模型间数据的流转路径。将数据集合进行划分,并描述其变化路径,这就是建模。
什么算好的建模
模型包含数据和行为。模型本质上是一个集合,是为了最大限度满足使用方信息完备要求并且使得系统管理代价最小的平衡结果。这里说得系统管理代价,实际上是指数据规模、信息密度超出人脑处理信息上下文的容量后带来的复杂度急剧上升,从而无法做出有效、正确决策,使得系统和业务的一致性越来越低,使软件系统在错误的道路上越走越远。因此建模的评判标准有三个:1、和业务保持一致;2、信息完备;3、管理代价最小。
“和业务保持一致”:
这里就是DDD中提倡的“共同语言”,创建的模型应该能和业务中的概念术语一一对应,不能自己创造、篡改、臆想出一个模型,应该是业务流程中确实存在、需要的数据子集。数据的流转应该符合业务实际中的数据变化实际,而不能为了技术、性能、方便等理由而强行创建出一条数据流。
“信息完备”是指应该从业务流程出发,将上下文需要的数据都在模型中体现。如果某个数据在节点3处使用,而它是在节点1出产生的,那么它就应该沿着节点1-节点2-节点3的路径流转,而不是在节点3处再去节点1中获取。
“管理代价最小”主要指的是系统复杂度要最小,复杂度的度量有两个指标:模型数量和模型关系,且模型关系对复杂度的影响远大于数量。这里说得复杂度仍然是描述开发人员对系统的理解、掌握、改造时要处理的信息大小。为什么说关系对复杂度的影响远大于数量呢?
一本新华字典的体量远大于一本红楼梦小说,但理解红楼梦的复杂度远大于新华字典。因为字典中收录的汉字都是独立的,前后并无强烈关联。但红楼梦中包含的人物、故事纷纭复杂,想要读懂甚至修改红楼梦需要大师级的文学素养。因此建模时我们应该着力避免模型间的关系,必要时可以用模型的数量来规避关系。
建模方法
由于DDD追求建模和业务的一致性,且愿意使用模型的数量来置换关系以追求系统的复杂度降低,DDD中常采用的建模方法是CQRS(Command Query Responsibility Segregation,命令查询职责分离)和命令-事件模型,。CQRS将系统的功能分为两类:写操作和读查询,每个写操作视为一个命令,是真正的业务流程,只针对写操作建模。而读请求不会对系统造成更改,可以直接从数据层取数据组装返回。大部分系统属于写少读多,运用CQRS的方式会大大降低建模难度。
命令-事件模型将系统中的写操作分类两部分:命令、事件,命令由外部触发,其携带了上下文数据,命令通过执行对系统的状态(数据)产生了变更,由此产生了事件,该事件携带了一些数据,可能被系统中某些部分关注,从而做出反应,这些反应通常也会使用执行命令的方式完成,由此循环往复,把系统中的所有写操作使用命令-事件模型描述清楚。
例如,用户选择商品后点击下单,对于系统来说,用户触发了一个命令,该命令包含了用户和商品等上下文数据,系统需要执行该命令。通过某个命令执行器,将“创建订单”命令执行完毕后,系统中将会保存一份订单数据,还需要广播一个事件“订单已创建”,其将包含已创建订单的关键信息。用户积分管理业务关注“是否有订单创建”这件事,通过合适的方式监听到“订单已创建”事件,将自己要进行的业务操作,也封装成一个命令,通过专有的命令执行器处理自己的业务。
为何命令-事件模型适合DDD?
命令-事件模型只是一种建模方法,其可以脱离DDD使用,但因为其具有的几个特点,使得其非常适合应用在DDD中。
1、命令-事件模型只针对写操作建模,符合CQRS思想。
2、命令-事件的万能句式是:xxx操作触发了yyy事件,发生yyy事件时做zzz操作。使用这样的句式能将业务流程、内里变化描述得非常清晰,而且只需要命令、事件两类模型即可完成建模,意味着模型和业务保持一致非常容易。
3、由于建模结果容易理解,且具有很强的业务表达能力,意味着使用代码描述模型将会非常容易,因此更能保证代码和模型的一致性。
4、命令-事件天然具有对应关系,而事件到命令可通过命令总线方式隔离、解耦,使得系统中模型间关系减少,大大降低了系统复杂度。
总结
本篇主要分析DDD中对建模的一些要求,建模首先要保证和业务的一致性,齐次通过减少关系来降低结果复杂度。CQRS+命令-事件模型是目前DDD中的操作性强、适应范围广的,符合DDD核心要求的可落地建模方法。这里推荐一个B站视频:Java8 到 .NET8 - 掌握这个模型你就能设计一切,详细介绍了如何使用命令-事件模型进行建模的细节。