1. 引言
Ordering microservice(訂單微服務)就是處理訂單的了,它與前面講到的幾個微服務相比要複雜的多。主要涉及以下業務邏輯:
- 訂單的建立、取消、支付、發貨
- 庫存的扣減
2. 架構樣式
如上圖所示,該服務基於CQRS 和DDD來實現。
從專案結構來看,主要包括7個專案:
- Ordering.API:應用層
- Ordering.Domain:領域層
- Ordering.Infrastructure:基礎設施層
- Ordering.BackgroundTasks:後臺任務
- Ordering.SignalrHub:基於Signalr的訊息推送和實時通訊
- Ordering.FunctionalTests:功能測試專案
- Ordering.UnitTests:單元測試專案
從以上的專案定義來看,該微服務的設計並符合DDD經典的四層架構。
核心技術選型:
- ASP.NET Core Web API
- Entity Framework Core
- SQL Server
- Swashbuckle(可選)
- Autofac
- Eventbus
- MediatR
- SignalR
- Dapper
- Polly
- FluentValidator
3. 簡明DDD
領域驅動設計是一種方法論,用於解決軟體複雜度問題。它強調以領域為核心驅動設計。主要包括戰略和戰術設計兩大部分,其中戰略設計指導我們在宏觀層面對問題域進行識別和劃分,從而將大問題劃分為多個小問題,分而治之。而戰術設計從微觀層面指導我們如何對領域進行建模。其中戰術設計了引入了很多核心要素,指導我們建模:
- 值物件(Value Object)
- 物體(Entity)
- 領域服務(Domain Service)
- 領域事件(Domain Event)
- 資源庫(Repository)
- 工廠(Factory)
- 聚合(Aggregate)
- 應用服務(Application Service)
其中物體、值物件和領域服務用於表示領域模型,來實現領域邏輯。
聚合用於封裝一到多個物體和值物件,確保業務完整性。
領域事件來豐富領域物件之間的互動。
工廠、資源庫用於管理領域物件的生命週期。
應用服務是用來表達用例和使用者故事。
有了以上的戰術設計要素還不夠,如果它們糅合在一起,還是會很混亂,因此DDD再透過分層架構來確保關註點分離,即將領域模型相關(物體、值物件、聚合、領域服務、領域事件)放到領域層,將資源庫、工廠放到基礎設施層,將應用服務放到應用層。以下就是DDD經典的四層架構:
以上相關圖片來源於:張逸 · 領域驅動戰略設計實踐
4. Ordering.Domain:領域層
如果對訂單微服務應用DDD,那麼要摒棄傳統的面向資料庫建模的思想,轉向領域建模。該專案中主要定義了以下領域物件:
- Order:訂單
- OrderItem:訂單項
- OrderStatus:訂單狀態
- Buyer:買家
- Address:地址
- PaymentMethod:支付方式
- CardType:銀行卡片型別
在該示例專案中,定義了兩個聚合:訂單聚合和買家聚合,其中Order和Buyer分屬兩個聚合根,其中訂單聚合透過持有買家聚合的唯一ID進行關聯。如下圖所示:
我們依次來看其對物體、值物件、聚合、資源庫、領域事件的實現方式。
4.1. 物體、值物件與聚合
物體與值物件最大的區別在於,物體有識別符號可變,值物件不可變。為了保證領域的不變性,也就是更好的封裝,所有的屬性欄位都設定為 privateset
,集合都設定為只讀的,透過建構式進行初始化,透過暴露方法供外部呼叫修改。
從類圖中我們可以看出,其主要定義了一個 Entity
抽象基類,所有的物體透過繼承 Entity
來實現命名約定。這裡面有兩點需要說明:
- 透過
Id
屬性確保唯一識別符號 - 重寫
Equals
和GetHashCode
方法(hash值計算:this.Id.GetHashCode()^31
) - 定義
DomainEvents
來儲存物體關聯的領域事件(領域事件的發生歸根結底是由於領域物件的狀態變化引起的,而領域物件[物體、值物件和聚合])中值物件是不可變的,而聚合往往包含多個物體,所以將領域事件關聯在物體上最合適不過。)
同樣,值物件也是透過繼承抽象基類 ValueObject
來進行約定。其主要也是多載了 Equals
和 GetHashCode
和方法。這裡面有必要學習其 GetHashCode
的實現技巧: