基於DDD、CQRS、微服務和事件溯源構建系統

對於構建系統來說,模塊化是至關重要的,但實現模塊化需要應對一些反模塊化的做法。兩種典型的反模塊化做法就是在不考慮業務領域的情況下走捷徑和拆分微服務,這會增加技術債務。最近,在由AxonIQ舉辦的阿姆斯特丹事件驅動微服務大會上,Allard Buijze分享了他的一些想法,以及基於DDDCQRS、微服務和事件溯源構建系統的親身經驗。

Buijze是AxonIQ的CTO。他指出,另一個反模塊化做法就是名詞驅動設計(noun-driven design)。名詞驅動設計用於發現應用程序中的對象,通常是通過查找應用程序中出現的名詞,而非採用那些用於創建服務的對象。如果開發人員需要一次性重新部署多個服務,或是某個服務需要依賴於其他服務,就會從單體應用轉爲分佈式單體應用,但潛在的問題並沒有得到解決,這並不是微服務架構。

Buijze認爲,轉向微服務需要一個過程,無法一步將業務模型轉爲微服務。反之,我們應該從那些具有良好結構和模塊化的單體應用着手,根據需求逐步拆分出新的微服務。他強調,創建微服務的需求應該是非功能性需求。

Buijze指出,將組件抽取成微服務的重點在於實現位置透明性。組件不應該知道或者假定知道與之交互的其他組件的位置。這意味着當組件可被抽取爲微服務時就無需重寫現有組件與新服務之間的通信。

解決位置透明性問題的通常做法是使用事件。一個服務無需直接調用與之通信的其他服務,而是通過發佈事件並設置服務去監聽事件。Buijze指出,使用事件的一個重要特點是實現了依賴關係的轉置,即訂閱事件的組件或服務可以直接監聽發送的事件。

Buijze介紹了只使用事件會出現的一個常見錯誤,即事件也會被用於間接請求發生於其他服務中的操作,這會增加服務間的耦合度,可能導致雙向依賴,使得服務間依賴關係難以編排。如下例,圖中沒有負責業務流程的服務:

組件間會因爲不同的原因發起通信。除了使用事件之外,還存在另外兩種消息,一種是用於表示改變事物的“命令”(Command),另一是用於獲取信息的“查詢”(Query)。在上例中,訂單服務(Order)可使用“命令”去請求支付(Payment)和交貨(Shipping)服務,交貨服務可使用“查詢”去請求訂單服務細節。Buijze建議我們應該像使用事件那樣同等使用命令和查詢。

事件溯源是另一個與事件相關的概念。在使用事件溯源時,組件中存儲的並非實體的具體狀態,而是導致每個實體狀態發生變更的事件。Buijze認爲,事件源就是獲取所有的事實,並且只關心事實。但他也指出,事件溯源必須被正確實現,否則就無法確保事實的完整性。要驗證事件溯源是否被正確實現,可以試着拋開事件存儲以外的東西。如果應用程序的狀態可重現,說明事件溯源做對了。Buijze進一步指出,要在服務中使用事件溯源,服務必須使用自己發佈的事件來保持一致的狀態。

從業務和技術方面來看,事件溯源都有一些優勢,審計和單一數據源就是兩個很好的例子。 事件溯源同樣存在一些挑戰,例如增加了存儲規模、實現複雜度高。但Buijze認爲,這些問題現已不復存在,目前最大的挑戰在於事件思維(event thinking)。在Buijze看來,事件是一種更爲自然的理解應用程序行爲的思維方式。他建議,我們應該將注意力放在行爲上,而非狀態上。事件和命令描述了應用程序的功能,因此讓應用程序的行爲變得更容易理解。但他並不建議對所有應用程序使用事件溯源。與其他工具一樣,事件溯源僅在合適的場景才能發揮作用。

最後,Buijze強調,所有的通信都是某種形式的合約。基於事件架構的一個缺點是需要對很多未知的組件建立合約。因此,我們要對事件和合約的範圍加以約束,避免各個有界上下文之間存在耦合。在同一上下文中,所有服務使用同一種語言,並且能理解所有的事件。但是很多事件,尤其是事件溯源中的事件,只在特定的上下文中是有用的,不應該被髮布到上下文之外。對於一些公共事件,一種好的做法是將事件轉換爲公共API。

Martin Fowler在2015年發表的一篇博文中提到,他已經注意到乎所有成功的微服務均源於單體應用。但此後不久,Stefan Tilkov在其博客中提出了不同的看法,即如果目標是實現微服務架構,那麼從單體應用着手是完全錯誤的做法

歐洲2019 DDD大會的一個演講中,Eric Evans介紹了各種類型的“有界上下文”,其中一些類型尤其適用於基於事件的系統。

大會演講全程錄像,並將於近幾周內公開發布。

原文鏈接:

Sense and Nonsense in Event Thinking and Microservices

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章