<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>CQRS on 文艺技术笔记</title>
        <link>https://wenyiblog.top/tags/cqrs/</link>
        <description>Recent content in CQRS on 文艺技术笔记</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <copyright>文艺技术笔记 | 软件工程师文艺</copyright>
        <lastBuildDate>Fri, 10 Jul 2026 11:00:00 +0800</lastBuildDate><atom:link href="https://wenyiblog.top/tags/cqrs/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>从单体到事件驱动：用 Event Sourcing 重构订单系统的架构决策复盘</title>
        <link>https://wenyiblog.top/2026/07/event-sourcing-order-system/</link>
        <pubDate>Fri, 10 Jul 2026 11:00:00 +0800</pubDate>
        
        <guid>https://wenyiblog.top/2026/07/event-sourcing-order-system/</guid>
        <description>&lt;h2 id=&#34;当订单状态机开始失控&#34;&gt;&lt;a href=&#34;#%e5%bd%93%e8%ae%a2%e5%8d%95%e7%8a%b6%e6%80%81%e6%9c%ba%e5%bc%80%e5%a7%8b%e5%a4%b1%e6%8e%a7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;当订单状态机开始失控
&lt;/h2&gt;&lt;p&gt;有句话说，架构的腐化往往不是一夜之间发生的，而是在无数次&amp;quot;临时方案&amp;quot;中悄然完成。&lt;/p&gt;
&lt;p&gt;一个典型的电商订单系统，最初总是简单的。创建订单、支付、发货、完成——四五个状态，一张数据库表，几个 CRUD 接口，一个单体应用就能跑起来。团队小，业务量低，一切都运转良好。&lt;/p&gt;
&lt;p&gt;然后业务开始增长。&lt;/p&gt;
&lt;p&gt;退款流程加进来了。部分发货加进来了。预售、拼团、秒杀，每种场景都有自己的状态流转规则。运营说需要一个&amp;quot;订单变更历史&amp;quot;功能，客服说需要看到&amp;quot;这个订单为什么被取消了&amp;quot;的完整链路。数据团队说，他们想知道用户从下单到支付之间的平均犹豫时长。&lt;/p&gt;
&lt;p&gt;你看了看数据库里那张 &lt;code&gt;orders&lt;/code&gt; 表——它已经有 47 个字段，其中 12 个是各种 &lt;code&gt;xxx_time&lt;/code&gt;，8 个是 &lt;code&gt;xxx_status&lt;/code&gt;，还有几个谁也不敢动的 &lt;code&gt;legacy_xxx&lt;/code&gt;。每次修改订单状态，你都需要在一个 300 行的 &lt;code&gt;updateOrder&lt;/code&gt; 方法里小心翼翼地维护十几个 if-else 分支。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这不是某个团队的问题。这是 CRUD 模式在复杂业务场景下的宿命。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文将以一个真实电商订单系统的演进过程为案例，系统性地复盘从传统 CRUD 架构转向 Event Sourcing + CQRS 的每一个关键决策。不是为了推销某种架构范式，而是试图回答一个更实际的问题：&lt;strong&gt;在什么条件下、以什么节奏、付出什么代价，才能完成这次架构迁移。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;第一部分crud-模式的天花板在哪里&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%b8%80%e9%83%a8%e5%88%86crud-%e6%a8%a1%e5%bc%8f%e7%9a%84%e5%a4%a9%e8%8a%b1%e6%9d%bf%e5%9c%a8%e5%93%aa%e9%87%8c&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第一部分：CRUD 模式的天花板在哪里
&lt;/h2&gt;&lt;h3 id=&#34;订单系统的-crud-黄金时代&#34;&gt;&lt;a href=&#34;#%e8%ae%a2%e5%8d%95%e7%b3%bb%e7%bb%9f%e7%9a%84-crud-%e9%bb%84%e9%87%91%e6%97%b6%e4%bb%a3&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;订单系统的 CRUD 黄金时代
&lt;/h3&gt;&lt;p&gt;在系统早期，订单的数据模型非常直观：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;orders 表
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── id
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── user_id
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── status (created / paid / shipped / completed / cancelled)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── total_amount
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── created_at
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── updated_at
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每次状态变更就是一次 UPDATE 语句。查询订单就是一次 SELECT。整个流程清晰、可预测、容易调试。&lt;/p&gt;
&lt;p&gt;这个阶段，CRUD 模式是正确选择。它简单、高效、团队理解成本低。过早引入事件溯源反而是一种架构浪费。&lt;/p&gt;
&lt;h3 id=&#34;第一个裂缝状态变更的上下文丢失&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%b8%80%e4%b8%aa%e8%a3%82%e7%bc%9d%e7%8a%b6%e6%80%81%e5%8f%98%e6%9b%b4%e7%9a%84%e4%b8%8a%e4%b8%8b%e6%96%87%e4%b8%a2%e5%a4%b1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第一个裂缝：状态变更的上下文丢失
&lt;/h3&gt;&lt;p&gt;问题从&amp;quot;退款&amp;quot;功能开始浮现。&lt;/p&gt;
&lt;p&gt;当用户申请退款时，订单状态从 &lt;code&gt;shipped&lt;/code&gt; 变为 &lt;code&gt;refunding&lt;/code&gt;，再变为 &lt;code&gt;refunded&lt;/code&gt;。但如果退款被拒绝呢？状态回退到 &lt;code&gt;shipped&lt;/code&gt;。此时，数据库里的订单记录看起来和一个从未申请过退款的订单完全一样。&lt;/p&gt;
&lt;p&gt;信息丢失了。&lt;/p&gt;
&lt;p&gt;你当然可以加一个 &lt;code&gt;refund_history&lt;/code&gt; 表来记录退款历史。然后再加一个 &lt;code&gt;status_change_log&lt;/code&gt; 表来记录所有状态变更。然后再加一个 &lt;code&gt;operation_log&lt;/code&gt; 表来记录运营的手工操作。&lt;/p&gt;
&lt;p&gt;这就是大多数团队走过的路——用补丁来弥补 CRUD 模式的天然缺陷。&lt;/p&gt;
&lt;h3 id=&#34;第二个裂缝跨服务的状态一致性&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%ba%8c%e4%b8%aa%e8%a3%82%e7%bc%9d%e8%b7%a8%e6%9c%8d%e5%8a%a1%e7%9a%84%e7%8a%b6%e6%80%81%e4%b8%80%e8%87%b4%e6%80%a7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第二个裂缝：跨服务的状态一致性
&lt;/h3&gt;&lt;p&gt;当系统从单体拆分为微服务后，一个&amp;quot;支付完成&amp;quot;的动作需要同时更新：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;订单服务：状态变为 &lt;code&gt;paid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;库存服务：扣减库存&lt;/li&gt;
&lt;li&gt;积分服务：累积积分&lt;/li&gt;
&lt;li&gt;通知服务：发送支付成功消息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;传统的做法是用分布式事务或者消息队列来做最终一致性。但问题在于——如果订单服务更新了状态，但积分服务处理失败了，你如何回溯到底发生了什么？&lt;/p&gt;
&lt;p&gt;数据库里只有最终状态，没有过程。你无法区分&amp;quot;积分服务从未收到消息&amp;quot;和&amp;quot;积分服务收到了消息但处理失败&amp;quot;。&lt;/p&gt;
&lt;h3 id=&#34;第三个裂缝查询需求与写入模型的矛盾&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%b8%89%e4%b8%aa%e8%a3%82%e7%bc%9d%e6%9f%a5%e8%af%a2%e9%9c%80%e6%b1%82%e4%b8%8e%e5%86%99%e5%85%a5%e6%a8%a1%e5%9e%8b%e7%9a%84%e7%9f%9b%e7%9b%be&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第三个裂缝：查询需求与写入模型的矛盾
&lt;/h3&gt;&lt;p&gt;数据团队需要分析&amp;quot;用户在下单后多久完成支付&amp;quot;。这个需求看似简单，但在 CRUD 模型下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;orders&lt;/code&gt; 表只有 &lt;code&gt;created_at&lt;/code&gt; 和 &lt;code&gt;paid_at&lt;/code&gt;，没有中间的犹豫、取消、重新下单的过程&lt;/li&gt;
&lt;li&gt;如果用户先下单、取消、再下单、再支付，&lt;code&gt;paid_at - created_at&lt;/code&gt; 算的是第二次下单到支付的时间，丢失了第一次尝试的信息&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;查询需求&lt;/th&gt;
					&lt;th&gt;CRUD 模型下的困境&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;订单完整变更历史&lt;/td&gt;
					&lt;td&gt;需要额外维护 log 表，与主表容易不一致&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;用户行为路径分析&lt;/td&gt;
					&lt;td&gt;状态快照无法还原过程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;跨时间维度的统计&lt;/td&gt;
					&lt;td&gt;需要定时快照或 CDC，引入额外复杂度&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;审计合规要求&lt;/td&gt;
					&lt;td&gt;UPDATE 操作天然覆盖历史数据&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;CRUD 模型的核心假设是&amp;quot;我们只关心当前状态&amp;quot;。当这个假设不再成立时，整个架构的地基就开始动摇。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;第二部分event-sourcing-的核心思想&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%ba%8c%e9%83%a8%e5%88%86event-sourcing-%e7%9a%84%e6%a0%b8%e5%bf%83%e6%80%9d%e6%83%b3&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第二部分：Event Sourcing 的核心思想
