JiveJdon的DDD分析设计

JiveJdon 下载网址:  https://github.com/banq/jivejdon

总体介绍

Jdon框架和JiveJdon作者的书籍:《复杂软件设计之道:领域驱动设计全面解析与实战》,书中有大量JiveJdon代码详细讲解。

JiveJdon是DDD领域驱动设计实现案例,运行在Jdon.com网站多年.

JiveJdon不同于传统企业软件,是基于业务领域中心的现代架构:

DDD聚合模型设计图:

JiveJdon遵循最新的领域模型建模原则:

  1. 高层次的封装

private默认情况下,所有成员setter方法都是internal:

 

需要繁重的构建器模式来创建聚合根:

  1. 高水平的PI(持久性无知)

不依赖于基础设施,数据库和其他东西。所有课程都是POJO。

jdonframework的客户/供应模型可以从Persistence / Repository中分离域模型。

域外的所有数据都打包在DTO贫血模型(AnemicMessageDTO)中,因此聚合根实体中的业务规则不会泄漏到域外。

  1. 丰富的行为

所有业务逻辑都位于域模型中。没有泄漏到应用程序层或其他地方。

  1. 低水平的原始观察

Entites的原始属性使用ValueObjects组合在一起。 MessageVO是聚合根实体ForumMessage的valueObject ,包含消息内容:subject和body,它将使用复杂的业务过滤逻辑进行处理,这些过滤器有很多实现,例如:TEXT到HTML。

  1. 业务语言

以有界上下文中使用的业务语言命名的所有类,方法和其他成员。

事件溯源

想想您的银行帐户或支付宝账户,经常注意到的是账户当前的余额,如果对当前余额感到奇怪,怎么这个月我花了那么多钱呢?这时您需要查询当月进出明细,看看一笔笔账户进出的金额,工资入账多少钱,在某宝购物花了多少钱。
您有没有想到账户余额与进出明细的关系呢?进出明细应该是导致账户余额变动的原因,这些进出明细存储了构成账户余额的各个事件,导致当前账户余额的是这些事件总和,简而言之,这就是事件溯源,通过发生过的事件追溯当前状态的原因,这些事件是造成当前状态的唯一真相来源。

发一个帖子代表发生一笔明细,帖子相互回帖组成了一个帖子集合,这个集合称之为Thread,每发一个回帖,都会改变帖子集合Thread的最新回复状态,数据库并没有专门的状态字段保存Thread的最新回复状态,这个状态是通过查询发帖事件集合计算出来的,通过下面一条SQL语句:

SELECT messageID from jiveMessage WHERE  threadID = ?

 ORDER BY modifiedDate DESC

这条语句实际是便利了所有发帖事件集合,获得最新的帖子ID,将这个帖子作为Thread的最新回复状态。数据库中没有专门字段保存最新回复状态,状态是通过事件集合计算得来的。

当用户发布新的回帖ForumMessage时,领域中发生的领域事件ReplyMessageCreatedEvent将保存到事件存储表JiveMessage中,同时刷新内存中事件快照ForumThreadState对象。

CQRS架构

CQRS是命令和查询分离,命令主要实现写入功能,例如新增、修改、删除。

在JiveJdon中,查询数据库是使用缓存,而写入数据库使用普通MySQL,两者之间数据同步通过领域事件(如图中“ReplyMessageCreatedEvent”)实现最终一致性。

六边形架构和干净架构:

JiveJdon是使用支持Customer / Supply或pub-sub模型的JdonFramework开发的应用,该模型可以将领域逻辑与基础架构,数据库和其他东西中分离。

models.xml是一个适配器,它类似MVC中的控制器。

发布回复消息时,用户点按“发布”按钮,发布帖子的POST命令将传入领域服务forumMessageService的createReplyMessage方法。

领域服务forumMessageService将命令传递给业务逻辑的决策者,也就是聚合根实体:ForumMessageForumMessage有两种类型:主题帖子和依赖帖子,主题是根消息并且有很多回复消息,所有消息组成一个线程(ForumThread)

业务/域逻辑位于ForumMessage的addChild消息方法中:

@OnCommand(“postReplyMessageCommand”)注释是来自jdonframework的pub-sub模型,它可以使该方法实现单写原则: 无阻塞,无锁定,高并发。

“eventSourcing.addReplyMessage”可以将领域事件发送到基础架构层,例如Repository。这是来自jdonframework的pub-sub模型,它使得领域层不再依赖于基础设施,数据库和其他东西。

领域中发生的领域事件ReplyMessageCreatedEvent将保存在事件存储jiveMessage数据表中,这是已发布帖子的事件表。用于投射ForumThread的最新回复状态。

领域事件“ReplyMessageCreatedEvent”做了三件事:

  1. 添加新的帖子消息给“jiveMessage”(事件集合日志)
  2. 清除查询缓存(CQRS)
  3. 更新/项目最后一次回复状态(事件投射状态)

用例图

角色设计:Anonymous普通用户;注册用户User;管理者Admin;
普通用户用例功能:
浏览所有论坛;
浏览所有帖子;
浏览其他用户信息;

本系统总体设计实现思路是:首先使用基于Model使用Jdon框架将整体结构搭建出来,实现基本功能,然后在这个基础结构上进行细化,就如同绘画,首先打出轮廓,这样把握整体结构,然后再进入详细表达。

建模

根据用例图,找出原始模型,复杂的用例,我们可以借助四色图帮助我们分类,本案例用例由于我们都比较熟悉,能够从用例中找出一些被操作对象,这些被操作对象大概有几个:Account用户 Forum论坛 ForumMessage帖子。
这里注意一个特殊情况,论坛不是直接并行的多个帖子组成,这些帖子之间也有回复关系,也就是说,回复帖子肯定不是论坛的直接组成成分,这里非常类似订单案例:订单中有多个商品,但是商品不是订单的直接组成部分,有可能有两个相同商品,每个商品还对应一个新属性数量,商品和数量联合起来才可能是订单的组成部分,这个联合起来的对象是订单条目,订单条目才是订单的直接组成部分。
在当前案例,论坛和帖子之间也需要一个类似订单条目的概念,订单条目可能看不见摸不着,它的组成部分就是商品和数量,订单条目其实是一个集合概念,那么在论坛和帖子之间其实也存在这样一个集合概念:FourmThread,如下:

ForumThread相当于主题Topic; 但Topic主要内容放入rootMessage中,可以说相当于所有rootMessage的主题提要,包括回复rootMessage的最后的一个回帖,包括rootMessage在内的所有帖子数等,主要服务于显示一个论坛中所有rootMessage集合。
ForumThread和Forum之间是N:1关系ForumMessage相当于帖子;ForumMessage之间有一个父子关系,表示帖子之间回帖关系;ForumMessage和ForumThread之间是N:1关系,和Forum之间也是N:1关系。
领域模型图如下:

Evans DDD在领域对象的生命周期中对不变性(invariant)进行了定义,指无论何时数据发生变化,都必须满足所有对象一致变化的规则。
围绕ForumMessage,其组成部分Fourm ForumThread Account都是其核心部分,就像汽车由发动机 车身 轮胎组成一样,缺一不可。特别是ForumThread和ForumMessage,更是这种不变性的高度统一,ForumThread实则是虚的,它里面实体就是ForumMessage,两者是高度一致的。
DDD指出;聚合内部的不变量必须在每次事务完成时满足。这可有仓储来实现。
当然,还有一些依赖关系只能在某些特定的时刻,通过事件处理、批处理和其他更新机制来实现,比如上图中state和tag 以及property。

90年代Web又重新回归

下篇