在近期舉行的PHPDublin見面會上,來自DynamicRes的架構師Barry
Sullivan被問到“什麼是事件溯源”,作為對這個問題的回答,他在部落格上寫下了這篇文章,詳細解釋了什麼是事件溯源以及事件溯源有哪些好處。以下内容翻譯自Barry的部落格,已獲得作者授權。
Web開發的現狀
在詳細解釋事件溯源之前,先讓我們來看看Web開發的現狀。
目前的Web開發是以資料庫作為驅動的,在設計Web應用的時候,我們會自然而然地将系統設計與資料庫存儲機制聯系在一起。如果使用的是MySQL,我們就會把資料結構設計成表,如果使用的是MongoDB,就會把資料結構設計成文檔。這樣會強制我們隻關注事物的目前狀态,我們會想“怎樣儲存這些資料,以便将來可以再把它們拿出來(或者修改它們)”?

這種方式存在三個問題。
1. 有悖于我們的思維
人類的思考和交流并不是以狀态為中心。如果我們在咖啡店相遇,我會問你“最近可好”?如果你隻是告訴我一堆狀态卻指望我從中猜出發生了什麼事情,這是非常不合情理的。
“我有一幢房子、一輛汽車、一個冰箱、三個社交媒體賬号、一隻貓咪,我的右腳有點痛,我不擅長聊天,我還有另一隻貓咪……”
看到沒有?這樣我會瘋掉的。你應該告訴我,從上次見面之後都發生了哪些事情,這樣我才能知道你現在的狀況是什麼樣子的。簡單地說,你應該告訴我一個故事,這個故事是由一連串事件組成的。
2. 單一的資料模型
在上圖中,讀寫操作使用了相同的模型。我們從寫資料的角度來設計表,然後基于這樣的結構查詢資料。這對于小型的應用來說是沒有問題的,但用在大型的應用裡就會有問題。随着系統的增長,查詢會變得越來越複雜,總有一天,一個查詢可能會包含10個連接配接操作,代碼有100行那麼多。系統很快就會變得脆弱無比,難以維護和變更。
3. 關鍵業務資訊的丢失
這是一個大問題。在以表作為驅動的系統裡,你隻儲存了系統的目前狀态,你根本就無法知道系統是如何達到目前狀态的。如果我問你“這個使用者修改了幾次郵件位址”,你有辦法回答嗎?或者我再問“有多少人把一件商品添加到購物車裡,然後又移除掉,直到一個月之後才買了那件商品”,你就更沒法回答了。你存儲資料的方式丢掉了很多有用的業務資訊!
事件溯源
事件溯源與上述的情況恰好相反,它并不關心目前狀态,而是關注持續不斷的變化事件。
舉個例子,假設我們有一個“購物車”,我們可以建立購物車,往裡面添加商品或移除商品,然後結賬。
購物車的生命周期可以包含如下一系列事件:
建立購物車往購物車裡添加商品再次往購物車裡添加商品從購物車裡移除商品結賬
這些就是一個購物車的生命周期,包含了一系列事件。這就是事件溯源,非常簡單吧?
幾乎所有的流程都可以被看成一系列事件。在與領域專家交談時,他們不會提及“表”和“連接配接”,他們會将流程描述成一系列事件以及可以應用在這些事件上的規則。
如何實施業務規則?
大部分的業務操作都有硬性限制。對于購物車來說,它的限制就是“一件商品必須先被放進購物車後才能被移除”。如果一件商品沒有被添加到購物車裡,又怎麼能夠移除它?這種事件順序是不可能發生的。在沒有狀态的情況下,你怎樣才能知道“購物車裡是否有這件商品”?
很簡單,你隻要檢查之前是否發生過“商品被添加到購物車裡”這個事件,這樣你就可以知道購物車裡是否存在這件商品,然後移除它。
這樣不會浪費時間嗎?
一點也不。一般來說,要執行限制,隻需要獲得事件的一個很小子集。通過簡單的資料庫查詢就可以獲得有用的曆史事件,在加載完這些事件後重放它們,把它們“投射”出來,以此建構你的資料集。這樣的操作其實是很快的,因為你使用的是本地的處理器,而不是執行一系列SQL查詢(跨域網絡的調用要比本地操作慢得多,至少會相差兩個數量等級)。
如何展示資料?
如果說每一個狀态都是通過重放事件來獲得的,那麼該如何抓取資料并把它們展示給使用者看?每次都需要抓取所有的資料然後再建構這些資料集嗎?
答案是你沒必要這樣做,這樣做其實是很荒唐的。
你可以在背景建構資料集,然後把中間結果儲存在資料庫裡。這樣,使用者就可以在很短的時間内查詢到這些資料。
有了事件溯源,你就不再局限于目前的表結構。需要做其他的查詢?隻要設計一個新的結構就可以了。你可以自由地實作各種讀取模型,在不需要它們的時候再把它們抛棄掉。
事件溯源的好處
1. 臨時的資料結構
因為所有的狀态都可以通過重放事件獲得,是以就沒有必要把目前“狀态”與應用程式綁在一起。如果需要以新的方式檢視資料,直接建立新的資料視圖即可。不再需要繁雜的資料遷移腳本,要做的隻是建立新視圖,抛棄舊視圖。我現在幾乎離不開事件溯源了。
2. 與領域專家的溝通變得更簡單
正如之前所述,領域專家通常将業務流程描述成一系列事件,而不是狀态。基于事件溯源的系統與領域專家的描述不謀而合,是以就沒有必要把他們的描述轉換成技術概念,這樣也避免了資訊丢失。與領域專家的溝通是以變得更加順暢,因為我們正在使用他們能夠了解的語言與他們溝通,這也讓軟體開發變得很不一樣。
3. 極具表現力的模型
在事件溯源模型裡,事件是一等對象,事件模型更加接近于實際的業務流程。這讓很多東西都變得清晰明了,你就不會陷入存儲技術的泥潭。
4. 生成報告更輕松
在事件溯源系統裡,生成複雜的報告是一件輕而易舉的事情。你擁有完整的曆史事件,它們按照時間排序,你可以盡情地使用這些曆史資料。
以之前的例子為例,假設你想知道有多少個使用者從他們的購物車裡移除了商品,卻在一周後購買了這些商品。按照一般的開發方式,通常需要幾周的時間才能開發出這個功能,而在釋出之後,需要等上一段時間,等計算完所有資料之後才能生成報告。而在事件溯源系統裡,你可以馬上得到報告。你還可以得到之前任何一個時間點的報告,仿佛擁有了一台時光機。
5. 服務內建唾手可得
在标準的Web開發流程裡,內建兩個系統通常會導緻他們之間的耦合,而事件溯源系統通過事件來解耦被內建的系統。當一個系統發生某系事件需要觸發另一個系統的某個流程時,隻需要寫一個事件監聽器即可。這種機制可以讓你在不修改已有領域代碼的情況下增加新的內建邏輯或特性。
例如,你想要在使用者注冊的時候發送一封歡迎郵件給他們,你隻需要建立一個事件監聽器,監聽“使用者注冊”事件,而不需要去修改注冊邏輯代碼。
6. 在一般的資料庫上也能健步如飛
你不需要使用多麼奇特的資料庫來存儲事件,一般的MySQL資料庫就足以。資料庫都針對追加操作進行過優化,是以存儲資料的速度是很快的。這也就是為什麼事件溯源在目前的技術條件下能夠良好運作,儲存事件都是追加操作。
7. 可以随意更換資料庫
基于事件溯源的資料結構都是臨時性的,是以你可以使用你喜歡的資料庫來存儲狀态,也就是說你完全可以選擇最好的工具來完成你的工作。如果你發現了更好的工具,可以在任何時候把舊工具替換掉。我們目前正在從MySQL遷移到OrientDB,可以說是輕而易舉。
事件溯源的不足
天下沒有完美的東西,事件溯源給我們帶來了諸多好處,但也存在一些不足。
1. 最終一緻性
事件溯源隻能保證最終一緻性。也就是說,在一個事件發生了之後,其他系統不會立即感覺到它,在它們收到事件之前會有一定的延遲(比如100毫秒),是以你所投射的資料可能不是最新的。這看起來似乎是一個大問題,但其實不是的。例如,基于ReactJS建構的Web應用會基于使用者的操作事件建構狀态,查詢端出現幾毫秒的延遲并不會有什麼問題。
老實講,這可以說是塞翁失馬,焉知非福。最終一緻性的系統具有容錯能力,可以解決服務中斷問題。如果使用微服務架構或無伺服器架構來建構分布式系統,就需要通過最終一緻性來保證穩定性。
2. 事件結構發生變化
事件結構會發生變化,如果事先沒有考慮到這個問題,後續處理起來會有些麻煩。如果事件結構發生變化,需要寫一個更新器來轉換新舊事件。轉換過程可以在将資料從資料存儲中讀取出來之後進行。這個沒有它看起來那麼難,隻需要準備好應對政策就可以了。
3. 開發人員需要改變思維
目前的Web開發主要還是以狀态作為驅動,是以開發者習慣了從表的角度看待問題,而不是事件。我發現要讓開發者改變思維需要一些時間,因為他們需要時間來改變習慣。最好的解決辦法是讓有經驗的事件溯源開發者與傳統的開發者結對。
總結
我很喜歡事件溯源,在建構大規模分布式系統時,它幫助我們解決了很多問題。我們可以使用領域專家能夠了解的語言與他們進行溝通,我們可以自由地改變和适配系統。盡管事件溯源有一定的學習曲線,但一旦你進入到這個領域,就不會想要回頭。
本文轉自d1net(轉載)