Chuyển đến nội dung chính

第 9 課:事件溯源與 CQRS — 何時需要,何時不需要?

事件溯源:儲存事件而不是狀態。 CQRS:單獨的讀取/寫入模型。結合事件溯源 + CQRS。權衡、複雜性和決策框架。什麼時候 CQRS 太複雜,什麼時候真的有必要?

🏗️ 建築 — 第 9 課 第 9 課:事件溯源與 CQRS — 何時 需要,什麼時候不需要?

微服務與微前端系統設計-從基礎到生產

第 3 部分:微服務中的資料架構

亞洲開發網

簡介

事件溯源和 CQRS 是兩種強大但經常被濫用的模式。本文將幫助您了解它的本質、真正的好處,最重要的是—何時不使用它。

CQRS 和事件來源 — 單獨的命令和查詢


1. 事件溯源

1.1 核心思想

不保存當前狀態,而是保存發生的事件序列:

Traditional (State-based):
┌─────────────────────────┐
│ orders table            │
│ id: 123                 │
│ status: SHIPPED   ← chỉ biết trạng thái hiện tại
│ total: 500.000         │
└─────────────────────────┘

Event Sourcing:
┌─────────────────────────────────────┐
│ Event Store (Order #123)            │
│ 1. OrderCreated    {items, total}   │
│ 2. PaymentReceived {amount}         │
│ 3. ItemRemoved     {itemId}   ← biết toàn bộ lịch sử
│ 4. OrderConfirmed  {}               │
│ 5. OrderShipped    {trackingId}     │
└─────────────────────────────────────┘

State = replay(events) → current state

1.2 好處

  • 完整的審計追蹤:確切地知道誰在何時做了什麼
  • 時間旅行:隨時重建狀態
  • 調試:重播事件以重現錯誤
  • 分析:分析事件模式

1.3 缺點

  • 複雜性:需要事件儲存、預測、快照
  • 最終一致性:讀取模型不是即時的
  • 模式演化:改變事件模式非常困難
  • 困難查詢:無法 SELECT * FROM 訂單 WHERE status = 'SHIPPED'

2.CQRS(指令查詢職責分離)

2.1 讀寫分離

Traditional:
┌──────────┐     ┌──────────┐
│  Client  │────►│ Service  │────► Database
│          │◄────│(CRUD all)│◄──── (1 model)
└──────────┘     └──────────┘

CQRS:
                 ┌──────────────┐     ┌────────────┐
           ────► │ Command Side │────►│ Write DB   │
┌──────────┐     │ (Create,     │     │ (optimized │
│  Client  │     │  Update)     │     │  for write)│
└──────────┘     └──────────────┘     └─────┬──────┘
           ────► ┌──────────────┐           │ Events
                 │ Query Side   │     ┌─────▼──────┐
           ◄──── │ (Read,       │◄────│ Read DB    │
                 │  Search)     │     │ (optimized │
                 └──────────────┘     │  for read) │
                                      └────────────┘

2.2 CQRS 什麼時候有價值?

  • 讀/寫比率非常不同(90% 讀取,10% 寫入)
  • 讀取模型需要不同的格式寫入模型(非規範化視圖)
  • 需要獨立擴充讀取/寫入(新增唯讀副本)
  • 複雜查詢需要優化的讀取模型(Elasticsearch、物化視圖)

3. 決策框架

3.1 何時使用事件溯源 + CQRS?

✅ 財務系統(需要審計追蹤) ✅ 訂單處理(狀態複雜,需要歷史記錄) ✅ 協作編輯(衝突解決) ✅ 監管合規(需要證明發生了什麼)

3.2 何時不使用?

❌ 簡單的 CRUD(部落格、使用者個人資料)→ 矯枉過正 ❌團隊小,無經驗→學習曲線太高 ❌ 無需審核蹤跡或時間旅行 ❌ 要求即時一致性

3.3 可以使用 CQRS 而無需事件溯源

CQRS without Event Sourcing (pragmatic approach):

Write Side: PostgreSQL (normalized)
  → On write: publish event to Kafka
  
Read Side: Elasticsearch (denormalized)
  → Consumer: listen events → update search index

Đơn giản hơn nhiều, vẫn có lợi ích tách read/write.

4. 申請電子商務

Product Service: CQRS (no Event Sourcing)
  Write: PostgreSQL (products table)
  Read:  Elasticsearch (search index, facets)
  Sync:  ProductUpdated event → ES consumer

Order Service: Event Sourcing + CQRS (trạng thái phức tạp)
  Event Store: PostgreSQL (events table)
  Read Model: PostgreSQL (materialized orders view)
  
Cart Service: Simple CRUD (Redis)
  Không cần CQRS — read/write model giống nhau

User Service: Simple CRUD (PostgreSQL)
  Không cần CQRS — straightforward CRUD

總結

圖案複雜性何時使用何時避免
簡單的增刪改查低大多數服務高流量閱讀
僅限 CQRS中獨立的讀取/寫入秤簡單域
事件溯源高審計追踪,時間旅行簡單的增刪改查
ES + CQRS非常高財務、訂購幾乎所有其他

黃金法則: 從簡單開始(CRUD)。僅當特定的痛點證明有必要時才添加 CQRS/ES。


下一篇文章: 第 10 課:什麼是微前端? — 效益、權衡與決策框架