2017-01-20 50 views
1

假設我有在一個非規範化視圖2個聚集體StaffShop,我有一個包含店鋪信息AA讀模型StaffModel(shopId,姓名,地址等) 。在DDD&CQRS,上同步多個骨料更新讀模型適當的設計

業務規則是創建一個Staff並在單個請求Shop,所以我有一個CreateStaffService創建一個工作人員和火災StaffCreatedEvent,然後CreateShopService偵聽StaffCreatedEvent,創建一個Shop,然後觸發ShopCreatedEvent

在讀模式方面,我有4個方面設計了同步服務:

  1. 訂閱StaffCreatedEvent,創造StaffModel記錄。然後訂閱ShopCreatedEvent,使用staffId更新StaffModel上的商店信息。

  2. ShopCreatedEvent包含員工信息,同步器服務訂閱事件並一次插入完整的讀取模型。但是員工信息與Shop總計無關,可以將它包含在事件中嗎?

  3. 模型StaffModelShopModel分開,更新模型以響應相應的聚集事件。

  4. CreateStaffService和在單個事務CreateShopService,火StaffAndShopCreatedEvent

我個人比較喜歡選擇2和4,與選項1是很難保證StaffCreatedEventShopCreatedEvent之前一直到達。

請分享你在這個問題上的想法和經驗。 感謝

更新時間:

避免了使用序列號順序事件消耗,可以說我生成使用數據庫序列的序列號,由1每次遞增,那麼假設我的用戶消耗事件1,因此最後處理的事件序列爲1.然後,生產者按順序發佈事件2,事件3和事件4,並且僅噹噹前事務成功時才發送事件。因此,如果事務2創建序列號2,但事務失敗並回滾,則事件2未發送,但事件3和事件4已成功發送。

在消費者方面,事件3和事件4都比上次處理的事件1更新,並且事件2永遠不會到來。因此,在這種情況下,檢查lastProcessed + 1 == currentVersion是錯誤的,除非事件序列號(版本)是嚴格順序的,這也很難保證。

+1

您的閱讀模型是否線性化,意味着它在單個線程上按順序運行,並且沒有競爭事件消耗?在這種情況下,即使事件可能出現故障,也很容易與#1一起使用。此外,您描述寫入方的方式(等待事件按特定順序發生)意味着這些事件肯定會按照正確的順序具有順序號,這些順序號應該用於對讀取模型的事件進行排序。 – plalx

+0

@Jay只是好奇 - 爲什麼你將'Staff'和'Shop'建模爲兩個獨立的聚合體? 「StaffAndShopCreated」海事組織有些奇怪。他們有相同的壽命嗎? – guillaume31

+0

Hi @ guillaume31,這個例子模擬了一個零售公司,它有員工和商店,員工屬於公司,可以分配到一個商店並關聯ShopStaff。我描述的商業案例應該是創建人員 - >創建商店 - >將工作人員分配給商店,對於混淆感到抱歉。 – Jay

回答

2

您已經定義了兩個不同的聚合,它們也可以在它們自己的有界上下文中。有了事件,每個事件都應該描述一個聚合。您將看到的大多數文檔和示例都顯示單一的聚合標識符。請記住,當你正在查看你的事件的創造性方面時,它仍然只是一個三角洲 - 在這種情況下,從「無」到「某種東西」。

很多框架都涉及您主要關心的問題,即處理亂序事件。這是一個真正的問題,特別是在分佈式系統中。爲了緩解這種情況,事件通常在寫入端被賦予一個連續的標識符,然後非正規化器將按順序饋入事件。所以,如果事件4到達,並且只處理了事件2,它將持有事件4,直到事件3被處理。

這裏是圍繞着同一個問題一個廣義的討論:Handling out of order events in CQRS read side

這聽起來像你正在推出自己的框架,它可以是艱鉅的。我正在做同樣的事情,但它更多的是擴展知識,而不是在現實世界中使用計劃。然而,我可以爲你設想的是考慮如何讓事件更加可預測地到達。如果您現在沒有考慮擴展,那麼您可以通過確保將事件推入FIFO隊列來緩解您的擔憂。然後,同步器服務可以輪詢隊列,而不是訂閱事件。在重新構建聚合時,將它與排序重新排序的事件相結合,並且您有一個很好的起點。這樣做,除非您有多個進程輪詢您的隊列,否則您不必擔心亂序事件。