&lt;/h2&gt;&lt;h3 id=&#34;不是存储状态而是存储事实&#34;&gt;&lt;a href=&#34;#%e4%b8%8d%e6%98%af%e5%ad%98%e5%82%a8%e7%8a%b6%e6%80%81%e8%80%8c%e6%98%af%e5%ad%98%e5%82%a8%e4%ba%8b%e5%ae%9e&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;不是存储状态，而是存储事实
&lt;/h3&gt;&lt;p&gt;Event Sourcing（事件溯源）的核心思想可以用一句话概括：&lt;strong&gt;不要存储实体的当前状态，而是存储导致当前状态的所有事件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以订单为例，传统 CRUD 存储的是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;order_id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ORD-20260710-001&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;shipped&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;total_amount&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;299.00&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;shipping_address&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;上海市浦东新区...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;updated_at&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;2026-07-10T14:30:00&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;而 Event Sourcing 存储的是一系列不可变事件：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1. OrderCreated      { order_id: &amp;#34;ORD-001&amp;#34;, items: [...], amount: 299.00 }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2. PaymentReceived   { order_id: &amp;#34;ORD-001&amp;#34;, amount: 299.00, method: &amp;#34;alipay&amp;#34; }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3. OrderShipped      { order_id: &amp;#34;ORD-001&amp;#34;, tracking_no: &amp;#34;SF123456&amp;#34;, carrier: &amp;#34;顺丰&amp;#34; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;当前状态？不需要存储。把这三个事件按顺序重放（replay），就能还原出&amp;quot;已发货&amp;quot;的当前状态。&lt;/p&gt;
&lt;h3 id=&#34;事件不是日志&#34;&gt;&lt;a href=&#34;#%e4%ba%8b%e4%bb%b6%e4%b8%8d%e6%98%af%e6%97%a5%e5%bf%97&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;事件不是日志
&lt;/h3&gt;&lt;p&gt;这是最常见的误解。很多团队说&amp;quot;我们已经有操作日志了，不就是事件溯源吗？&amp;quot;&lt;/p&gt;
&lt;p&gt;不是。区别在于：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;操作日志 (Audit Log)&lt;/th&gt;
					&lt;th&gt;事件 (Domain Event)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;定位&lt;/td&gt;
					&lt;td&gt;辅助性的旁路记录&lt;/td&gt;
					&lt;td&gt;系统的唯一事实来源 (Source of Truth)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;完整性&lt;/td&gt;
					&lt;td&gt;可以丢失，不影响业务&lt;/td&gt;
					&lt;td&gt;丢失任何一个都无法还原正确状态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;粒度&lt;/td&gt;
					&lt;td&gt;通常是粗粒度的操作记录&lt;/td&gt;
					&lt;td&gt;细粒度的领域事实&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可重放性&lt;/td&gt;
					&lt;td&gt;不支持&lt;/td&gt;
					&lt;td&gt;完整重放可还原任意时刻的状态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;与主数据的关系&lt;/td&gt;
					&lt;td&gt;从属关系，主数据在数据库&lt;/td&gt;
					&lt;td&gt;事件就是主数据&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;操作日志是&amp;quot;顺便记一下&amp;quot;，事件溯源是&amp;quot;只记这个&amp;quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;事件流的结构设计&#34;&gt;&lt;a href=&#34;#%e4%ba%8b%e4%bb%b6%e6%b5%81%e7%9a%84%e7%bb%93%e6%9e%84%e8%ae%be%e8%ae%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;事件流的结构设计
&lt;/h3&gt;&lt;p&gt;一个设计良好的事件需要包含几个关键部分：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Event&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;event_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;      &lt;span class=&#34;err&#34;&gt;全局唯一标识&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UUID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;event_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;    &lt;span class=&#34;err&#34;&gt;事件类型&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;如&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;OrderCreated&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;aggregate_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;err&#34;&gt;聚合根标识&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;如订单号&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;sequence&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;      &lt;span class=&#34;err&#34;&gt;该聚合内的事件序号&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;用于乐观并发控制&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;timestamp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;     &lt;span class=&#34;err&#34;&gt;事件发生时间&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;payload&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;       &lt;span class=&#34;err&#34;&gt;事件数据&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JSON&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;metadata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;      &lt;span class=&#34;err&#34;&gt;元数据&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;用户&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ID&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;、&lt;/span&gt;&lt;span class=&#34;ne&#34;&gt;IP&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;、请求&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ID等&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里有一个重要的设计决策：&lt;strong&gt;事件是面向过去的陈述，不是面向未来的指令。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OrderShipped&lt;/code&gt; 是陈述&amp;quot;订单已经发货了&amp;quot;这个事实，而不是指令&amp;quot;请把订单标记为已发货&amp;quot;。这个语义区别看似微妙，却决定了事件能否被安全地重放、投影和分发。&lt;/p&gt;
&lt;h2 id=&#34;第三部分为什么订单系统适合事件溯源&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%b8%89%e9%83%a8%e5%88%86%e4%b8%ba%e4%bb%80%e4%b9%88%e8%ae%a2%e5%8d%95%e7%b3%bb%e7%bb%9f%e9%80%82%e5%90%88%e4%ba%8b%e4%bb%b6%e6%ba%af%e6%ba%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第三部分：为什么订单系统适合事件溯源
&lt;/h2&gt;&lt;p&gt;并不是所有系统都适合事件溯源。一个简单的博客系统用 CRUD 就够了。但订单系统有几个天然特征，使它成为事件溯源的理想场景。&lt;/p&gt;
&lt;h3 id=&#34;特征一状态变更有明确的业务含义&#34;&gt;&lt;a href=&#34;#%e7%89%b9%e5%be%81%e4%b8%80%e7%8a%b6%e6%80%81%e5%8f%98%e6%9b%b4%e6%9c%89%e6%98%8e%e7%a1%ae%e7%9a%84%e4%b8%9a%e5%8a%a1%e5%90%ab%e4%b9%89&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;特征一：状态变更有明确的业务含义
&lt;/h3&gt;&lt;p&gt;订单的每一次状态变更都对应一个具体的业务动作——创建、支付、发货、退款。这些动作天然就是&amp;quot;事件&amp;quot;。你不需要硬凑事件，业务本身就在产生事件。&lt;/p&gt;
&lt;h3 id=&#34;特征二历史数据有独立的业务价值&#34;&gt;&lt;a href=&#34;#%e7%89%b9%e5%be%81%e4%ba%8c%e5%8e%86%e5%8f%b2%e6%95%b0%e6%8d%ae%e6%9c%89%e7%8b%ac%e7%ab%8b%e7%9a%84%e4%b8%9a%e5%8a%a1%e4%bb%b7%e5%80%bc&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;特征二：历史数据有独立的业务价值
&lt;/h3&gt;&lt;p&gt;客服需要看订单历史来做售后决策。风控需要看用户行为序列来识别欺诈。数据团队需要完整的状态流转数据来做漏斗分析。这些需求不是&amp;quot;有了更好&amp;quot;，而是直接影响业务决策的刚需。&lt;/p&gt;
&lt;h3 id=&#34;特征三审计和合规要求&#34;&gt;&lt;a href=&#34;#%e7%89%b9%e5%be%81%e4%b8%89%e5%ae%a1%e8%ae%a1%e5%92%8c%e5%90%88%e8%a7%84%e8%a6%81%e6%b1%82&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;特征三：审计和合规要求
&lt;/h3&gt;&lt;p&gt;电商订单涉及资金流转，财务审计要求每一笔状态变更都可追溯。在很多地区的法规下，你需要能回答&amp;quot;这笔订单在某个时间点的状态是什么&amp;quot;以及&amp;quot;为什么变成了现在的状态&amp;quot;。CRUD 模式下的 UPDATE 覆盖天然无法满足这个要求。&lt;/p&gt;
&lt;h3 id=&#34;特征四多个下游系统需要消费状态变更&#34;&gt;&lt;a href=&#34;#%e7%89%b9%e5%be%81%e5%9b%9b%e5%a4%9a%e4%b8%aa%e4%b8%8b%e6%b8%b8%e7%b3%bb%e7%bb%9f%e9%9c%80%e8%a6%81%e6%b6%88%e8%b4%b9%e7%8a%b6%e6%80%81%e5%8f%98%e6%9b%b4&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;特征四：多个下游系统需要消费状态变更
&lt;/h3&gt;&lt;p&gt;订单状态变更后，库存、物流、积分、通知、推荐等系统都需要做出响应。事件天然是消息——它既是你自己的状态变更记录，也是发给下游系统的通知。一石二鸟。&lt;/p&gt;
&lt;h3 id=&#34;什么时候不该用事件溯源&#34;&gt;&lt;a href=&#34;#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e4%b8%8d%e8%af%a5%e7%94%a8%e4%ba%8b%e4%bb%b6%e6%ba%af%e6%ba%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;什么时候不该用事件溯源
&lt;/h3&gt;&lt;p&gt;同样重要的是知道什么时候不该用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读多写少且不需要历史的系统&lt;/strong&gt;：比如配置管理、字典表&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;团队对事件驱动架构完全没有经验&lt;/strong&gt;：学习曲线是真实的成本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对延迟极度敏感的场景&lt;/strong&gt;：事件重放有固有延迟（虽然可以通过快照优化）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单的内部管理后台&lt;/strong&gt;：ROI 不划算&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;架构选型的本质不是&amp;quot;哪个更好&amp;quot;，而是&amp;quot;在什么约束条件下，哪个更合适&amp;quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;第四部分cqrs读写分离的必然搭档&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e5%9b%9b%e9%83%a8%e5%88%86cqrs%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb%e7%9a%84%e5%bf%85%e7%84%b6%e6%90%ad%e6%a1%a3&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第四部分：CQRS——读写分离的必然搭档
&lt;/h2&gt;&lt;h3 id=&#34;事件溯源带来的读取问题&#34;&gt;&lt;a href=&#34;#%e4%ba%8b%e4%bb%b6%e6%ba%af%e6%ba%90%e5%b8%a6%e6%9d%a5%e7%9a%84%e8%af%bb%e5%8f%96%e9%97%ae%e9%a2%98&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;事件溯源带来的读取问题
&lt;/h3&gt;&lt;p&gt;事件溯源解决了&amp;quot;写入&amp;quot;和&amp;quot;历史追溯&amp;quot;的问题，但引入了一个新问题：&lt;strong&gt;如何高效读取当前状态？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果你每次查询一个订单的详情，都需要从事件流的第一个事件开始重放，这在事件数量增长后会成为严重的性能瓶颈。一个活跃了一年的订单可能有几百个事件，一个活跃用户可能有几千个事件。&lt;/p&gt;
&lt;p&gt;这就是 CQRS（Command Query Responsibility Segregation，命令查询职责分离）出场的时候。&lt;/p&gt;
&lt;h3 id=&#34;cqrs-的核心思想&#34;&gt;&lt;a href=&#34;#cqrs-%e7%9a%84%e6%a0%b8%e5%bf%83%e6%80%9d%e6%83%b3&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;CQRS 的核心思想
&lt;/h3&gt;&lt;p&gt;CQRS 把系统的读和写分成两个独立的模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命令侧（Write Side）&lt;/strong&gt;：负责接收命令、校验业务规则、产生事件、持久化事件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询侧（Read Side）&lt;/strong&gt;：负责维护一个或多个为读取优化的数据视图&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;用户请求 → [命令侧] → 事件流 → [事件处理器] → 查询视图 → 响应
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            (写入)              (投影/Projection)     (读取)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在订单系统中，命令侧处理&amp;quot;创建订单&amp;quot;&amp;ldquo;支付订单&amp;quot;&amp;ldquo;取消订单&amp;quot;等写操作，查询侧维护&amp;quot;订单列表&amp;quot;&amp;ldquo;订单详情&amp;quot;&amp;ldquo;用户订单统计&amp;quot;等读视图。&lt;/p&gt;
&lt;h3 id=&#34;投影projection从事件到视图&#34;&gt;&lt;a href=&#34;#%e6%8a%95%e5%bd%b1projection%e4%bb%8e%e4%ba%8b%e4%bb%b6%e5%88%b0%e8%a7%86%e5%9b%be&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;投影（Projection）：从事件到视图
&lt;/h3&gt;&lt;p&gt;投影是 CQRS 中最关键的概念之一。它是一个将事件流转换为查询视图的过程。&lt;/p&gt;
&lt;p&gt;以一个&amp;quot;订单列表&amp;quot;视图为例：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;EventStore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ├── OrderCreated    → 在 order_list_view 中插入一行
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ├── PaymentReceived → 更新该行的 status 字段
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ├── OrderShipped    → 更新该行的 status 和 tracking_no
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  └── OrderCancelled  → 更新该行的 status 为 cancelled
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;投影函数是纯函数——给定相同的事件序列，它总是产生相同的视图状态。这意味着你可以随时从头重建任何一个视图，也可以为不同的查询需求创建不同的视图。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;查询视图&lt;/th&gt;
					&lt;th&gt;数据来源&lt;/th&gt;
					&lt;th&gt;存储形式&lt;/th&gt;
					&lt;th&gt;优化方向&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;订单列表&lt;/td&gt;
					&lt;td&gt;OrderCreated + 状态变更事件&lt;/td&gt;
					&lt;td&gt;关系数据库表&lt;/td&gt;
					&lt;td&gt;分页、筛选、排序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;订单详情&lt;/td&gt;
					&lt;td&gt;该订单的完整事件流&lt;/td&gt;
					&lt;td&gt;文档数据库 / 缓存&lt;/td&gt;
					&lt;td&gt;一次查询获取完整信息&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;用户订单统计&lt;/td&gt;
					&lt;td&gt;所有用户的订单事件&lt;/td&gt;
					&lt;td&gt;列式存储 / 物化视图&lt;/td&gt;
					&lt;td&gt;聚合计算&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;订单变更时间线&lt;/td&gt;
					&lt;td&gt;该订单的所有事件 + 元数据&lt;/td&gt;
					&lt;td&gt;时序存储&lt;/td&gt;
					&lt;td&gt;按时间排列展示&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;最终一致性必须接受的代价&#34;&gt;&lt;a href=&#34;#%e6%9c%80%e7%bb%88%e4%b8%80%e8%87%b4%e6%80%a7%e5%bf%85%e9%a1%bb%e6%8e%a5%e5%8f%97%e7%9a%84%e4%bb%a3%e4%bb%b7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;最终一致性：必须接受的代价
&lt;/h3&gt;&lt;p&gt;CQRS + Event Sourcing 引入了最终一致性。当用户支付完成后，订单详情视图可能不是立即更新的——中间有几百毫秒甚至几秒的延迟。&lt;/p&gt;
&lt;p&gt;这在很多场景下是可以接受的。用户支付后看到&amp;quot;处理中&amp;quot;是合理的体验。但对于一些场景——比如用户支付后立刻查看订单状态——你需要在前端做适当的 UX 处理（乐观更新、加载态等）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最终一致性不是缺陷，而是分布式系统的客观现实。CQRS 只是诚实地面对了这个现实，而不是试图用分布式事务来掩盖它。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;第五部分重构路径从-crud-到事件溯源的渐进式迁移&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%ba%94%e9%83%a8%e5%88%86%e9%87%8d%e6%9e%84%e8%b7%af%e5%be%84%e4%bb%8e-crud-%e5%88%b0%e4%ba%8b%e4%bb%b6%e6%ba%af%e6%ba%90%e7%9a%84%e6%b8%90%e8%bf%9b%e5%bc%8f%e8%bf%81%e7%a7%bb&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第五部分：重构路径——从 CRUD 到事件溯源的渐进式迁移
&lt;/h2&gt;&lt;h3 id=&#34;为什么不能一步到位&#34;&gt;&lt;a href=&#34;#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e8%83%bd%e4%b8%80%e6%ad%a5%e5%88%b0%e4%bd%8d&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;为什么不能一步到位
&lt;/h3&gt;&lt;p&gt;理论上，你可以停下所有开发，花三个月把整个订单系统重写为事件溯源架构。实际上，这在大多数团队中是不可能的——业务不会等你，团队需要持续交付。&lt;/p&gt;
&lt;p&gt;更关键的是，一步到位的重写风险极高。你无法在生产环境验证新架构之前，就废弃旧系统。&lt;/p&gt;
&lt;h3 id=&#34;阶段一旁路事件捕获strangler-fig-模式&#34;&gt;&lt;a href=&#34;#%e9%98%b6%e6%ae%b5%e4%b8%80%e6%97%81%e8%b7%af%e4%ba%8b%e4%bb%b6%e6%8d%95%e8%8e%b7strangler-fig-%e6%a8%a1%e5%bc%8f&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;阶段一：旁路事件捕获（Strangler Fig 模式）
&lt;/h3&gt;&lt;p&gt;第一步不是改造写入侧，而是在现有 CRUD 系统旁边添加事件捕获能力。&lt;/p&gt;
&lt;p&gt;具体做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在现有的订单状态变更方法中，每次 UPDATE 之后，同步写入一条事件到事件存储&lt;/li&gt;
&lt;li&gt;事件存储此时是旁路记录，不是 Source of Truth&lt;/li&gt;
&lt;li&gt;所有现有的查询仍然走原来的数据库&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[用户请求] → [现有 OrderService] → [数据库 UPDATE]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  └→ [EventPublisher] → [EventStore (旁路)]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                                            │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                                            └→ [Projection] → [Read Model (试验)]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个阶段的目标是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;验证事件模型的完整性——能否从事件重放出正确的当前状态&lt;/li&gt;
&lt;li&gt;验证投影的正确性——Read Model 和原有数据库的查询结果是否一致&lt;/li&gt;
&lt;li&gt;团队开始熟悉事件驱动的思维模式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;阶段二读写分离&#34;&gt;&lt;a href=&#34;#%e9%98%b6%e6%ae%b5%e4%ba%8c%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;阶段二：读写分离
&lt;/h3&gt;&lt;p&gt;当旁路事件捕获稳定运行一段时间后，开始将查询流量逐步迁移到 Read Model。&lt;/p&gt;
&lt;p&gt;具体做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新建查询 API，指向 Read Model&lt;/li&gt;
&lt;li&gt;前端和调用方逐步切换到新 API&lt;/li&gt;
&lt;li&gt;监控新 API 的数据一致性和性能&lt;/li&gt;
&lt;li&gt;双写双读一段时间，确认无误后关闭旧查询路径&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个阶段的关键是&lt;strong&gt;灰度切换&lt;/strong&gt;。可以先切 10% 的查询流量到新 API，观察一段时间，再逐步提升比例。&lt;/p&gt;
&lt;h3 id=&#34;阶段三切换写入侧&#34;&gt;&lt;a href=&#34;#%e9%98%b6%e6%ae%b5%e4%b8%89%e5%88%87%e6%8d%a2%e5%86%99%e5%85%a5%e4%be%a7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;阶段三：切换写入侧
&lt;/h3&gt;&lt;p&gt;这是最关键的一步——将事件存储从&amp;quot;旁路记录&amp;quot;升级为&amp;quot;Source of Truth&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;具体做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;命令侧不再直接 UPDATE 数据库，而是只做业务校验 + 写入事件&lt;/li&gt;
&lt;li&gt;原有的数据库表降级为投影之一&lt;/li&gt;
&lt;li&gt;所有写入操作的事务边界变为&amp;quot;事件写入成功即提交&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[用户请求] → [CommandHandler]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  ├→ 加载聚合根 (从事件流重放)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  ├→ 执行业务逻辑
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  ├→ 产生新事件
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                  └→ 写入 EventStore (Source of Truth)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                        │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                        └→ [Event Handlers] → 更新各 Read Model
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;阶段四优化和演进&#34;&gt;&lt;a href=&#34;#%e9%98%b6%e6%ae%b5%e5%9b%9b%e4%bc%98%e5%8c%96%e5%92%8c%e6%bc%94%e8%bf%9b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;阶段四：优化和演进
&lt;/h3&gt;&lt;p&gt;系统完全切换到事件溯源后，可以进一步优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快照（Snapshot）&lt;/strong&gt;：对事件数量多的聚合根，定期保存状态快照，减少重放开销&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件版本管理&lt;/strong&gt;：业务演进导致事件结构变化时，通过版本号 + 升级函数来处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saga 编排&lt;/strong&gt;：跨聚合根的业务流程用 Saga 模式协调&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时间旅行查询&lt;/strong&gt;：利用事件的不可变性，支持&amp;quot;查看某个时间点的状态&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;迁移过程中的常见陷阱&#34;&gt;&lt;a href=&#34;#%e8%bf%81%e7%a7%bb%e8%bf%87%e7%a8%8b%e4%b8%ad%e7%9a%84%e5%b8%b8%e8%a7%81%e9%99%b7%e9%98%b1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;迁移过程中的常见陷阱
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;陷阱&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;应对策略&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;事件粒度不当&lt;/td&gt;
					&lt;td&gt;事件太粗（一个事件包含太多变更）或太细（每个字段变更都是事件）&lt;/td&gt;
					&lt;td&gt;事件粒度对齐业务动作，不是数据库操作&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事件包含过多数据&lt;/td&gt;
					&lt;td&gt;把整个订单对象塞进每个事件&lt;/td&gt;
					&lt;td&gt;事件只包含增量信息和必要的上下文&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;忽略幂等性&lt;/td&gt;
					&lt;td&gt;事件重放导致重复处理&lt;/td&gt;
					&lt;td&gt;所有事件处理器和投影函数必须是幂等的&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;低估数据迁移&lt;/td&gt;
					&lt;td&gt;旧数据无法完美转换为事件流&lt;/td&gt;
					&lt;td&gt;接受&amp;quot;切割点&amp;rdquo;——旧数据用快照表示，新数据用事件&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;查询侧过度设计&lt;/td&gt;
					&lt;td&gt;为每个查询需求都建一个 Read Model&lt;/td&gt;
					&lt;td&gt;先合并相似查询，只在性能需要时拆分&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;第六部分聚合根设计订单系统的事件建模&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e5%85%ad%e9%83%a8%e5%88%86%e8%81%9a%e5%90%88%e6%a0%b9%e8%ae%be%e8%ae%a1%e8%ae%a2%e5%8d%95%e7%b3%bb%e7%bb%9f%e7%9a%84%e4%ba%8b%e4%bb%b6%e5%bb%ba%e6%a8%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第六部分：聚合根设计——订单系统的事件建模
&lt;/h2&gt;&lt;h3 id=&#34;什么是聚合根&#34;&gt;&lt;a href=&#34;#%e4%bb%80%e4%b9%88%e6%98%af%e8%81%9a%e5%90%88%e6%a0%b9&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;什么是聚合根
&lt;/h3&gt;&lt;p&gt;在领域驱动设计中，聚合根（Aggregate Root）是一组相关对象的根入口，外部只能通过聚合根来修改其内部状态。在事件溯源中，聚合根也是事件流的边界——每个聚合根有自己独立的事件流。&lt;/p&gt;
&lt;h3 id=&#34;订单系统的聚合根划分&#34;&gt;&lt;a href=&#34;#%e8%ae%a2%e5%8d%95%e7%b3%bb%e7%bb%9f%e7%9a%84%e8%81%9a%e5%90%88%e6%a0%b9%e5%88%92%e5%88%86&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;订单系统的聚合根划分
&lt;/h3&gt;&lt;p&gt;一个常见的错误是把所有和订单相关的东西都塞进一个&amp;quot;订单&amp;quot;聚合根。实际上，合理的划分可能是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;聚合根 1：订单（Order）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;负责订单的核心生命周期：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OrderCreated          → 创建订单
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OrderLineAdded        → 添加商品行
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OrderLineRemoved      → 移除商品行
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OrderConfirmed        → 用户确认订单
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OrderCancelled        → 取消订单
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;聚合根 2：支付（Payment）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;负责支付流程：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PaymentInitiated      → 发起支付
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PaymentReceived       → 支付成功
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PaymentFailed         → 支付失败
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PaymentRefunded       → 退款完成
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;聚合根 3：履约（Fulfillment）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;负责发货和物流：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;FulfillmentRequested  → 请求发货
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ShipmentCreated       → 创建物流单
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ShipmentDelivered     → 签收确认
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么要把支付和履约拆成独立的聚合根？因为它们有独立的生命周期和一致性边界。一个订单可以有多次支付尝试（每次是独立的 Payment 聚合），一个订单也可以拆成多个包裹发货（每个是独立的 Fulfillment 聚合）。&lt;/p&gt;
&lt;h3 id=&#34;跨聚合根的协调saga-模式&#34;&gt;&lt;a href=&#34;#%e8%b7%a8%e8%81%9a%e5%90%88%e6%a0%b9%e7%9a%84%e5%8d%8f%e8%b0%83saga-%e6%a8%a1%e5%bc%8f&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;跨聚合根的协调：Saga 模式
&lt;/h3&gt;&lt;p&gt;当用户支付成功后，需要触发发货流程。但 Payment 和 Fulfillment 是两个独立的聚合根，不能在同一个事务中操作。&lt;/p&gt;
&lt;p&gt;这时需要 Saga 模式——一个编排器（Orchestrator）来监听事件并发出命令：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PaymentReceived (事件)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └→ OrderSaga (编排器)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          │
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          ├→ 发出命令: ConfirmOrder (给 Order 聚合根)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          ├→ 发出命令: RequestFulfillment (给 Fulfillment 聚合根)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          └→ 发出命令: AwardPoints (给积分系统)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果 &lt;code&gt;RequestFulfillment&lt;/code&gt; 失败了，Saga 需要执行补偿操作——比如发起退款。这就是 Saga 的&amp;quot;补偿事务&amp;quot;机制。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Saga 不是分布式事务的替代品，而是对&amp;quot;长事务&amp;quot;的务实解法。它用一系列本地事务 + 补偿逻辑来替代全局锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;第七部分事件存储的技术选型&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%b8%83%e9%83%a8%e5%88%86%e4%ba%8b%e4%bb%b6%e5%ad%98%e5%82%a8%e7%9a%84%e6%8a%80%e6%9c%af%e9%80%89%e5%9e%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第七部分：事件存储的技术选型
&lt;/h2&gt;&lt;h3 id=&#34;eventstoredb-vs-关系数据库-vs-kafka&#34;&gt;&lt;a href=&#34;#eventstoredb-vs-%e5%85%b3%e7%b3%bb%e6%95%b0%e6%8d%ae%e5%ba%93-vs-kafka&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;EventStoreDB vs 关系数据库 vs Kafka
&lt;/h3&gt;&lt;p&gt;事件存储是整个架构的基石，选型至关重要。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方案&lt;/th&gt;
					&lt;th&gt;优势&lt;/th&gt;
					&lt;th&gt;劣势&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;EventStoreDB&lt;/td&gt;
					&lt;td&gt;专为事件溯源设计，支持投影、订阅、快照&lt;/td&gt;
					&lt;td&gt;运维复杂度较高，社区相对小&lt;/td&gt;
					&lt;td&gt;重度事件溯源系统&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;PostgreSQL + 事件表&lt;/td&gt;
					&lt;td&gt;团队熟悉，运维简单，事务支持好&lt;/td&gt;
					&lt;td&gt;不是为事件流设计的，大规模下性能有限&lt;/td&gt;
					&lt;td&gt;初期或中等规模系统&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Kafka&lt;/td&gt;
					&lt;td&gt;天然的事件流，高吞吐，生态丰富&lt;/td&gt;
					&lt;td&gt;不保证事件顺序（需要分区策略），没有内置的聚合根概念&lt;/td&gt;
					&lt;td&gt;高吞吐、事件分发场景&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自研方案&lt;/td&gt;
					&lt;td&gt;完全按需定制&lt;/td&gt;
					&lt;td&gt;开发和维护成本极高&lt;/td&gt;
					&lt;td&gt;极端特殊需求&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;对于电商订单系统，一个务实的路径是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初期&lt;/strong&gt;：用 PostgreSQL 的一张 &lt;code&gt;events&lt;/code&gt; 表做事件存储，足够支撑日均百万级订单&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中期&lt;/strong&gt;：当单表行数超过千万，引入分区表或切换到 EventStoreDB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大规模&lt;/strong&gt;：Kafka 做事件总线 + EventStoreDB 做持久化存储&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;postgresql-事件表的设计&#34;&gt;&lt;a href=&#34;#postgresql-%e4%ba%8b%e4%bb%b6%e8%a1%a8%e7%9a%84%e8%ae%be%e8%ae%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;PostgreSQL 事件表的设计
&lt;/h3&gt;&lt;p&gt;如果选择 PostgreSQL 作为事件存储，表结构大致如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;events&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;event_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UUID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;gen_random_uuid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aggregate_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UUID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aggregate_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;event_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;VARCHAR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JSONB&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;metadata&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;JSONB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sequence&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;BIGINT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;created_at&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;DEFAULT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;now&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;UNIQUE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aggregate_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sequence&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INDEX&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;idx_events_aggregate&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ON&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;events&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;aggregate_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sequence&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;INDEX&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;idx_events_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;ON&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;events&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;event_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;created_at&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;UNIQUE (aggregate_id, sequence)&lt;/code&gt; 这个约束至关重要——它保证了同一个聚合根的事件序号不会重复，实现了乐观并发控制。当两个请求同时尝试修改同一个订单时，只有一个能成功写入下一个序号的事件，另一个会因为唯一约束冲突而失败并重试。&lt;/p&gt;
&lt;h2 id=&#34;第八部分性能优化快照与缓存策略&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e5%85%ab%e9%83%a8%e5%88%86%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96%e5%bf%ab%e7%85%a7%e4%b8%8e%e7%bc%93%e5%ad%98%e7%ad%96%e7%95%a5&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第八部分：性能优化——快照与缓存策略
&lt;/h2&gt;&lt;h3 id=&#34;快照减少重放开销&#34;&gt;&lt;a href=&#34;#%e5%bf%ab%e7%85%a7%e5%87%8f%e5%b0%91%e9%87%8d%e6%94%be%e5%bc%80%e9%94%80&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;快照：减少重放开销
&lt;/h3&gt;&lt;p&gt;当一个聚合根的事件数量增长到数百甚至数千时，每次加载聚合根都需要重放所有事件，性能会显著下降。&lt;/p&gt;
&lt;p&gt;快照的机制很简单：每隔 N 个事件（或每隔一段时间），将聚合根的当前状态序列化后存储。下次加载时，先找到最近的快照，然后只重放快照之后的事件。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;加载 Order#001:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1. 查找最近的快照 → 第 450 个事件后的状态
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2. 重放第 451~478 个事件
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3. 得到当前状态
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;快照不影响事件流的完整性——它只是一个性能优化，可以随时从事件流重新生成。&lt;/p&gt;
&lt;h3 id=&#34;查询侧的缓存策略&#34;&gt;&lt;a href=&#34;#%e6%9f%a5%e8%af%a2%e4%be%a7%e7%9a%84%e7%bc%93%e5%ad%98%e7%ad%96%e7%95%a5&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;查询侧的缓存策略
&lt;/h3&gt;&lt;p&gt;Read Model 天然适合缓存，因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件流是 append-only 的，不存在 UPDATE 操作&lt;/li&gt;
&lt;li&gt;当有新事件到来时，可以精确地知道哪些缓存需要失效&lt;/li&gt;
&lt;li&gt;不同视图的缓存策略可以独立配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个典型的缓存分层：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;请求 → Redis 缓存 → Read Model 数据库 → (重建) ← 事件流
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         ↑ 失效通知
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    事件处理器
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;对于订单详情这种&amp;quot;写一次、读多次&amp;quot;的场景，缓存命中率通常很高。&lt;/p&gt;
&lt;h2 id=&#34;第九部分事件溯源带来的额外收益&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%b9%9d%e9%83%a8%e5%88%86%e4%ba%8b%e4%bb%b6%e6%ba%af%e6%ba%90%e5%b8%a6%e6%9d%a5%e7%9a%84%e9%a2%9d%e5%a4%96%e6%94%b6%e7%9b%8a&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第九部分：事件溯源带来的额外收益
&lt;/h2&gt;&lt;h3 id=&#34;时间旅行查询&#34;&gt;&lt;a href=&#34;#%e6%97%b6%e9%97%b4%e6%97%85%e8%a1%8c%e6%9f%a5%e8%af%a2&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;时间旅行查询
&lt;/h3&gt;&lt;p&gt;由于所有历史事件都被完整保留，你可以回答一些在 CRUD 模式下根本无法回答的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;这个订单在 3 月 15 日下午 2 点的状态是什么？&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;上个月有多少订单经历了&amp;rsquo;取消→重新下单&amp;rsquo;的路径？&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;如果我们的退款策略改为自动审批，有多少历史订单会受到影响？&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第三个问题尤其有价值——它允许你用历史事件来模拟新业务规则的效果，而不需要在生产环境做 A/B 测试。&lt;/p&gt;
&lt;h3 id=&#34;事件作为集成枢纽&#34;&gt;&lt;a href=&#34;#%e4%ba%8b%e4%bb%b6%e4%bd%9c%e4%b8%ba%e9%9b%86%e6%88%90%e6%9e%a2%e7%ba%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;事件作为集成枢纽
&lt;/h3&gt;&lt;p&gt;在微服务架构中，事件天然是服务间通信的最佳载体。订单系统产生的事件可以被：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;推荐系统消费，用于优化推荐策略&lt;/li&gt;
&lt;li&gt;风控系统消费，用于识别异常行为&lt;/li&gt;
&lt;li&gt;数据仓库消费，用于离线分析&lt;/li&gt;
&lt;li&gt;客服系统消费，用于构建用户全景视图&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个消费方可以根据自己的需要，选择不同的投影方式来处理同一组事件。事件的生产者不需要知道谁会消费，消费者不需要了解生产者的内部实现。&lt;/p&gt;
&lt;h3 id=&#34;可测试性的提升&#34;&gt;&lt;a href=&#34;#%e5%8f%af%e6%b5%8b%e8%af%95%e6%80%a7%e7%9a%84%e6%8f%90%e5%8d%87&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;可测试性的提升
&lt;/h3&gt;&lt;p&gt;事件溯源对测试有显著的正面影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;聚合根测试&lt;/strong&gt;：给定一组输入事件，断言产生的输出事件。不需要 mock 数据库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;投影测试&lt;/strong&gt;：给定一组事件，断言产生的视图状态。纯函数，容易测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集成测试&lt;/strong&gt;：用内存事件存储跑完整流程，速度快、隔离性好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回归测试&lt;/strong&gt;：把生产环境的事件流导出，在新版本上重放，对比结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第十部分团队和组织层面的挑战&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e5%8d%81%e9%83%a8%e5%88%86%e5%9b%a2%e9%98%9f%e5%92%8c%e7%bb%84%e7%bb%87%e5%b1%82%e9%9d%a2%e7%9a%84%e6%8c%91%e6%88%98&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第十部分：团队和组织层面的挑战
&lt;/h2&gt;&lt;h3 id=&#34;思维模式的转换&#34;&gt;&lt;a href=&#34;#%e6%80%9d%e7%bb%b4%e6%a8%a1%e5%bc%8f%e7%9a%84%e8%bd%ac%e6%8d%a2&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;思维模式的转换
&lt;/h3&gt;&lt;p&gt;事件溯源要求开发者从&amp;quot;状态思维&amp;quot;切换到&amp;quot;过程思维&amp;quot;。在 CRUD 模式下，你思考的是&amp;quot;这个字段应该设为什么值&amp;quot;；在事件溯源下，你思考的是&amp;quot;发生了什么事实&amp;quot;。&lt;/p&gt;
&lt;p&gt;这个转换不是一天就能完成的。团队成员在初期经常会写出&amp;quot;事件风格的 CRUD&amp;quot;——形式上是事件，语义上还是状态变更。&lt;/p&gt;
&lt;p&gt;建议的做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;组织事件风暴（Event Storming）工作坊，让团队一起梳理业务流程中的事件&lt;/li&gt;
&lt;li&gt;在代码审查中重点关注事件的命名——应该是过去时态的陈述句&lt;/li&gt;
&lt;li&gt;从最简单的聚合根开始练习，不要一开始就设计复杂的 Saga&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;运维复杂度的增加&#34;&gt;&lt;a href=&#34;#%e8%bf%90%e7%bb%b4%e5%a4%8d%e6%9d%82%e5%ba%a6%e7%9a%84%e5%a2%9e%e5%8a%a0&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;运维复杂度的增加
&lt;/h3&gt;&lt;p&gt;事件溯源系统的运维比 CRUD 系统复杂：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件存储的磁盘占用会持续增长（事件不可删除）&lt;/li&gt;
&lt;li&gt;投影的延迟需要监控和告警&lt;/li&gt;
&lt;li&gt;事件版本管理需要额外的工具支持&lt;/li&gt;
&lt;li&gt;调试时需要理解事件流的因果关系，不能只看当前状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些是真实的成本，在决策时必须纳入考量。&lt;/p&gt;
&lt;h3 id=&#34;什么时候应该止损&#34;&gt;&lt;a href=&#34;#%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e5%ba%94%e8%af%a5%e6%ad%a2%e6%8d%9f&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;什么时候应该止损
&lt;/h3&gt;&lt;p&gt;不是所有的重构都必须完成。如果在阶段一（旁路事件捕获）执行后发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件模型无法完整表达业务语义&lt;/li&gt;
&lt;li&gt;团队始终无法适应事件驱动的思维方式&lt;/li&gt;
&lt;li&gt;性能开销超过了业务可接受的范围&lt;/li&gt;
&lt;li&gt;维护两套系统的成本高于收益&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么保留 CRUD + 旁路事件日志的模式也是完全可以接受的。架构演进的目标是解决问题，不是追求纯粹。&lt;/p&gt;
&lt;h2 id=&#34;架构决策的评估框架&#34;&gt;&lt;a href=&#34;#%e6%9e%b6%e6%9e%84%e5%86%b3%e7%ad%96%e7%9a%84%e8%af%84%e4%bc%b0%e6%a1%86%e6%9e%b6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;架构决策的评估框架
&lt;/h2&gt;&lt;p&gt;回到最初的问题：何时该从 CRUD 转向事件溯源？&lt;/p&gt;
&lt;p&gt;可以用一个简单的评估矩阵来辅助决策：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;评估维度&lt;/th&gt;
					&lt;th&gt;权重&lt;/th&gt;
					&lt;th&gt;CRUD 得分 (1-5)&lt;/th&gt;
					&lt;th&gt;Event Sourcing 得分 (1-5)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;历史追溯需求&lt;/td&gt;
					&lt;td&gt;高&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;审计合规要求&lt;/td&gt;
					&lt;td&gt;高&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;下游消费者数量&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;团队经验储备&lt;/td&gt;
					&lt;td&gt;高&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;2&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;运维成熟度&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;业务复杂度&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;性能要求（延迟）&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;迭代速度要求&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;3&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;当&amp;quot;历史追溯&amp;quot;&amp;ldquo;审计合规&amp;quot;&amp;ldquo;下游消费者&amp;quot;这几个高权重维度的需求足够强时，即使团队经验不足，也值得投入学习成本来引入事件溯源。反之，如果这些需求都不强烈，CRUD + 操作日志的补丁方案可能是更务实的选择。&lt;/p&gt;
&lt;p&gt;架构决策从来不是非此即彼的二元选择。在 CRUD 和完整的 Event Sourcing + CQRS 之间，存在很多中间态——部分聚合根用事件溯源、部分用 CRUD，先做 CQRS 读写分离再逐步引入事件存储，用 CDC（变更数据捕获）替代手工事件发布——这些都是合理的路径。&lt;/p&gt;
&lt;p&gt;关键在于理解每种选择的代价和收益，然后基于自己团队和业务的真实约束做出判断。有句话说，好的架构不是最复杂的架构，而是在当前约束下最合理的架构。对于订单系统来说，当&amp;quot;发生了什么&amp;quot;比&amp;quot;现在是什么&amp;quot;更重要的时候，就是考虑事件溯源的时候了。&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
