关于ddd:CQRS在一条订单系统中的实践二DDD与CQRS结合

30次阅读

共计 2550 个字符,预计需要花费 7 分钟才能阅读完成。

DDD 与 CQRS 联合

背景常识

探讨这个问题之前,咱们先回顾几个根底概念。

聚合根: 恪守不变性规定设计的聚合边界,聚合内的所有操作都是严格遵守业务规约具备强统一的保障

聚合根的概念很形象,但却是领域建模外围中的外围。这里不探讨畛域划分或者聚合根如何建设,仅谈谈我对 ” 恪守不变性规定设计的聚合边界 ” 这句话的了解。
首先咱们得意识到领域建模实质上是一种常识建模的计划,在这个前提下,咱们的建模必须不能违反业务法令。聚合基本质上就是 ” 业务法令 ” 在零碎内的一致性体现,
比方在 ” 拆单 ” 这个语境下不仅仅是父订单状态变为已拆单,也必须随同着子单的生成。
在此基础上与聚合根无关的另一条设计准则 ” 通过聚合根拜访实体 ” 也就不难理解了,通过聚合根来无效的限度实体(可变对象)在零碎内的拜访,使实体的所有变更
都在 ” 不变性规定 ” 下进行,也就可能防止 ” 业务法令 ” 被毁坏。

洋葱模型

两个同心圆档次:
代表传播机制和基础设施的外层;
代表业务逻辑的内层

在了解了聚合根的根底上,我再来看下 ” 洋葱模型 ” 亦或是 ” 六边形架构 ” 就不难理解了。聚合根设计的基本出发点是 ” 业务准则的高度内聚 ”,从这个角度登程势必要求零碎内
的数据扭转都必须通过畛域模型束缚,简略的能够将过程概括为
用户层(同步事件处理(即对外 API)、异步事件处理(音讯监听))==> 畛域层(业务逻辑)==> 基础设施层(聚合内的数据变动长久化(DB 或其余存储模式),跨零碎的长久化变更(发送畛域事件))

CQRS: 命令查问责任拆散,读写双模型;

命令模型用于无效地执行写 / 更新操作,而查问模型用于无效地反对各种读模式。通过畛域事件或其余各种机制将命令模型中的变更流传到查问模型中,让两个模型之间的数据放弃同步。
CQRS 两种实现策略
同步计划,实时双写,长处是实时性高,双写带来了额定的写时开销,具体案例能够参看《拍卖系统优化历程(一)— 建设拍品 Lot 模型,实际 CQRS》;
异步计划,借助音讯或者其余异步同步机制,让两个模型达到最终一致性(这里介绍咱们如何借助数据库的读写同步策略来达到读写模型的最终一致性);

领取流程的领域建模与 CQRS 落地

订单畛域划分

订单根底畛域划分

  • 平安的构建实体(不变性规定的一部分)

    围绕订单咱们拆分成了多个畛域(” 订单 ” 作为各自畛域的聚合根),这些不同畛域上的订单尽管基于的底层数据是重合的,但 ” 订单 ” 在不同的上下文内的定义是不雷同的。
    以 ” 领取订单 (PayingOrder)” 与 ” 待拆单订单(SplittingOrder)” 为例,其本质上的底层数据都建设在同样的数据上,划分这两个聚合的边界一方面是其畛域内聚焦的业务问题不雷同,
    另一方面从 ” 订单 ” 这个实体上看其生命状态有着显著的边界,PayingOrder 待领取订单,SplittingOrder 已领取实现待拆单订单,订单是否领取这两个订单实体有了实质的区别。
    留神这里咱们探讨的是实体状态而非数据状态,这一点很重要,在这个前提下如果订单状态不合乎以后聚合下的状态要求则聚合内的实体构建失败,通过屏蔽非平安的实体(或者聚合) 构建防止了不平安因素造成的影响。
    问题到此并没有完结,后面咱们定义的不平安,有时候可能是某个时间段内的误判,比方因为限流降级、主同步提早等策略失效导致了咱们基于偏差的数据得出了 ” 不平安的拜访 ” 的谬误断定
    (这也是咱们做读写拆散比拟头疼的问题),那么怎么解决?这里就要借助聚合之外的最终一致性策略来达到。

  • 聚合外的最终一致性

    咱们在聚合外部须要谋求强一致性保障,聚合外咱们往往采纳最终一致性来解除零碎间耦合(上图中彩色箭头),对于聚合内产生的事件能够采纳例如 kafka 这类的消息中间件来进行通信。
    必然能够做到对下面误判的重试(比方音讯的重试)直至最终统一,而不必从业务实现上再去投入过多精力进行干涉,因为这个零碎间的一致性问题没有主同步提早、限流降级的影响它也是存在的。

领取事件的处理过程

洋葱模型比拟形象,换个形式形容

这里的具体实现能够参看我之前写的两篇文章
《DDD 实际落地(二)》
《领取订单领域建模实际》

从 CommandModel 到 QueryModel

后面提到了两种实现 CQRS 的机制,并不难理解。

  • 同步计划,DomainRepository 拜访读写模型各自的数据源,进行同步的数据更新;
  • 异步计划,CommandModel(畛域实体)执行完命令后发送响应的畛域事件,通过订阅畛域事件来更新查问模型;
  • 借助数据层的读写同步机制,达到读写模型的最终一致性;

    CQRS 读写职责拆散,实质上是通过实体职责的拆散简化了读模型的结构(优化查问),同时因为读模型不再承载写命令的执行所以也躲避掉了读模型在数据不齐备或者不实时的状况下不会进一步扩散过期读问题。
    在 ” 订单领取 ”、” 订单发货 ” 这些场景咱们采纳了不同于其上两种形式的读写模型同步计划(或者说是异步计划的变种),在内部零碎收到领取实现事件后通过读模型查问待拆单订单,待拆单订单的无效结构建设在
    待领取订单的领取行为的失去了一致性解决(PayingOrder 负责了写模型对应的数据的强一致性,SplittingOrder 只须要验证其状态即可能确保数据失去了残缺的同步),若构建胜利则意味着读模型数据的同步已实现进行数据返回,
    否则领取实现的订阅方持续重试期待读模型的数据同步实现。

    具体的实现细节能够参看 CQRS 在一条订单零碎中的实际(一)
    中对于订单读写库的应用细节

  • 读模型进行事件重放,达到逻辑的一致性;

    在读写同步的计划中,咱们能够看到同步都是通过畛域事件影响形成读写模型的数据层,并向上反馈来达到最终一致性。那么如果某些场景下读模型对于事件响应不通过数据层向上反馈,而是间接作用于逻辑层是不是仍旧行的通?

    具体的实现细节能够参看 CQRS 在一条订单零碎中的实际(一)
    中对于 ” 订单查问流程 ” 的形容

    这个计划可能失效必须建设在读模型的结构过程中可能重放写模型的事件,许多场景下这个条件还是很难达到的。

总结

回过头来咱们再看整个计划中咱们并没有从业务实现上刻意的去做一些调整但仍旧可能实现了 CQRS 的落地,而这所有的基本其实还是在于开篇所谈的聚合根的准则:” 在聚合边界内建模真正的不变条件 ”、” 在聚合边界之外应用最终一致性 ”。
从书本中来到实际中去,这也是我对近期实际的一次总结,心愿可能大家一些启发。

正文完
 0