爲確保您實際上是按順序生成事件,您所描述的內容聽起來像是一個很好的域服務用例。你正在協調兩個總計的運作。通過您的域名服務提升這些事件可以幫助您確保兩個聚合的操作都已完成。

更新時間:

我會擴大一點,以反映額外的問題。讓我們先退後一步。在寫作方面,你永遠不會堅持你的聚合。您堅持反映狀態更改的事件,其中包括創建。你的目標是你將發出一個命令,命令將被賦予聚合,並且聚合將從該命令創建一個事件。創建事件後,聚合將會將其應用於自身,然後使其可供命令處理程序從聚合中檢索。既然這個事件是總體需要知道的改變狀態的事情,那就是我們將不得不保存到寫入端的一切。當我們再次需要這個集合時,我們只需加載與給定集合標識符相關的所有事件,然後根據集合類重放它們。一旦所有事件都被重播,你已經水化了你的總量。

當您的命令處理程序將事件傳遞到事件存儲時,它只關心它是一個事件。在持久性方面,您將有可能在特定字段中序列化的事件以及包含事件周圍元數據的其他字段;如聚合標識符和聚合類型(當需要重播時,這使得查詢更容易)。另外,該事件將具有順序標識符。這是您的自動增量標識符的好地方。而且,這是非常重要的,你有連續性。

對於你正在做的最大部分,你將最終一次只插入一個事件。很可能,它將構成您的大部分業務。這種優雅就是幾乎沒有失敗的機會。由於事件只是一串三角洲,所以沒有參照完整性。您可能會遇到更復雜的場景,主要是通過使用傳奇或流程管理器,您可以在將事件發佈到事件存儲之前維護多個聚合的狀態。你如何做到這一點可以從簡單到複雜。儘管如此,您的主要困難在於確保聚合不會被其他發生在流程管理器/事件範圍之外的事件所突變。但是,這遠遠超出了你所要求的範圍。最後,我會提供一個很好的資源供您閱讀。

返回活動流。由於您只是將事件推送到您的寫作方,並且我們可以假設它們是按順序創建的,所以當您進行預測時,可能會出現亂序事件。最常見的情況是,這將在擴展部署中發生,在這種部署中,您有多個服務器從隊列中選擇命令並分發它們。在你的事件存儲將你的事件推送到你的寫入端以進行持久化之後,它將把它推送到事件總線,在那裏你的訂戶將對它們採取行動。這是訂單變得重要的地方,因爲這是您的閱讀方將被更新的地方。

以您的情況爲例,事件1正在創建員工,事件2正在創建商店。如果他們以相反的順序擊中比賽巴士,你需要知道這一點。您的活動巴士的目標是在某處提供您的活動,這通常是一個隊列。一個簡單的FIFO隊列在這一點上實際上會出現問題,因爲您處於一種模擬所接收訂單的情況。您的排隊機制應該有能力檢查所有排隊的項目,並且您需要跟蹤上次處理的事件。在處女系統上,在你的兩個請求中,無論監視隊列是否知道最後一次處理的事件是0.它看到2到達並且什麼都不做。然後1到達,它可以將事件1推送給所有訂戶,將跟蹤上次處理的事件的數值更新爲1,分派事件2並將內部跟蹤器更新爲2.基本上,您正在查看某種服務監視事件總線發佈到的隊列,然後爲每個事件向用戶顯式調度。

我已經提到了一個涵蓋很多這個的資源,那就是Microsoft's CQRS Journey,它們是Practices系列的一部分。即使您不在Microsoft堆棧中,它也有很多很棒的信息。最好的部分是它確實是一個「旅程」。您不僅可以獲取代碼示例,還可以瞭解爲什麼您應該這樣做,但您可以在準實際應用程序中看到項目的發展。整本書可以免費下載,如PDF格式。如果您願意,還可以獲得紙質書。 (我這樣做過,因爲技術書籍的觸覺感受和我發現自己在翻閱這些書籍的方式有些關係,這不利於電子書籍)。

+0

感謝您的詳細解答,我編輯了我的任務,並在序列號方法上添加了另一個問題 – Jay

+0

對不起,@Jay。我看到我的答案中可能引入了一些混淆。明天我會爲您提供最新的答案。 –

+0

謝謝約瑟夫,期待你的分享 – Jay