实践 DDD 的一种思路
在实践 DDD 的道路上,我们一开始容易被各种概念带偏,写出各种样板式代码,满足相应的分层。定义聚合、实体、值对象
,然后对聚合操作要有Repository
,当聚合比较复杂时还要有Factory
,后面又冒出来一个领域服务
的概念,这个是大家最容易迷惑的,领域服务不是领域内的服务,不是对领域能力包装成的一个服务,领域服务是实在不知道放到哪个聚合下才妥协的产物,领域服务能不用最好就不用,因为它本来是不该存在的。同时传统 DDD 在实践过程中增加的读写放大,多次操作数据库怎么优化,没有给出很好的解法。
综上:我觉得这也是大家实践起来一个很大的门槛
CQRS 的迷思
近些年,对我影响最大的一个概念就是 CQRS,在各种业务场景的设计中,或多或少都会有类似的设计,相比传统的 CRUD,CQ 是一种思维,在遇到业务场景时,会主动的识别 Command 和 Query,因为两者面临要解决的问题域不尽相同,现有的存储体系,读和写几乎是不可兼得的,所以我们会有异构数据源,针对读库的个性化定制又特别多,写模型又要保障很好的性能,稳定性和扩展性。CQRS 的思路我觉得是现有体系下比较合理的一个解
EventSourcing
事件溯源,比较难落地的一个概念,短平快的互联网应用,要记录每次操作的事件,无疑是一种负担
最终一致性
当我们采取了传统 DDD 的架构思路,为了写出符合 DDD 的代码,在复杂技术的实现上还要考虑到会不会兼容这种写法,为了实现聚合间的最终一致,我们的分布式事务如何做
DDD 应用框架
enode 提供了一个思路解决上述问题,和 axon 的解决思路还不太一样
整体架构
这个是 enode 的架构图
使用约束
一个命令一次只修改一个聚合根
聚合间只能通过领域消息交互
聚合内强一致性
聚合间最终一致性
核心思路
一个命令一次只修改一个聚合根
首先做这个限制是从业务研发的角度来考虑的,这会让命令的职责更加具体,便于问题的拆解,职责的划分,如果一个命令要修改多个聚合根,应该通过 Saga(Process Manage)来完成
加上这个约定后带来的收益:
同一个聚合根的命令操作都会路由到同一个分区,聚合根就可以常驻内存(In-Memory),这样就不必每次重建聚合根,缓存利用率聚合是 100%,是一种大限度利用内存的设计
命令路由到同一个分区,命令的操作顺序就可以保障(命令会携带聚合根的版本),这就保障了聚合根在同一时刻只有一个在操作,直接避免了并发问题,因为在设计上是无锁的
关于命令操作顺序的保障,为了提升吞吐,要求队列是无序消费,但队列无序了怎么保证操作是有序的呢,这点就有点类似 Flink 中的 watermarker 的设计了,聚合根的 mailbox 会记录每个消息的版本,如果高版本的数据先到,数据就会暂存,等到中间的版本处理完成才处理,通过 mailbox 中的顺序保证了操作的有序
基建依赖
分布式消息队列
依赖队列的原因主要有三点:
面向不同服务场景资源隔离,可针对性的优化
为了 C 端高吞吐,可通过队列无限扩缩容,且节省资源
为了同一个聚合根路由到同一个消费者,减少聚合的重建,缓存利用率高
EventStore
在存储方面需要额外保障两张表,这个也是不得已而为之的一个设计,因为要实现 EventSourcing,事件记录总要有个地方放事件表(event_stream),聚合根消费进度表(published_version)限定要提供的能力:
批量提交事件,同时能识别出哪些是重复命令和重复的版本
通过聚合根 id 和 commandId 点查
通过聚合根 id 和 version 点查
通过聚合根 id 和最小最大 version 范围查找
同时留了扩展,可自主的选择实现,目前提供默认的实现(MySQL,PG,MongoDB)
编程模型
事件驱动的迷思:
什么时候采取事件驱动,什么时候使用过程式编程呢?
命令和事件的区别,两者都是消息,为什么要分开表示呢?
我的理解如下,
命令可以被拒绝。事件已经发生。
这可能是最重要的原因。在事件驱动的体系结构中,毫无疑问,引发的事件代表了已发生的事情。
现在,因为命令是我们想要发生的事情,并且事件已经发生了,所以当我们命名这些事情时,我们应该使用不同的词,命令一般是名词,事件一般是过去分词
举个例子,拿订单系统来说,我们有个外部支付系统的依赖。
当用户在支付系统完成支付后,支付系统会向订单系统发送一个 Command,MarkOrderAsPayed(标记订单已支付),订单在处理这个 Command 时,获取当前订单,调用订单的标记已支付
(行为),产生了OrderPayed
(订单已支付)事件。
我们可以看到,命令通常由系统外调用,事件是由处理程序和系统中的其他代码提供的。
这是他们分开表示的另一个原因。概念清晰度。
命令和事件都是消息。但它们实际上是独立的概念,应该明确地对概念进行建模。
这两者我理解都是符合人类思维的,首先是基于大脑接收到感知到的消息(Event)产生一个想法【意图】(Command),然后如何实现这个想法,思考的维度是过程式的,在实现的过程中,会产生一些事件消息,这个消息又会影响到大脑。如此循环往复。
事件风暴
简单说下事件风暴的一些经验
按照用例维度开始分析
每个用例以终为始
先枚举主流程,然后补充异常处理,MECE,逻辑闭环,穷尽
补充 actor 和命令信息
最后补充 Policy
Saga 实现
Saga 的实现有两种模式,一种是控制,一种是编排,通过事件消息,完全不需要业务关心技术实现的过程,通过和业务专家沟通需求,事件风暴,画出命令事件,逻辑是不是立马就清晰了。通过打点也可以把可观测性
做的很好,同时分布式的事务模型,Saga 在性能方面更有优势,这里和 axon 的 unit of work 有着本质的区别。
评论