事件驅動系統中不同型別的事件 - frankdejonge

banq發表於2022-02-20

事件驅動系統有各種形狀和大小。明顯的共同點是;他們都使用事件來傳達資訊。這些事件有多種形式和大小,確定事件中的內容會對系統設計產生巨大影響。
在這篇文章中,我想討論三種不同型別的事件。我希望澄清這些型別將使您能夠更好地討論事件驅動的架構和整合。
三種事件原型
當我與其他開發人員討論事件時,我區分了三種型別的事件。每種型別都有其獨特的特徵、優勢和劣勢。這些型別的事件都不一定比另一種更好,但在特定情況下,特定型別可能更適合。
這些型別的事件是:
  1. 領域事件
  2. 觸發或訊號事件
  3. RESTful 或“胖”事件

讓我們逐一介紹它們,看看它們是什麼,以及它們何時有用。
 

領域事件
對於任何對領域驅動設計感興趣的人來說,這將是最熟悉的事件型別。領域事件是歷史的記錄,捕捉重要時刻的意圖和任何相關上下文。領域事件關注“領域”,這意味著它們關注與業務相關的事物。因為他們記錄歷史,所以他們用過去時表達。
下圖是事件之間的因果與相關性關係:

事件驅動系統中不同型別的事件 - frankdejonge
領域事件以明確表達意圖的方式命名。建議使用人類語言來命名這些事件,儘量避免使用“嗶嗶”語言。而不是命名事件OrderStateChanged或OrderEvent,使用類似的東西OrderWasShipped。
與其他事件型別不同,域事件非常適合捕獲意圖。因為領域事件只捕捉重要時刻的相關上下文,所以它們也非常適合捕捉變化。這讓活動消費者對正在發生的事情有了深刻的瞭解。事件源系統更進一步,使領域事件成為軟體模型的基石。
領域事件特別適合用於建立讀取模型。在讀取用例要求與有助於做出決策的資料模型非常不同的情況下。在建立讀取模型和分析資料模型中,以更改和意圖為中心的表示非常適合聚合。

class OrderWasShipped
{
    public function __construct(
        private OrderId $orderId,
        private MomentOfSchipment $shippedAt,
        private ShipmentAddresss $shipmentAddress,
    );
   
    public function orderId(): OrderId
    {
        return $this->orderId;
    }
   
    public function shippedAt(): MomentOfSchipment
    {
        return $this->skippedAt;
    }
   
    public function shipmentAddress(): ShipmentAddresss
    {
        return $this->shipmentAddress;
    }
}

  • 優點

領域事件非常適合捕捉意圖和“變化”。它們允許您構建強大的讀取模型,其擴充套件性比原始資料模型上的複雜查詢要好得多。領域意圖對於建立分析模型非常強大,它可以提供對業務中正在發生的事情的深刻見解。
  • 缺點

域事件暴露了域內部發生的事情。如果消費者依賴此資訊,他們就會與之耦合。如果使用領域事件來建立決策模型,那麼這些事件的耦合會對開發速度造成壓力。耦合增加了改變的成本,所以知道你暴露了什麼以及暴露給誰總是好的。作為預設做法,將每個域事件視為“私有”,僅用於內部使用。只有透過故意曝光,消費者才能訪問事件,類似於使用 API 而非直接訪問資料庫的方式。
  

觸發或訊號事件
觸發或訊號事件是最小的事件。此事件通常僅包含一個引用聚合或實體的 ID,也可能包含時間戳。正如名稱觸發器所暗示的,這些事件用於觸發消費方的反應。觸發器最常用於通知其他業務流程發生變化。在您儲存敏感資料的情況下(看看您,GDPR),使用觸發器可以幫助防止將事件基礎設施暴露於具有挑戰性的要求。

class OrderWasShipped
{
    public function __construct(
        private OrderId $orderId
    );
   
    public function orderId(): OrderId
    {
        return $this->orderId;
    }
}

  • 優點

當域事件可能包含敏感資料時,觸發器很有用。在這些情況下,生產者發出一個訊號並期望消費者使用安全的 API 來獲取相應的 ID。觸發器不容易引起資訊級耦合,僅僅是因為它們不包含任何內容。
  • 缺點

由於觸發器不包含任何資訊,因此消費者總是依賴於 API。當許多消費者消費許多事件時,這可能會給您的系統帶來一些意想不到的負載。資訊的缺失也限制了彙總資料的能力。
由於事件是非同步處理的,因此從 API 檢索的資料可能處於消費者期望的不同狀態。消費者必須始終檢查從 API 檢索的資源是否是他們所期望的,並準備好處理資源可能處於的任何可能狀態。例如,如果訂單已發貨,但商家立即取消發貨,則消費者可能會檢索到與事件名稱所暗示的狀態不匹配的貨運資源。當發生事件處理延遲時,這可能會產生意想不到的結果。
 

RESTful 或“胖”事件
最後一個原型是“胖”事件。我個人更喜歡術語 RESTful 事件,因為它更好地描述了有效負載中的內容。這種型別的事件包含您將從 RESTful API 檢索到的完整資源表示。這是一個很好的整合活動,對外部消費者最有用。
與觸發器相比,RESTful 事件阻止消費者往返 API。如果將其與域事件進行比較,它可以防止消費者不得不組合多個事件來獲得完整的畫面。

class OrderWasShipped
{
    public function __construct(
        private OrderId $orderId,
        private OrderLines $orderLines,
        private DiscountCodes $discountCodes,
        private OrderAmount $orderAmount,
        private MomentOfSchipment $shippedAt,
        private ShipmentAddresss $shipmentAddress,
    );
   
    public function orderId(): OrderId
    {
        return $this->orderId;
    }
   
    public function orderLines(): OrderLines
    {
        return $this->orderLines;
    }
   
    public function discountCodes(): DiscountCodes
    {
        return $this->discountCodes;
    }
   
    public function orderAmount(): OrderAmount
    {
        return $this->orderAmount;
    }
   
    public function shippedAt(): MomentOfSchipment
    {
        return $this->skippedAt;
    }
   
    public function shipmentAddress(): ShipmentAddresss
    {
        return $this->shipmentAddress;
    }
}

  • 優點

RESTful 事件非常適合將狀態推送給消費者。在一個事件中,消費者對資源瞭如指掌。對於每個資源,只需將最後一個事件備份到最新狀態,這對於災難恢復非常有用。如果另一個服務依賴於您的服務的狀態,則使用 RESTful 事件是一種將狀態推送到那裡的好方法。在最終一致性可以接受的情況下,這樣做將消除對服務的直接依賴。
  • 缺點

根據我的經驗,RESTful 事件僅作為“外部”消費者的整合工具有用。它們對內部建模沒有用處。它們很大,更匿名,傳達的意圖較少,因此不太適合內部建模。RESTful 事件通常需要您構建一個防損壞層來將其他型別的事件轉換為 RESTful 事件,這是“額外”的工作。
 

就事件進行有意義的討論
在技​​術討論中,很容易跳到解決方案上。只需新增該欄位,只需將此內部事件公開給外部消費者,即可解決問題。我希望透過確定幾種型別的事件,您可以將這些資訊帶入您正在進行的討論中。嘗試確定計劃中的事件型別,它們具有哪些特徵,以及這些特徵如何影響您應用它們的情況。暴露領域事件?注意資訊級耦合。因為消費者需要它們而將越來越多的資訊新增到事件中?也許切換到 RESTful 事件。最後,請記住,如果在正確的環境中應用不同的溝通方式,效果最好。您應該意識到這一點並做出正確的選擇。
 

相關文章