1. 程式人生 > 其它 >程式開發中驅動設計之WIN事件驅動-自學分享

程式開發中驅動設計之WIN事件驅動-自學分享

程式開發中驅動設計之WIN事件驅動-自學分享

從物流詳情開始

大家對物流跟蹤都不陌生,它詳細記錄了在什麼時間發生了什麼,並且資料作為重要憑證是不可變的。我理解其背後的價值有這麼幾個方面:業務方可以管控每個子過程、知道目前所處的環節;另一方面,當需要追溯時候僅僅通過每一步的記錄就可以回放整個歷史過程。

我在之前的文章中提出過“軟體專案也是人類社會生產關係的範疇,只不過我們所創造的勞動成果看不見摸不著而已”。所以我們可以借鑑物流跟蹤的思路來開發軟體專案,把複雜過程拆解為一個個步驟、子過程、狀態,這和我們事件劃分是一致的,這就是事件驅動的典型案例。

領域事件

領域事件(Domain Events)是領域驅動設計(Domain Driven Design,DDD)中的一個

概念,用於捕獲我們所建模的領域中所發生過的事情。

領域事件本身也作為通用語言(Ubiquitous Language)的一部分成為包括領域專家在內的所有專案成員的交流用語。

比如在前述的跨境物流例子中,貨品達到保稅倉以後需要分派工作人員進行分揀分包,那麼“貨品已到達保稅倉”便是一個領域事件。

首先,從業務邏輯來說該事件關係到整個流程的成功或者失敗;同時又將觸發後續子流程;而對於業務方來說,該事件也是一個標誌性的里程碑,代表自己的貨品就快配送到自己手中。

所以通常來說,一個領域事件具有以下幾個特徵:較高的業務價值,有助於形成完整的業務閉環,將導致進一步的業務操作。這裡還要強調一點,領域事件具有明確的邊界。

比如:如果你建模的是餐廳的結賬系統,那麼此時的“客戶已到達”便不是你關心的重點,因為你不可能在客戶到達時就立即向對方要錢,而“客戶已下單”才是對結賬系統有用的事件。

建模領域事件

建模領域事件時,我們應該根據限界上下文中的通用語言來命名事件及屬性。如果事件由聚合上的命令操作產生,那麼我們通常根據該操作方法的名字來命名領域事件。

對於上面的例子“貨品已到達保稅倉”,我們將釋出與之對應的領域事件公司的。

GoodsArrivedBondedWarehouseEvent(當然在明確的界限上下文中也可以去掉聚合的名字,直接建模為ArrivedBondedWarehouseEvent,這都是命名方面的習慣)。

事件的名字表明瞭聚合上的命令方法在執行成功之後所發生的事情,換句話說待定項以及不確定的狀態是不能作為領域事件的。

一個行之有效的方法是畫出當前業務的狀態流轉圖,包含前置操作以及引起的狀態變更,這裡表達的是已經變更完成的狀態所以我們不用過去時態表示,比如刪除或者取消,即代表已經刪除或者已經取消。

然後對於其中的節點進行事件建模。如下圖是檔案雲端儲存的業務,我們分別對預上傳、上傳完成確認、刪除等環節建模“過去時”事件,PreUploadedEvent、ConfirmUploadedEvent、RemovedEvent。

領域事件程式碼解讀

package domain.event;
 
import java.util.Date;
import java.util.UUID;
 
public class DomainEvent {
 
    /**
     * 領域事件還包含了唯一ID,
     * 但是該ID並不是實體(Entity)層面的ID概念,
     * 而是主要用於事件追溯和日誌。
     * 如果是資料庫儲存,該欄位通常為唯一索引。
     */
    private final String id;
 
    /**
     * 建立時間用於追溯,另一方面不管使用了
     * 哪種事件儲存都有可能遇到事件延遲,
     * 我們通過建立時間能夠確保其發生順序。
     */
    private final Date occurredOn;
 
    public DomainEvent() {
        this.id = String.valueOf(UUID.randomUUID());
        this.occurredOn = new Date();
    }
}

在建立領域事件時,需要注意2點:

  • 領域事件本身應該是不變的(Immutable);
  • 領域事件應該攜帶與事件發生時相關的上下文資料資訊,但是並不是整個聚合根的狀態資料。例如,在建立訂單時可以攜帶訂單的基本資訊,而對於使用者更新訂單收貨地址事件AddressUpdatedEvent事件,只需要包含訂單、使用者以及新的地址等資訊即可。
    微服務內的領域事件可以通過事件匯流排或利用應用服務實現不同聚合之間的業務協同。即微服務內發生領域事件時,由於大部分事件的整合發生在同一個執行緒內,不一定需要引入訊息中介軟體。但一個事件如果同時更新多個聚合資料,按照 DDD“一個事務只更新一個聚合根”的原則,可以考慮引入訊息中介軟體,通過非同步化的方式,對微服務內不同的聚合根採用不同的事務。
  • Saga實現

  • 通過上面的例子我們對Saga有了初步的體感,現在來深入探討下如何實現。當通過系統命令啟動Saga時,協調邏輯必須選擇並通知第一個Saga參與方執行本地事務。一旦該事務完成,Saga協調選擇並呼叫下一個Saga參與方。

    這個過程一直持續到Saga執行完所有步驟。如果任何本地事務失敗,則 Saga必須以相反的順序執行補償事務。以下幾種不同的方法可用來構建Saga的協調邏輯。

  • 也許你現在已經磨刀霍霍,準備在專案中實踐一下這些技巧。不過我們要明白“每一個硬幣都有兩面性”,我們不僅看到高擴充套件、解耦的、易編排的優點以外,仍然要明白其所帶來的問題。利弊分析以後再去決定如何實現才是正確的應對之道。

    • 這類程式設計模式有一定的學習曲線;
    • 基於訊息傳遞的應用程式的複雜性;
    • 處理事件的演化有一定難度;
    • 刪除資料存在一定難度;
    • 查詢事件儲存庫非常有挑戰性。這裡為什麼會出現Read/Write Service,是為了封裝呼叫,service內部是基於聚合傳送事件。因為我發現在實際專案中,很多人都會第一時間問我要XXXService而不是XXX模型,所以在DDD沒有完全普及的專案中建議大家採取這種居中策略。這也符合咱們的解耦,對方依賴我的抽象能力,然而我內部是基於DDD還是傳統的流程程式碼對其是無關透明的。
快速開發量化機器人軟體,量化交易機器人APP系統搭建,諮詢熱線:17154957219