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

CQRS + Event Sourcing Architecture Patterns

Choyeon
Choyeon
Sep 14, 2026 1 min read 383 Views
545 words 3,229 chars Updated Nov 8, 2026
CQRS + Event Sourcing Architecture Patterns
Table of contents

As systems scale, traditional CRUD single-model architectures hit bottlenecks in complex queries, audit trails, and concurrency. CQRS + ES are advanced patterns that break these limits, but added complexity demands careful adoption decisions.

Event Sourcing: Events as Source of Truth

Never persist current state; append only events that caused state changes (OrderCreated → ItemAdded → OrderPlaced). Replaying reconstructs state at any point; complete audit trail for free. Pair with snapshots to avoid replaying from zero on each load.

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: Separate Write and Read Models

Write path goes through domain model and produces events. Event subscribers asynchronously build denormalized projection tables tuned for queries. Write side enforces consistency while read side optimized for retrieval, but introduces an eventual consistency window.

Dimension Classic CRUD CQRS Only CQRS + Event Sourcing
Storage Single table both Write DB + Read DB Event stream + N projections
Query flexibility Medium Very High Very High + time travel
Audit history Extra columns Hard Free and immutable
Debug difficulty Low Medium-High Very High
Fit for 90% of business Read-heavy complex queries Finance/ledger/provenance

Best Practices

Ask three questions before adopting: Do you require an immutable audit trail? Are queries genuinely painful with CRUD? Does the team have DDD experience? Only proceed if all three are Yes. Otherwise classic CRUD plus a simple audit table nearly always delivers better ROI.

X微博Telegram

About the author

Poll

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

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

Comments (6)

0 / 3000
东京留学僧
东京留学僧Sep 12, 2026

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

摸鱼达人小王
摸鱼达人小王Sep 12, 2026

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

全栈打工人
全栈打工人Sep 11, 2026

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

机器学习萌新
机器学习萌新Sep 11, 2026

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

技术笔记控
技术笔记控tech-notes.funSep 11, 2026

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

云端漫步
云端漫步cloud-notes.cloudSep 11, 2026

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

Related posts

GraphQL vs REST: 7-Dimension Deep Comparison

GraphQL vs REST: 7-Dimension Deep Comparison

End the API style debate: compared across 7 dimensions - network efficiency, DX, evolution cost, caching, type-safety, security risks, ecosystem - with clear decision tree and hybrid architecture patterns.

1 min read
JWT Security Best Practices + OAuth2 vs SAML

JWT Security Best Practices + OAuth2 vs SAML

Comprehensive JWT vulnerability playbook: alg confusion attacks, key brute force, KID injection, replay defenses. Compare OAuth2(+PKCE), OIDC, SAML 2.0 across dimensions for protocol selection.

1 min read
Prompt Engineering 101: Few-Shot + CoT + Structured Output

Prompt Engineering 101: Few-Shot + CoT + Structured Output

Systematic prompt engineering primer covering three core techniques: Few-Shot examples for style injection, Chain-of-Thought for complex reasoning, JSON Schema enforced structured outputs for downstream systems. Includes real templates and anti-patterns.

1 min read
Redis Cache Design Patterns

Redis Cache Design Patterns

Three core caching patterns: Cache-Aside consistency, Write-Through atomic updates, Write-Behind async high-throughput writes, plus solutions for stampede, penetration, and avalanche.

1 min read
PostgreSQL Query Optimization Techniques

PostgreSQL Query Optimization Techniques

Master PostgreSQL optimization: EXPLAIN ANALYZE plans, choosing index types (B-tree/GIN/BRIN), updating statistics, tuning joins, and partitioned table design for maximum performance.

1 min read
Alembic Deep Dive: Migrations, Autogenerate, and Branch Merging

Alembic Deep Dive: Migrations, Autogenerate, and Branch Merging

Comprehensive Alembic (official SQLAlchemy migration tool) guide: env.py setup, autogenerate detecting model changes (fields, indexes, constraints), batch scripts, downgrade rollback strategies, plus standard SOP for multi-developer migration branch conflicts and divergent head merging, with production deploy checklist.

1 min read