Skip to content

风暴建模

更新: 3/15/2025 字数: 0 字 时长: 0 分钟

用事件风暴完成领域建模,这一过程我喜欢称之为风暴建模。

为什么要进行风暴建模?风暴建模不就是一堆人坐一起进行开会吗?这是我最初在学习DDD时经常有的一个想法。

但相信我,风暴建模绝对不是这么简单的东西,就目前我的认知下,风暴建模是理清业务,将业务转换成为代码的最佳方式,甚至,我认为风暴建模可以作为我们面对生活中许多问题的方法论。

那么究竟什么是风暴建模呢,让我们来聊一下。

流程

风暴建模很像项目组里的大佬带着一群对业务理解不那么清楚的小白,然后大家一起在大佬的带领下一起试图将产品给出的抽象业务转化成程序员可以书写的代码,那么这个过程如何实现呢,我们可以使用一个例子来说明。

拿到业务

首先,产品会给到我们一个极其具象的业务,这里面既包含了用户的操作,也包含了在完成业务时我们要做到的行为,甚至可能还会包含一些特殊情况(比如用户重复用一个手机号注册账号,那么我们肯定是不能给到通过的,这种情况产品很有可能会告诉你)

抽象出完备的领域事件

在拿到业务时我们可能是一头雾水的,没关系,这里我们一步步的来。

我们先做到的应该是抽象出领域事件。

text
语文小课堂:

抽象————将许多具有相同关系的东西抽出一个共有的部分,我们称呼这个部分叫象。

案例:抽象类,就是所有实现该类的类共有的部分

所谓领域事件,就是对我们系统有持久意义的事件,他会导致查询模型出现修改,甚至会导致自己所控制的表产生修改。

领域事件是一个既定的事实,一旦发生就会修改。

text
做一个停车计费程序
·车牌识别系统会在车辆入场和出场的时候调用计费程序
付费后15分钟内可以离场,超过15分钟要补费
·车辆入场出场失败的时候,要给管理员报警
·计费程序提供查询当前某个车牌号应付款
·支付系统通知计费程序车辆已付款
·用户要能够查看某个车牌号过去的停车记录
·管理员要查看在场车辆总数,每日营业额
·计费规则是每小时1块,不足一小时当成1小时

上面这个案例中能抽象出什么领域事件呢?

  • 车辆已进场
  • 车辆已出厂
  • 车辆已付费
  • 入场已失败
  • 出场已失败

有没有发现一个共同点?

是的,这些事件都是完成时,为什么呢,我们可以这么理解,因为我们之前说了,领域事件是一个既定的事实,既然是一个事实,那么他肯定就要围绕是在一系列操作(命令)完成后才会出现的情况

为什么一定要是既定事实?因为只有一个确定发生了的是事情才可以对我们的系统做出持久的改变

完备

什么是完备?我们认为这里的完备就是每一个事件都有所来源,事件只能是由历史的事件(比如定时任务定时去扫描数据库过去的数据来触发新的事件),或者用户的命令来触发,不能在我们的风暴图中存在一个没有来源的事件

得到完备用户命令(Command)

在已经得到领域事件之后,我们就要尝试去获取用户命令,这个行为其实还是比较简单的,就是你通过结果去推问题。

比如车辆进厂和进厂失败,我们很容易就能看出这俩是一个命令的两种情况对吧。

那是什么命令呢?就是我们业务里面的用户入场

特别注意的是,到了这一步,我们仍然要重点去关注当时产品給我们的需求,不论是领域事件还是用户命令,乃至整个软件设计,其实都是要来源于需求

完备

所谓命令完备性是指我们一个命令完成所需要的所有信息只能来源于用户传入的参数和历史消息中获取(那么历史消息从哪里来呢 ,其实也就是我们Command部分进行的存储,对于Command来说,这一步是从Event中获取的)

由于Command的这一特性,我们很容的就会发现可能有的Command的事件找不到直接能有Event提供的数据,且Command自己不通过Event也无法给出这个数据,那么就说明我们的Event发生了缺失,需要补上

但是补上之后又出现一个问题,因为毕竟是Command产生的Event,那么这个Event的数据只能来源于其他的Command,那么是否有其他的Command能够产生这个Event呢?如果没有,则又进一步说明Command不全面,需要补充Command

获取聚合

当领域事件完备,我们就可以寻找聚合了。

聚合需要依靠一个Id将多个Command关联起来,也就是说,一个聚合应该可以找到一个唯一建作来整合所有的Command

注意:这里的Id并非表中的唯一主键,而是逻辑上的可以将这些Command联系起来的一个值

我们对聚合的要求很简单

  1. 聚合能承担所有领域事件的任务
  2. 聚合本身充血,所有命令可以依靠聚合本身或者领域服务来完成

注意聚合不要太大(影响并发),不要太小(退化为实体)。

那么聚合要持有哪些数据呢

我们需要根据Event进行反推,即我能通过哪些数据可以推导出Command已经完成,Event成功成为既定事实

获取读模型

接着我们就要思考需要跟用户看的读模型都有哪些了

读模型的特点是:**返回给用户(哪些数据应该给到用户,哪些不应该给到用户),应该是用户友好的,即用户保证信息正确传递的情况下使用信息最少

读模型完备

每一个读模型都应该至少收到一个领域事件的通知

外界信息

有时候我们的领域模型影响的实际上不是我们的领域数据,而是外部数据,这时候据需要引入RPC或者多领域间的service进行处理了

协作

不论是单体项目还是多模块项目,我们的代码往往都很有可能设计一个协作的功能。协作可以是之前的一个聚合由于并发问题被拆分成了两个聚合导致的问题,也可能是一个老项目,在加入新功能时影响到了老功能导致。

这里我们来介绍一下这种情况的解决方式

首先,我们应该通过上面的办法得到两个完备的领域模型,然后我们的任务就是将两个领域模型做出连接。

这个连接往往可能会出现在Command——>Aggregate——>Event的这个过程,由于新引入的功能,导致Command可能无法直接到达Aggregate(Command要的数据Aggregate给不到,又或者说是Aggregate根本无法通过现有的能力去完成这个Command)

这时我们可以尝试通过相互引用/使用一个Service作为协调的方式来进行处理

对于依赖引用,我们会希望多变的/新的依赖于稳定的/老的依赖,因为不稳定性不会从依赖传向被依赖者,这也是我们依赖倒置的思想

另一种就是使用一个service,让service中书写协调二者的逻辑,进而完成整个过程的整合(这里的service可能承担一个转接,选择的作用,总之就是作为一个桥梁沟通起了整个系统/桥接模式)

本站访客数 人次      本站总访问量