提示:本网站现在为维护调试状态

CQRS + Event Sourcing 架構模式解析

Choyeon
Choyeon
2026年9月14日 1 分鐘閱讀 381 閱讀
702 字 2,423 字元 更新於 2026年11月8日
CQRS + Event Sourcing 架構模式解析
目錄

CQRS + Event Sourcing 架构模式解析

当业务发展到一定规模,传统 CRUD 读写同库模型在复杂查询/审计/并发控制遇到瓶颈。CQRS + ES 是打破瓶颈的高级模式,但复杂度成倍上升,必须慎重选型。

Event Sourcing:事件为真相源

不存当前状态,只存导致状态变化的事件流(OrderCreated → ItemAdded → OrderPlaced)。重放所有事件可重建任意时间点状态,天然拥有完整审计轨迹。配合快照避免每次从 0 重放。

from __future__ import annotations
import json, time
from dataclasses import dataclass, field
from typing import Callable, Iterable, Any
from collections import defaultdict

@dataclass
class DomainEvent:
    event_id: str
    aggregate_type: str
    aggregate_id: str
    event_type: str
    payload: dict
    occurred_at: float = field(default_factory=time.time)
    version: int = 1

class EventStore:
    def __init__(self) -> None:
        self._events: list[DomainEvent] = []
        self._snapshots: dict[tuple[str, str], tuple[int, Any]] = {}
        self._subs: dict[str, list[Callable[[DomainEvent], None]]] = defaultdict(list)

    def append(self, events: Iterable[DomainEvent], expected_version: int | None = None) -> None:
        events = list(events)
        if expected_version is not None and events:
            at, aid = events[0].aggregate_type, events[0].aggregate_id
            cur = sum(1 for e in self._events if e.aggregate_type == at and e.aggregate_id == aid)
            if cur != expected_version:
                raise ValueError(f"Concurrency conflict: expected v{expected_version}, got v{cur}")
        for e in events:
            self._events.append(e)
            for s in self._subs.get(e.event_type, []) + self._subs.get("*", []): s(e)

    def load(self, agg_type: str, agg_id: str, snapshot_every: int = 50) -> tuple[Any, int]:
        key = (agg_type, agg_id)
        start_ver, state = self._snapshots.get(key, (0, None))
        version = start_ver
        events = [e for e in self._events if e.aggregate_type == agg_type and e.aggregate_id == agg_id][start_ver:]
        for e in events:
            version += 1
            fn = globals().get(f"apply_{e.event_type}")
            state = fn(state, e) if fn else state
            if version % snapshot_every == 0: self._snapshots[key] = (version, state)
        return state, version

    def subscribe(self, event_type: str, fn: Callable[[DomainEvent], None]) -> None:
        self._subs[event_type].append(fn)

def apply_OrderCreated(state, e): return {"order_id": e.aggregate_id, "items": {}, "status": "CREATED", **e.payload}
def apply_ItemAdded(state, e):
    s = dict(state); pid = e.payload["product_id"]
    s["items"] = {**s["items"], pid: s["items"].get(pid, 0) + e.payload["qty"]}
    return s
def apply_OrderPlaced(state, e): return {**state, "status": "PLACED", "placed_at": e.occurred_at}

store = EventStore()
projection: dict[str, dict] = {}
store.subscribe("OrderPlaced", lambda e: projection.__setitem__(e.aggregate_id, {"status": "PLACED", **e.payload}))

CQRS:写读模型分离

写入侧走领域模型 + 事件,通过事件订阅异步构建读侧投影表(Projection)。写侧保证一致性,读侧针对查询做反范式和索引,各自最优,但引入最终一致性窗口。

维度 传统 CRUD 纯 CQRS CQRS + Event Sourcing
存储 单表既读又写 写库+读库 事件流+N个投影表
查询能力 中等 极高 极高+时间旅行
审计追溯 额外加字段 难 天生免费
调试难度 低 中高 非常高
适合场景 大部分业务 读多写少查询复杂 金融/账本/溯源

最佳实践

