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

CQRS + Event Sourcing 架构模式解析

Choyeon
Choyeon
2026年9月14日 1 分钟阅读 384 阅读
702 字 2,423 字符 更新于 2026年11月8日
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 分钟阅读