知识卡片

CQRS作为单体到微服务的零停机迁移方案

结构图卡

内容

单体遗留系统整体迁移到微服务时,最痛苦的环节往往不是写业务逻辑,而是历史数据迁移和新老系统切换那一刻的不确定性——数据模型要对齐、切换窗口要停机、一旦出问题很难回退。借用CQRS(命令查询职责分离)这种读写分离思路,可以完全绕开这个痛点:新微服务只管写,只负责处理业务逻辑和产生自己的新增/变更数据,不需要兼容老单体的历史数据模型;另外单独建一个CQRS查询库,专职提供读服务,这个查询库汇总了老单体的全部历史数据,以及新微服务产生的新增或变更数据。新微服务完成本地写操作后,通过领域事件驱动机制把数据异步复制一份到查询库;如果涉及修改历史数据,则先从查询库读出历史明细,在微服务内完成变更,再把结果异步写回查询库。这样,老单体和新微服务可以并行独立地各自完成写操作、互不干扰,同时两者共享同一个查询库,保证读到的数据始终是新老两边合并后的一致视图。这个设计的关键收益是彻底取消了传统迁移里最危险的”历史数据一次性搬迁”和”新老应用一刀切切换”两个动作:新微服务出问题时,老单体完全不受影响,可以继续正常受理业务;等新微服务稳定运行后,老单体才悄悄退役下线,整个过程用户和查询侧几乎无感知。集成过程中如果新老系统间还存在业务逻辑或服务不兼容,可以引入防腐层,把跟老系统交互相关、又不属于新领域模型职责的适配逻辑单独放进防腐层,避免这些兼容代码污染新构建的领域模型;等新老过渡完成后,防腐层代码本身也可以被直接丢弃。

结构图

flowchart TB
  A["老单体应用<br/>(继续独立处理写操作)"] -->|历史数据 + 后续变更<br/>领域事件异步同步| Q["CQRS查询库<br/>(汇总新老全量数据,专职读服务)"]
  B["新微服务<br/>(独立处理新业务写操作)"] -->|新增/变更数据<br/>领域事件异步同步| Q
  Q --> R["统一查询服务<br/>(读到的始终是新老合并后的一致视图)"]
  A -.通过防腐层适配旧系统交互,避免污染新领域模型.-> B
  A -.新微服务稳定后,老单体无感下线.-> X["下线"]

参考来源

- 位置:第23章《微服务拆分和设计原则》"23.2.2 单体遗留系统"(源文件:_epub-src/OEBPS/Text/chapter7-1-2-2.xhtml) - 结论依据:原文说明"我们可以借鉴命令与查询职责分离设计模式(CQRS)……新微服务中只负责业务逻辑处理和业务数据变更……你可以单独建立一个CQRS查询库,存储查询必需的全量业务数据,这些数据包括老单体应用的历史数据和新微服务变更或新增后的数据……新老两套应用就可以基于同一个CQRS查询库完成数据查询逻辑,而各自又可以互不影响地并行独立完成写的业务处理逻辑……当微服务运行完全稳定后,老单体应用就可以无感地完成历史使命成功下线了",直接支撑本卡片结论。 - 原始内容:经过CQRS改造后,你不再需要进行微服务和单体应用的数据模型适配,也省去了从单体应用向微服务的历史数据迁移和新旧应用切换的过程,从而避免了单体应用数据迁移对新的微服务运行的影响。而最关键的是:你再也不需要熬通宵来做应用切换和数据迁移了!