事件风暴(二)
1.本章目的
通过案例来实践事件风暴,来加深对事件风暴的理解
2.案例一:电商案例
提取事件
电商业务的常见事件如下图所示:
补充其他基本元素
我们先选择商品、库存和订单领域中的核心事件并添加命令、参与者及策略,之后可以得到下图所示的展示效果:
确定写模型
接下来提取电商业务场景下的写模型,并围绕写模型合理组织命令和事件,得到下图所示的展示效果:
确定限界上下文
基于写模型组合出来每部分,进行区分限界上下文,得到下图所示的展示效果:
3.案例二:在线小说阅读网
需求介绍
在线小说阅读网的小说由基本信息和章节内容两部分组成,基本信息包括小说名称,小说封面,小说简介等,小说的章节内容则是小说的主题部分
小说阅读网的核心用户有两类,一类是读者,另一类是作家
访问在线小说阅读网的游客完成注册之后,即自动成为读者,读者可以阅读免费的小说,或者付费订阅一些非免费的小说
已注册的用户可以提交作家身份认证申请,认证申请通过之后,用户获得作家身份。作家可以打开作家专区的页面,进行小说的创建以及后续章节内容的上传
由于作家笔名是稀缺资源,在线小说阅读网规定,如果认证作家身份成功后,在一年即没有创建小说,也没有上传过章节内容,那么在线小说阅读网将释放该笔名,并用随机字母代替
部分核心用例
- 游客注册成为读者
- 读者提交作家认证申请
- 运营人员审批作家认证申请
- 创建作家信息
- 作家创建一本小说
- 作家发布小说章节
- 一年内无作品,系统自动释放作家笔名
- 内容安全系统下架某部小说或某章节
- 读者订阅某章节
- 读者支付章节订阅费用
建模过程以及结果
4.案例三:论坛系统
思路
基本结构
功能
- 用户管理功能:能够注册和更新注册用户的个人信息
- 论坛管理功能:能够基于不同内容创建多个论坛版块,在每个版块中能够看到最新发布的帖子
- 帖子发布功能:可以在某个论坛版块下创建新的帖子并对帖子进行修改和删除,用户发帖时会有对应的积分
- 帖子置顶功能:能够对某个帖子执行置顶操作
- 帖子回复功能:能够在浏览某一个帖子时对其进行回复,用户回帖会有对应的积分
- 帖子查看功能:能够获取帖子的查看、回复等统计数据,用户浏览帖子会有对应的积分
- 附件上传功能:帖子可以支持多种类型的附件
- 订阅功能:能够对不同的论坛版块、帖子和用户账户进行订阅,从而确保这些论坛版块、帖子和用户账户更新时,用户能够收到消息推送(如短信息、电子邮件等)
- 标签管理功能:能够对论坛版块和帖子打标签,并能根据标签筛选版块或帖子
- 全局搜索功能:能够基于关键词对帖子内容进行搜索
基础设计(V1)
介绍
在不采用DDD的前提下完成一版方案设计,并和采用DDD的方案进行对比,从而充分展示不同方案之间的优劣点
在基础设计阶段,由于还没有介绍DDD中的各个概念,因此这里通过传统的系统建模方法来完成对论坛系统的设计。这一阶段的工作流程如下图所示:
UML交付物
我们使用的UML建模工具是Astah UML。下图展示了论坛系统的用例图:
可以看到,这里出现了两类角色——用户和管理员,分别包含了一组执行用例。在提炼系统用例时,一方面尽量挖掘出系统中潜在的用例,另一方面明确具体用例的执行对象。例如,基于“具备帖子置顶功能,能够对某个帖子执行置顶操作”这一功能,我们明确系统具有“置顶帖子”这一用例,但这一用例应该由谁来执行呢?显然,用户不应该具备置顶帖子的权限,执行该用例的应该是管理员
基于用例及功能描述,我们可以初步梳理系统中的核心类结构。这一步通常无法完全挖掘出系统中所应具备的所有类,但应该尽量实现这一点。下图展示了论坛系统的核心类及其交互关系:
在上图中,我们针对帖子设计了ViewCounter类用来标识该帖的浏览数量,但并没有针对“积分”这一概念设计独立的类。在基础设计阶段,出现这种情况是合理的,因为我们还没有深入探究论坛系统中各个业务流程的交互过程,也就无法合理判断“积分”这个概念是应该包含在Account类中还是单独提炼为一个类。这些设计决策点随着工作坊的演进渐进涌现
最后,我们基于上图中的核心类来梳理论坛系统的交互过程,下图展示了发帖和回帖的时序图:
Miro平台交付物
Miro这款在线工具非常适合DDD工作坊。Miro是一款功能强大的在线白板软件,被广泛应用于团队协作、创意思维和项目管理等领域。它提供了丰富的创作工具和功能,使得团队成员可以轻松地进行思维导图、流程图、便利贴、画笔绘制等操作。在白板软件领域,Miro是主流白板工具中存续时间最长,也是在功能和生态上最为成熟的一款工具
总结
到此,关于论坛系统的基础设计告一段落。这部分内容没有用到DDD的任何概念和实践,相对比较简单。唯一可能让人感到疑惑的是两张类图中展示的Thread类。我们已经有了代表帖子的Post类,为什么还要引入Thread类呢?实际上这是业界主流论坛系统中的一种设计方法
Thread和Post在英文中都有帖子的意思。两者的区别在于Thread相当于一种时间线,将同一主题不同时间发的帖子按时间先后连接起来。下图展示了Thread和Post之间的这层关系:
当我们在论坛系统中提炼出Thread类时,该帖子下的回帖数量、访问次数、最新一次回帖、帖子的标签和订阅情况等数据都可以通过该类进行统一管理,从而方便查看和维护
战略设计(V2)
介绍
在DDD战略设计工作坊中,我们将针对论坛系统开展事件建模,探索业务全景并尝试划分限界上下文
目标
DDD工作坊第二阶段的设计思路是让读者能够基于案例系统完成粗粒度的系统设计,实现系统边界的有效划分。在第一阶段的基础上,我们将引入目前主流的事件风暴方法和实践来设计一版方案。本阶段的主要目标如下:
- 基于事件风暴流程和模式,小组充分沟通,形成战略设计模型
- 梳理系统核心的领域事件,直接用于第三阶段,即战术设计工作坊演练阶段
- 完成限界上下文和子域的划分,作为第四阶段,即架构设计工作坊演练阶段的输入
基础建模
提取事件
针对论坛系统,此时可以得到部分领域事件。论坛系统领域事件如下图所示:
可以看到,这里展示了与帖子管理相关的4个核心事件,分别如下:
- 首帖已发布(TopicPostCreated)
- 回帖已发布(ReplyPostCreated)
- 帖子已修改(PostRevised)
- 帖子已删除(PostRemoved)
当然我们还可以使用Miro平台来实现战略设计工作坊的交付物。基于Miro平台,可以通过不同颜色的便利贴来区分事件风暴中的不同媒介,如下图所示:
针对论坛系统,可以借助Miro平台来梳理与帖子相关的领域事件,如下图所示:
可以看到,除了前面已经给出的首帖已发布(TopicPostCreated)、回帖已发布(ReplyPostCreated)、帖子已修改(PostRevised)和帖子已删除(PostRemoved)等领域事件以外,这里还出现了如下一组非常有用的领域事件:
- 帖子标题已修改(PostSubjectRevised)
- 帖子属性已修改(PostPropertiesRevised)
- 帖子附件已上传(UploadFilesAttached)
- 帖子已创建(ThreadCreated)
- 帖子已置顶(ThreadTopped)
- 帖子已查看(ThreadViewed)
对于这里出现的帖子已创建、帖子已置顶和帖子已查看3个领域事件为何使用Thread而不是Post,我们已经介绍过了
类似地,针对论坛系统中的其他业务场景,我们可以得到下图所示的领域事件列表:
在上图中,可以将领域事件初步按照业务场景进行排列。接下来,我们将围绕这些领域事件来完成详细的事件风暴建模
确定问题点
得到领域事件之后,我们就可以为对应的事件添加问题点。论坛系统问题点如下图所示:
这里为上传附件成功(UploadFilesAttached)及帖子被删除(PostRemoved)这两个事件添加了问题点
确定命令与参与者
接下来,我们进一步引入命令和执行者。论坛系统命令和执行者如下图所示:
确定自动策略
在上图中,命令和执行者分别用于指定触发领域事件的角色和动作。而有些领域事件是系统自动触发的,如当回帖已发布(ReplyPostCreated)这一事件被触发时,就需要自动触发帖子的订阅通知(ThreadSubscriptionNotified)事件,如下图所示:
上图进一步引入了两个外部系统——短信息系统和微博系统。当帖子的订阅通知被自动触发时,我们会使用这两个外部系统发送具体的通知信息
确定读模型
那么,我们如何执行回复帖子这一命令呢?此时需要引入读模型。显然,针对回复帖子这一场景,首先选中一个特定的帖子,这个帖子就是读模型,如下图所示:
确定写模型
最后,梳理命令和领域事件的关联关系就可以提炼出写模型,也就是聚合对象。下图展示了帖子(Post)这一聚合对象:
聚合分析
版块(ForumBoard)
在论坛系统中,版块是一组帖子的集合,如下图所示:
对于版块,管理员既可以创建新的版块,也可以修改现有版块的信息。而用户则可以订阅自己感兴趣的版块,这样当该版块被修改时就可以第一时间接收到订阅消息。下图展示了围绕版块这一概念的事件建模结果:
创建版块的过程非常简单。管理员执行“CreateForumBoardCommand”命令即可触发“版块已创建”(ForumBoardCreated)这一领域事件
而对于修改版块,事件就变得有点复杂了。可以看到,管理员通过“选中的ForumBoard”这个读模型来修改版块,从而触发“版块已修改”(ForumBoardRevised)这一领域事件。我们考虑论坛系统的原始需求中存在如下业务需求:通过订阅功能,可以订阅不同的论坛版块和帖子,从而确保能够及时收到这些论坛版块和帖子更新时推送的消息(如短信息、电子邮件等)
为了满足这条需求,一旦触发ForumBoardRevised事件,则通过“触发用户订阅”自动策略来自动触发“版块订阅被通知”(ForumBoardSubscriptionNotified)事件。而ForumBoardSubscriptionNotified事件会引发消息的发送,从而生成“消息已发送”(MessageSent)事件。因为这个过程同样是自动进行的,所以我们在ForumBoardSubscriptionNotified和MessageSent事件之间添加“触发消息推送”这条自动策略。最后,消息的发送方式可以是短信息、电子邮件或消息,这些渠道对论坛系统来说显然都是外部系统
帖子(Post)
创建帖子:
首先,我们关注创建帖子这一过程。这个过程可能比人们想象的要复杂。创建帖子场景下的事件建模结果如下图所示:
在上图中,用户通过执行“CreateTopicPostCommand”命令触发“首帖已发布”(TopicPostCreated)这一领域事件。请注意,这个领域事件会触发一系列的业务逻辑,包括生成Thread、增加用户积分和更新版块状态
当用户成功创建了一个帖子的首帖时,意味着围绕该首帖的Thread会被自动创建,后续围绕该首帖的所有回帖都会被纳入Thread的管理范畴。图6-14中展示了“自动生成Thread”这条自动策略所触发的“帖子已创建”(ThreadCreated)事件
回顾相关需求:具备帖子发布功能,可以在某个论坛版块下创建新的帖子并对其进行修改和删除,用户发帖时会有对应的积分
这条需求意味着系统需要为发帖成功的用户自动增加积分。上图展示了“自动增加积分”这条自动策略所触发的“用户账户积分已增加”(AccountPointIncreased)事件
对于论坛版块,常见的一条需求是在界面上展示该版块最新一条帖子的信息。因此,当在该版块发布一个新帖子后,系统需要自动更新当前版块最新帖子的信息。上图展示了“自动更新版块状态”这条自动策略所触发的“版块最新发帖已更新”(Board-LatestPostUpdated)事件
回复帖子:
帖子已创建成功,接下来看看回复帖子的过程,对应的事件建模结果如下图所示:
在上图中,用户通过执行“ReplyPostCommand”命令触发“回帖已发布”(ReplyPostCreated)这一领域事件。这个领域事件同样会进一步触发两个业务逻辑——增加用户积分和触发帖子订阅
关于用户积分逻辑,我们在前面介绍发帖过程时已经讲解过。上图同样展示了“自动增加积分”这条自动策略所触发的AccountPointIncreased事件
而对于“触发帖子订阅”这条自动策略,上图中用粉红色便利贴展示的一个问题点是“是否允许对回帖进行回复?如果是,是否需要控制回帖的层次?”。对于论坛系统的设计,这是一个常见的问题。很多论坛系统允许帖子的嵌套回复。在我们的案例中,为了简单起见,我们控制回帖的层级为一层——只允许对回帖做一次回复
另外,用户可以订阅自己感兴趣的帖子,从而在其他用户对帖子进行回复时获取对应的通知。这一过程与前面介绍的版块订阅逻辑类似。在上图中,同样可以看到,我们通过自动策略触发了“帖子订阅被通知”(ThreadSubscriptionNotified)事件,并进而触发外部系统的消息通知机制
修改帖子:
对于帖子管理,修改帖子是一个相对灵活的操作,对应的事件模型结果如下图所示:
可以看到,针对某一个帖子,我们可以通过“UploadAttachementCommand”命令上传附件,从而触发“附件已上传”(UploadFilesAttached)事件。这时一个潜在的问题点是“附件上传数量是否应该有限制?”。这是日常开发中常见的一个问题
同时,我们也可以通过“RevisePostPropertiesCommand”命令来修改帖子的属性,从而触发“属性已修改”(PostPropertiesRevised)事件。这时从业务逻辑上也经常需要考虑“哪些属性一旦创建就不允许修改?”这一问题点
当然,通常的做法是设计一个更为通用的“RevisePostCommand”命令,以便修改帖子。该命令会触发一个“帖子已修改”(PostRevised)事件,而该事件会进一步触发帖子订阅及消息推送机制,正如上图所展示的那样
删除帖子:
我们接着来看删除帖子的事件模型,对应的事件建模结果如下图所示:
我们可以通过“RemovePostCommand”命令触发“帖子已删除”(PostRemoved)事件,进而触发帖子订阅和消息推送机制。但是,这里有一个问题:如果要删除的帖子有回帖那又应该怎么处理呢?这个问题的答案取决于具体的场景需求。例如,可以在删除操作中加一层限制,指明存在回帖的帖子不允许被删除
置顶帖子:
置顶帖子是帖子管理中最简单的一个事件模型,对应的事件建模结果如下图所示:
在论坛系统中,管理员可以对某个帖子执行置顶操作,而原始需求中并没有对置顶操作的结果做过多说明,因此可以采用上图中的建模效果。当然,如果将置顶帖子操作看作一种更新帖子操作,那么也可以触发帖子订阅和消息推送机制
浏览帖子:
最后,考虑浏览帖子操作。正常情况下,针对只读的浏览帖子操作,不应该生成领域事件。但由于需求明确要求“用户浏览帖子会有对应的积分”,浏览帖子操作本身也应该考虑事件模型,对应的事件建模结果如下图所示:
用户在执行“ViewThreadCommand”命令时会触发“帖子已被浏览”(ThreadViewed)事件。而该事件通过“自动增加积分”这一自动策略触发“用户积分已增加”(AccountPointIncreased)事件,从而实现增加用户积分的效果
账户(Account)
在论坛系统中,账户(Account)管理并不是核心业务。关于账户操作的事件建模也比较简单,基本只包含用户注册、修改、删除账户,以及登录与退出账户操作。我们先来看用户登录账户时的事件模型,对应的事件建模结果如下图所示:
可以看到,用户执行“AccountLoginCommand”命令并触发“账户已登录”(AccountSignup)事件。同样,其他用户也可以订阅该用户,一旦该用户登录就会触发用户订阅和消息推送机制
类似地,当执行修改账户操作时,可以梳理出下图所示的事件模型:
在设计用户账户更新机制时,一个比较常见的问题点是“账户的哪些信息可以被修改?”。而当账户信息被修改之后,通常也会通知订阅该账户的用户。上图展示了这些设计上的细节
标签(Tag)
在论坛系统的需求中,还包括对标签(Tag)进行管理,为论坛版块和帖子打标签,并根据标签进行筛选。下图展示了围绕标签所展开的事件模型:
可以看到,与标签相关的操作主要是选择对应的帖子或版块并打标签。这些操作均由管理员完成,而用户则可以根据特定标签来筛选帖子和版块。因为筛选属于查询类操作,不需要生成领域事件,所以也就不需要体现在事件模型中
订阅(Subscription)
最后,我们来关注订阅(Subscription)操作。该操作由用户触发,可以作用于论坛版块、用户账户和帖子,对应的事件模型也比较简单,其建模结果如下图所示:
总结
在论坛系统的原始需求中,还存在关于搜索功能的相关描述。搜索功能属于查询类操作,一般不单独进行事件建模。但是在接下来的问题子域划分过程中,应单独考虑搜索功能
子域划分
识别子域
在论坛系统中,基于对业务描述的理解,很容易就能识别出如下子域:
- Post子域:代表论坛帖子处理相关领域
- ForumBoard子域:代表论坛版块管理相关领域
- Account子域:代表用户账户管理相关领域
我们接下来讨论订阅这个概念。对于帖子、论坛版块和用户账户,我们都可以执行订阅操作,因此可以将订阅单独提炼为一个子域。那么,由订阅触发的消息是否也要提炼为一个独立的子域呢?这取决于系统的复杂度和业务的规划。通常,用于发送消息的模块具备比较高的技术复用性,建议单独提炼为一个子域。现在,我们往论坛系统中继续添加如下两个子域
- Subscription子域:代表订阅操作相关领域
- Message子域:代表消息发送相关领域
我们继续讨论标签的处理方法。关于标签有两种处理策略:一种是单独形成一个子域,另一种则是将这部分功能分散到各个子域中,每个子域单独维护一套标签体系。对于论坛系统,由于标签能够同时作用于Post子域、ForumBoard子域或Account子域,因此它也具备通用性,可以考虑将其单独提炼为如下一个子域
- Tag子域:代表标签管理相关领域
到此,关于业务领域中所具备的子域提取过程告一段落。但是,不要忘了论坛系统的原始需求包含如下需求描述:具备全局搜索功能,能够基于关键词对帖子内容进行搜索
结合原先讲过的子域划分方法,对于那些由技术驱动的业务场景,我们建议单独提炼如下一个独立子域
- Search子域:代表搜索操作相关领域
子域映射图
- 核心子域:Post子域
- 支撑子域:ForumBoard子域、Subscription子域和Search子域
- 通用子域:Account子域、Tag子域和Message子域
论坛系统完整的子域映射图如下图所示:
限界上下文映射
介绍
在论坛系统中,为了简单起见,我们将每一个子域都提取为一个限界上下文,即Post上下文、ForumBoard上下文、Subscription上下文、Search上下文、Account上下文、Tag上下文和Message上下文。对于这些限界上下文,我们无意全部展开讨论,而是选择Post上下文和Subscription上下文这两个具有代表性的上下文进行分析
Post上下文
在Post上下文中,我们梳理它与其他上下文之间的交互关系。由于用户发布帖子时需要校验用户账户的有效性,因此Post上下文依赖Account上下文。Post上下文通过防腐层调用Account上下文所提供的开放主机服务,这两个上下文之间的协作模式应该是客户–供应商模式
在论坛系统中,所有的帖子都从属于某一个论坛版块,所以Post上下文依赖ForumBoard上下文。Post上下文和ForumBoard上下文之间的这种依赖关系与其和Account上下文之间的依赖关系类似
对于每一个帖子,管理员可以为其打标签,而用户则可以基于标签进行过滤,因此Post上下文同样依赖Tag上下文。Post上下文和Tag上下文之间的这种依赖关系与其和Account上下文之间的依赖关系类似
在Post上下文中,当用户修改和删除一个帖子时,会发送一条通知给订阅了该帖子的用户,所以Post上下文与Subscription上下文会发生交互关系。此时采用的通信集成模式应该是发布者–订阅者模式
由于需要对帖子执行多元化的搜索功能,因此Post上下文和Search上下文之间也存在交互关系。但Search上下文面向的是OLAP类的搜索场景,因此不存在上下游关系,也就是说不需要指定团队协作和通信集成模式
基于上述分析,我们可以发现Post上下文分别与Account上下文、ForumBoard上下文、Tag上下文、Subscription上下文和Search上下文存在交互,它们之间的上下文映射关系如下图所示:
Subscription上下文
对于Subscription上下文,我们可以充分发挥领域事件的优势来梳理它与其他上下文之间的映射关系。在Post上下文、ForumBoard上下文和Account上下文中,原则上都可以触发用户订阅事件,并将这些事件推送给Subscription上下文。而Subscription上下文接收这些订阅事件之后,可以进一步通过Message上下文发送电子邮件、短信息等消息。这两个步骤是高度解耦的,即Post上下文、ForumBoard上下文和Account上下文与Subscription上下文是一种发布者–订阅者模式,而Subscription上下文与Message上下文之间也应该是一种发布者–订阅者模式。下图展示了与Subscription上下文相关的上下文映射图
完整上下文映射图
关于论坛系统中其他上下文之间的映射关系,本节不再赘述。完整的论坛系统上下文映射图如下图所示: