JiveJdon的DDD分析设计
JiveJdon 下载网址: https://github.com/banq/jivejdon
总体介绍
Jdon框架和JiveJdon作者的书籍:《复杂软件设计之道:领域驱动设计全面解析与实战》,书中有大量JiveJdon代码详细讲解。
JiveJdon是DDD领域驱动设计实现案例,运行在Jdon.com网站多年.
JiveJdon不同于传统企业软件,是基于业务领域中心的现代架构:
DDD聚合模型设计图:
JiveJdon遵循最新的领域模型建模原则:
- 高层次的封装
private默认情况下,所有成员setter方法都是internal:
需要繁重的构建器模式来创建聚合根:
- 高水平的PI(持久性无知)
不依赖于基础设施,数据库和其他东西。所有课程都是POJO。
jdonframework的客户/供应模型可以从Persistence / Repository中分离域模型。
域外的所有数据都打包在DTO贫血模型(AnemicMessageDTO)中,因此聚合根实体中的业务规则不会泄漏到域外。
- 丰富的行为
所有业务逻辑都位于域模型中。没有泄漏到应用程序层或其他地方。
- 低水平的原始观察
Entites的原始属性使用ValueObjects组合在一起。 MessageVO是聚合根实体ForumMessage的valueObject ,包含消息内容:subject和body,它将使用复杂的业务过滤逻辑进行处理,这些过滤器有很多实现,例如:TEXT到HTML。
- 业务语言
以有界上下文中使用的业务语言命名的所有类,方法和其他成员。
事件溯源
想想您的银行帐户或支付宝账户,经常注意到的是账户当前的余额,如果对当前余额感到奇怪,怎么这个月我花了那么多钱呢?这时您需要查询当月进出明细,看看一笔笔账户进出的金额,工资入账多少钱,在某宝购物花了多少钱。
您有没有想到账户余额与进出明细的关系呢?进出明细应该是导致账户余额变动的原因,这些进出明细存储了构成账户余额的各个事件,导致当前账户余额的是这些事件总和,简而言之,这就是事件溯源,通过发生过的事件追溯当前状态的原因,这些事件是造成当前状态的唯一真相来源。
发一个帖子代表发生一笔明细,帖子相互回帖组成了一个帖子集合,这个集合称之为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将命令传递给业务逻辑的决策者,也就是聚合根实体:ForumMessage,ForumMessage有两种类型:主题帖子和依赖帖子,主题是根消息并且有很多回复消息,所有消息组成一个线程(ForumThread)
业务/域逻辑位于ForumMessage的addChild消息方法中:
@OnCommand(“postReplyMessageCommand”)注释是来自jdonframework的pub-sub模型,它可以使该方法实现单写原则: 无阻塞,无锁定,高并发。
“eventSourcing.addReplyMessage”可以将领域事件发送到基础架构层,例如Repository。这是来自jdonframework的pub-sub模型,它使得领域层不再依赖于基础设施,数据库和其他东西。
领域中发生的领域事件ReplyMessageCreatedEvent将保存在事件存储jiveMessage数据表中,这是已发布帖子的事件表。用于投射ForumThread的最新回复状态。
领域事件“ReplyMessageCreatedEvent”做了三件事:
- 添加新的帖子消息给“jiveMessage”(事件集合日志)
- 清除查询缓存(CQRS)
- 更新/项目最后一次回复状态(事件投射状态)
用例图
角色设计: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。