先问三问再决定:业务是否需要不可变审计?查询是否真的复杂到读写冲突?团队是否有 DDD 经验?三个 Yes 才考虑。否则传统 CRUD + 审计表往往 ROI 更高。

X微博Telegram

本文作者

投票

你最希望 Rosetta 优先加入哪项功能?

社区投票(演示数据,可后台删除)

留言 (6)

0 / 3000
东京留学僧
东京留学僧2026年9月12日

楼主,我按照你的步骤操作后启动报错:Error creating bean with name 'dataSource',版本是 Spring Boot 3.2,请问有遇到过吗?

摸鱼达人小王
摸鱼达人小王2026年9月12日

补充一点:实际使用中如果遇到 MySQL 的 8 小时超时问题,可以在连接串里加 autoReconnect=true 参数,虽然官方不推荐但挺好用的。

全栈打工人
全栈打工人2026年9月11日

代码示例里那个 SQL 查询是不是应该加个 LIMIT?不然数据量大的时候全表扫描会很慢哦。

机器学习萌新
机器学习萌新2026年9月11日

写得太好了,终于搞懂了分布式事务的几种方案区别,收藏了!

技术笔记控
技术笔记控tech-notes.fun2026年9月11日

作为一个刚入行的后端新人,这篇文章帮我理清了好多概念,谢谢博主持续输出高质量内容!

云端漫步
云端漫步cloud-notes.cloud2026年9月11日

太及时了,正好项目里要上 Redis 集群,照着来一遍省了好多事,谢谢!

相關文章

GraphQL vs REST:從 7 個維度深度對比

GraphQL vs REST:從 7 個維度深度對比

終結選型焦慮:從網絡傳輸效率、前端開發體驗、後端演進成本、緩存策略、類型安全、安全風險、生態工具鏈 7 大維度系統對比 GraphQL 與 REST,給出清晰決策樹與混合架構模式。

1 分鐘閱讀
JWT 認證安全最佳實踐 + OAuth2 vs SAML

JWT 認證安全最佳實踐 + OAuth2 vs SAML

系統性歸納 JWT 常見安全漏洞防護:算法混淆攻擊(none/RS256→HS256)、密鑰爆破、KID注入、重放攻擊防禦,再從多個維度對比 OAuth2(授權碼+PKCE)、OIDC、SAML 2.0 三種主流認證協議選型。

1 分鐘閱讀
Prompt Engineering 入門:Few-Shot + CoT + 結構化輸出

Prompt Engineering 入門:Few-Shot + CoT + 結構化輸出

系統入門 Prompt Engineering 三大核心技術:Few-Shot 示例學習注入專業領域風格、Chain-of-Thought (CoT) 思維鏈引導複雜推理、JSON Schema 強制結構化輸出對接下游系統。附真實 prompt 模板和避坑指南。

1 分鐘閱讀
Redis 緩存設計模式

Redis 緩存設計模式

系統講解三種核心緩存模式:Cache-Aside(旁路緩存)一致性保障、Write-Through(寫穿透)原子性保證、Write-Behind(寫回)高吞吐異步寫入,以及緩存擊穿/穿透/雪崩解決方案。

1 分鐘閱讀
PostgreSQL 查詢優化技巧

PostgreSQL 查詢優化技巧

深入掌握 PostgreSQL 查詢優化的核心方法:從 EXPLAIN ANALYZE 執行計劃解讀,到索引類型選擇(B-tree/GIN/BRIN)、統計資訊更新、連接策略調整、分區表設計,全方位提升數據庫查詢性能。

1 分鐘閱讀
Alembic 深度指南:數據庫遷移 + 自動生成 + 分支合併

Alembic 深度指南:數據庫遷移 + 自動生成 + 分支合併

SQLAlchemy 官方遷移工具 Alembic 完全指南:從 env.py 配置、autogenerate 自動檢測模型變更(字段、索引、約束)、批處理腳本、downgrade 回滾策略,到多人併發開發時 migration 分支衝突、head 分叉合併的標準操作流程,附生產部署檢查表。

1 分鐘閱讀