WIX是如何從CRUD轉換到Event Sourcing?

banq發表於2021-10-01

Wix.com是一個基於雲端計算的Web開發平臺,它允許使用者透過使用他們的線上拖放工具來建立HTML5網站和移動網站。
WIX的產品願景是朝著反應式Reactive函式發展,這意味著在正確的上下文中實時對多個領域事件做出反應。問題在於,我們的單體應用被設計為經典的 CRUD 系統,在發生狀態變化時同步執行業務邏輯。
本文是關於我們如何將事件溯源和事件驅動架構引入我們的客戶支援平臺Wix Answers的系列文章中的第一篇,這種方式允許逐步遷移,現在可以提供新的業務價值,而不會將現有功能置於風險之中。傳統的系統設計CRUD 方法側重於狀態以及多個使用者在分散式環境中如何建立、更新和刪除狀態,而事件溯源方法側重於領域事件、它們何時發生以及它們如何表達業務意圖。在事件溯源方法中,狀態是事件的具體化,這只是領域事件的許多可能用法之一。
Wix Answers 是一種客戶支援解決方案,它將票務、幫助中心和呼叫中心等支援工具整合到一個具有高階內建自動化和分析功能的直觀平臺中。
 

我們是如何將領域事件引入我們的整體 CRUD 系統的?
我們需要問的第一件事是真相的來源是什麼。我們的單體系統透過 REST API 接受狀態改變命令,更新 MySQL 中的實體,然後將更新的實體返回給呼叫者。
這使得 MySQL 成為事實的來源。如果不對我們的單體應用以及它與客戶端通訊的方式進行重大更改,我們就無法改變它,而後者必須變得非同步。這將導致客戶端發生重大變化。


將資料庫二進位制日誌流式傳輸到 Kafka 是一種眾所周知的做法,旨在複製資料庫。對錶行的每次更改都儲存在 binlog 中,作為具有先前和當前行狀態的記錄,有效地將每個錶轉換為可以以一致方式具體化為實體狀態的流。我們使用Debezium源聯結器將 binlog 流式傳輸到 Kafka。
使用Kafka Streams stateless轉換,我們把一個CDC記錄到一個命令釋出到總命令主題。我們這樣做有幾個原因:
  • 在很多情況下,我們有多個表使用實體 id 作為二級索引。我們希望我們的聚合處理與相同 id 相關的所有命令。例如:您可能有一個帶有主鍵 orderId 的“Order”表和一個帶有 orderId 列的“OrderLine”表。透過將 Order CDC 記錄轉換為 UpdateOrderCdc 命令,將 OrderLine CDC 記錄轉換為 UpdateOrderLineCdc 命令,我們確保相同的聚合將處理這些命令並可以訪問最新的實體狀態。
  • 我們想為所有聚合命令定義一個模式。該模式可能以 CDC 更新命令開始,但可能演變為更細粒度的命令,這些命令也可以由相同的聚合處理,從而實現向真正的事件溯源架構逐漸演變。

隨著聚合處理命令,它逐漸更新 Kafka 中的實體狀態。我們可以重新建立源聯結器並再次流式傳輸相同的表 - 但是,我們的聚合會根據 CDC 資料與從 Kafka 檢索到的當前實體狀態之間的差異生成事件。在某種程度上,就我們與 Monolith 並存的流媒體平臺而言,Kafka 成為了真相的來源。
 

CDC 記錄代表已提交的更改 - 為什麼它們不是事件?
CDC feed的目的是以最終一致的方式複製資料庫,而不是生成域事件。獲取包含 before 和 after 元素的 CDC 記錄並透過在 before 和 after 之間執行 diff 操作將其轉換為域事件可能很誘人。但是,僅依賴 CDC 記錄存在一些主要缺點。
當我們執行無狀態轉換時,我們無法正確響應來自不同表的 CDC 記錄,因為不同表之間沒有順序保證。我們可能會在獲取訂單記錄之前處理訂單行記錄。一個好的領域事件將提供一些 Order 上下文作為 OrderLine 事件的一部分。有狀態轉換允許我們使用聚合狀態作為 OrderLine 的儲存,並且只在 Order 資料到達時釋出 OrderLine 事件。這是作為實體事件源的聚合責任的一部分。請記住,我們無法一次性實現純架構,而只能實現並做邊改模式。
 

引入快照
binlog 永遠不會包含所有表的完整更改歷史記錄;因此,我們為新表配置的每個新 CDC 聯結器都將從快照階段開始。聯結器將在 binlog 中標記當前位置,然後執行全表掃描並將所有行的當前狀態流式傳輸為帶有快照標誌的特殊 CDC 記錄。這本質上意味著在每個快照中我們都會丟失領域事件資訊。如果訂單狀態隨時間變化多次,快照將只提供最新狀態。這是因為 binlog 的目標是複製狀態,而不是成為事件溯源的支柱。這是聚合狀態儲存和聚合命令主題變得至關重要的地方。我們希望以每個表只執行一次快照的方式設計我們的解決方案。
事件溯源的強大功能之一是能夠透過回放歷史事件或命令來重建狀態或重新建立領域事件。在這裡執行另一個快照不是正確的解決方案,因為快照會丟失事件資訊。
如果我們想重新建立我們的領域事件,我們需要重置我們的命令主題的消費者。命令主題將 CDC 記錄打包成命令,並且已經以正確的順序(或我們的聚合知道如何處理的順序)儲存來自不同表的命令。
 

總結
我們只涉及了使 Monolith 具有反應性的旅程中的基本步驟。我們討論瞭如何使用 CDC 來構建命令主題以及為什麼 CDC 記錄不是命令。一旦我們有了一個命令主題,我們就可以使用有狀態轉換來建立事件,我們就可以開始享受事件溯源的好處:重放命令以重新建立事件,重新處理事件以實現狀態。
在接下來的文章中,我們將討論更高階的主題:

  • 如何使用 Kafka Streams 來表達聚合的事件溯源概念。
  • 我們如何支援一對多關係。
  • 如何使用事件透過重新分割槽事件來驅動反應式應用程式。
  • 我們如何重新處理命令歷史記錄以重新建立事件,而無需停機來響應事件的響應式服務。
  • 最後,我們如何在多 DC Kafka 中執行有狀態轉換(提示:映象主題真的不夠)。


 

相關文章