July 05 18:08~19:23;20:07~21:06
▲圖1:喵星人需要做版本控管的嗎?
前言
Event Sourcing因為把事件當作單一真實資料來源(single source of truth),因此事件本身就相當於是一種API。事件改版就好像API改版一樣,對於Event Sourcing系統來講是一個很重要的議題,如果不同版本的事件不相容,之後在replay事件的時候就可能會出問題,導致系統錯誤甚至是無法使用。這一集介紹事件版本控制(Event Versioning)的基本觀念與做法。
***
Schema on Write and Schema on Read
傳統關聯式資料庫當資料寫入資料表格時,必須知道這個table schema才可以成功寫入資料,這種方式稱為schema-on-write。需要事先定義資料格式,然後依據這個格式寫入資料庫。好處是在寫入時可以驗證資料格式的正確性,缺點則是因為資料格式在寫入時就已經固定,所以比較沒有彈性。
NoSQL資料庫(包含EventStoreDB)以及Apache Pulsar event broker則是採用另一種稱為schema-on-read方式,寫入時不檢查資料格式(資料可能以byte array的方式存在資料中),因此比較有彈性;至於資料的正確性則是交由讀取者來負責。
在Event Sourcing系統中,領域事件的格式都不一樣,因此幾乎不可能採用schema-on-write的方式來儲存領域事件,所以自然地採用schema-on-read。但問題來了,如果想要:
- 在schema-on-read的情況底下,當資料寫入時也能夠驗證資料格式是否正確,那怎麼辦?
- 在schema-on-read的情況底下,當資料讀取時也能夠驗證資料格式是否正確,那怎麼辦?
在schema-on-read情況之下想要驗證事件資料的正確性,基本上有兩種做法,Strong Schema與Weak Schema,前者是Apache Kafka與Apache Pulsar這類event broker所採用的方式,後者是Gregory Young在《Versioning in an Event Sourced System》所建議的方式。
***
Strong Schema
如圖2所示,在schema-on-read情況之下想要驗證寫入事件資料的正確性,有一種最簡單的做法就是資料寫入的時候在payload(event data)之前儲存事件的schema definition,如此一來寫入事件的程式(Serializer)就可以自動驗證事件的格式是否正確。此外,讀取事件的程式(De-serializer)也可以依據這個schema來讀取資料。
▲圖2:每一個事件夾帶一個schema definition,寫入時用來驗證事件格式的正確性,讀取時做為解析event data的依據。
圖2的作法雖然簡單,但是每一個事件都夾帶一筆schema definition,這些重複的schema definition在網路傳輸時會占用頻寬,儲存時會占用空間。所以衍生了「何不將這些schema definition集中起來放在一個地方儲存,serializer與de-serializer有需時再來這裡讀取就好?」的想法,這就誕生了Schema Registry這種軟體。
***
圖3是Apache Pulsar Schema Registry的運作示意圖,Schema Registry針對每一個Topic,保存曾經出現在裡面的事件版本及其schema。當Writer要寫入事件時,透過Pulsar client程式把事件的schema(SchemaInfo物件)向Schema Registry詢問該schema是否註冊過,如果沒有就註冊,如果有就得到一個代表該schema的serializer,然後透過它寫入事件。讀取事件時,同樣提供Schema Registry一個SchemaInfo物件,然後回傳一個de-serializer並透過它讀取事件。
基本上Schema Registry就是一個集中式的查表服務,採用Schema Registry之後,寫入資料庫中的事件就不需要像圖2一樣,每一筆事件都夾帶一個schema definition。只要把事件版本與schema definition向Schema Registry註冊,然後寫入事件時Schema Registry回傳的serializer會知道事件的版本編號,最後寫入資料庫只需要加上這個版本編號就可以。如圖3中事件前方綠色方框裡面的數字就代表該事件的版本編號
▲圖3:Apache Pulsar Schema Registry運作示意圖,參考《Apache Pulsar in Action》
Schema Registry詳細運作還有很多細節,有興趣的鄉民可以參考Apache Pulsar或是Apache Kafka,這兩者都使用Schema Registry,只不過預設使用的Schema Registry軟體不同。在《Apache Pulsar in Action》書中針對Pulsar的Schema Registry運作原理有很詳盡的說明,有興趣的鄉民可以參考。
***
Weak Schema
Strong Schema在schema-on-read的情況下提供了寫入與讀取自動驗證事件格式的機制,感覺起來很不錯,可以減少人為錯誤。但Gregory Young在《Versioning in an Event Sourced System》書中卻建議採用另一種稱為Weak Schema的方式,其特性如圖4所示。
Weak Schema不使用Strong Schema的deserialization方式讀取資料,而是採用mapping的方式。什麼叫做mapping的方式?有點類似你把領域物件轉成DTO(data transfer object)往前端傳遞的時候,手動撰寫Mapper程式把Domain Object 轉成DTO的方式。只不過Weak Schema說的mapping,是把事件從資料庫映射(map)成領域事件物件的過程。
Mapping原則很簡單,只有三條,如圖4中所說明。
▲圖4:Weak Schema特性說明
採用Weak Schema的方式,事件本身就不需要加上版本號碼,因為只要依循Weak Schema的規則,不管資料庫中儲存多少事件的版本,讀取端一定能夠用mapping的方式把事件讀出來。此外。這種方式也不需要一個集中式的Schema Registry。
但是Weak Schema有一個很重要的限制,就是事件的欄位名稱不能改名(不能rename),還有就是如果有些必填欄位在某些事件版本被取消或是忘了填,則mapper程式要做特別的檢查,如圖4右下角程式所示。
關於事件版本控制的問題還有很多細節與相對應的方法,Teddy建議有興趣的鄉民可以仔細閱讀Gregory Young的《Versioning in an Event Sourced System》(這是一本電子書)。
***
哪一種比較好?
Strong Schema和Weak Schema哪種比較好?說實話Teddy也不知道。ezKanbna目前是採用偏向Weak Schema的方式,而且據Teddy的了解,EventStoreDB好像也沒有支援Schema Registry。EventStoreDB最早是由Gregory Young所開發,既然「祖師爺」建議採用Weak Schema,EventStoreDB不支援Schema Registry也是很合理的。
但如果今天的應用場景改成跨微服務訊息溝通,而不是單純的Event Sourcing,通常會使用Kafka或是Pulsar這種event broker軟體。在這種情況下使用Schema Registry(Strong Schema)就非常普遍。
但是,依據Gregory Young的說法,即使是在跨微服務訊息溝通的情況下,還是可以採用Weak Schema。就Teddy所知,Apache Pulsar的Topic在寫入與讀取的時候是可以設定不要檢查schema,這種方式就可以支援Weak Schema採用mapping的方式讀取事件。
兩種方式各有利弊,要用哪種方式就留給鄉民們自行決定。
***
下集預告
下一集談和事件版本管理相關的議題:行為版本管理。
***
友藏內心獨白:搞了好一陣子才把這個議題弄清楚。
沒有留言:
張貼留言