1. 程式人生 > 其它 >領域驅動設計(3) DDD設計流程

領域驅動設計(3) DDD設計流程

DDD整體包含戰略設計和戰術設計兩部分。戰略設計過程會從業務視角出發,進行場景分析、領域建模,並劃分領域邊界、建立通用語言、確定限界上下文;戰術設計則關注如何將模型轉化為軟體實現,涉及聚合根、實體、值物件、領域服務、應用服務等概念。所以戰略設計重在把控方向、建立模型,戰術設計重在軟體實現,戰略設計的好壞直接決定了DDD能否成功實施。所以除了關注DDD戰術設計中用到的工具集、思想(比如四層架構、CQRS等)之外,更應該關注其戰略設計。

DDD也是協作方式的變革,在戰略設計、戰術設計過程中,需要業務、技術雙方角色的共同參與,比如領域專家、業務需求方、產品經理、架構師、開發經理、測試經理等。
下面以一個請假、考勤系統的案例來貫穿DDD的整個設計流程。系統的功能包括:

  1. 請假:請假人可以提交請假單,根據請假人的身份、請假型別、請假天數進行校驗,然後根據審批規則逐級提交給上次審批,審批通過後記錄考勤,否則請假申請被拒絕;
  2. 考勤:根據考勤規則,核銷請假資料,對請假資料進行校驗,輸出考勤統計。

戰略設計

戰略設計的目的是根據使用者旅程分析,找出領域物件和聚合根,對實體和值物件進行聚類組成聚合,劃分限界上下文,建立領域模型。
戰略設計採用事件風暴的方法,包括產品願景和場景分析、領域建模、微服務拆分(如果要實現為微服務的話)等過程。

產品願景與場景分析

產品願景是對產品頂層價值的設計,避免產品偏離方向。事件風暴中,大家討論的維度可以圍繞產品目標使用者、核心價值、差異化競爭點。如果所涉及的系統目標和需求非常明確,那麼這一步可以忽略。

場景分析則是從使用者的視角出發,探索業務領域中的典型場景,明確涉及的場景、每個場景的用例。然後根據不同角色的場景分析,儘可能全面地梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係。

以請假和審批為例:請假的使用者(角色)為請假人,審批的使用者(角色)為審批人。
作為請假人,其使用者旅程為:

  • 登入系統,從鑑權中心獲取請假人資訊和許可權資料,完成登入認證;
  • 建立請假單:在請假頁面選擇請假型別、起始時間,錄入請假休息,儲存請假單;
  • 修改請假單:查詢請假單,修改、儲存;
  • 提交審批:將建立的請假單提交審批,系統會獲取審批規則,並根據審批規則,從人員組織關係中獲取審批人,給請假單分配審批人。

作為審批人,其使用者旅程為:

  • 登入系統,從鑑權中心獲取請假人資訊和許可權資料,完成登入認證;
  • 獲取請假單:獲取審批人名下的請假單;
  • 審批:填寫審批意見,批准或者拒絕;
  • 逐級審批:審批人批准請假單後,系統會根據審批規則判斷是否需要繼續審批;
  • 完成審批:系統發出領域事件:請假單已審批通過,後續會進一步執行郵件通知請假人、將請假資料傳送到考勤的操作。

基於上述使用者旅程進行場景分析,可以用不同的顏色來區分命令、業務流、事件、說明資訊。
以業務流為主線,梳理出請假、審批的流程,然後基於業務流的每個步驟,梳理出觸發這個步驟的命令,步驟完成後,會產生的事件。最終的參考結果如下圖:

這張圖中,“根據審批規則查詢審批人”需要依賴另一個上下文:人員組織關係,它的場景分析如下:

領域建模

領域建模,會根據場景分析梳理出的事件、命令,找出實體、聚合根、限界上下文,最終建立領域模型。
如果說場景分析是一個發散的過程,各種角色各抒己見,大家一起分析使用者旅程;那麼領域建模就是一個收斂的過程。在這一步中,會由對DDD有足夠了解的架構師、開發人員來主導完成。而且見仁見智,收斂的結果很可能因人而異,不過建模結果並沒有絕對的好壞,只要能適應當前場景,而且模型還會在後續的知識迴圈、迭代中不斷完善,最終不斷逼近最優解。
領域建模可以按這三個步驟進行:

  1. 找出實體、值物件等領域物件;
  2. 將這些領域物件形成聚合,確定聚合根;
  3. 根據業務、語義邊界等因素,定義限界上下文;

找出領域物件

場景分析梳理出了事件、命令,接下來就要分析並找出產生這些命令、領域事件的實體和值物件。
下圖為在請假、審批系統找出的實體、值物件(圖中都是綠色):

定義聚合

定義聚合前,先找出聚合根,聚合根可以管理聚合中的實體,所以可以將請假單、人員作為兩個聚合根;然後找出與聚合根緊密依賴的實體和值物件,請假單聚合根下包含審批意見、審批規則,人員聚合根下包含組織關係。
那麼剩下的刷卡明細、考勤明細、考勤統計該怎麼辦呢,這也是一類典型的場景,即幾個實體、值物件之間相互獨立,找不出聚合根,卻又共同完成一項功能,具有很高的業務內聚性。對於這種情況,仍然可以採用DDD的分析設計方法,將這些實體、值物件組成一個沒有聚合根的聚合,但由於沒有聚合根,實體的管理就交給領域服務了。
於是現成了請假、人員、考勤三個聚合:

其中請假聚合中,可以將審批規則作為值物件,因為它是來自人員聚合的副本;人員聚合中,人員和組織關係都作為實體,沒有值物件;考勤聚合也沒有值物件。
實際執行中,可以用一張表格整理這些聚合中的領域物件,標記它們分別是實體還是值物件.

定義限界上下文

人員組織關係聚合和請假聚合共同完成請假的業務功能,所以可以將兩者劃分為請假限界上下文;考勤聚合單獨構成考勤限界上下文。

微服務拆分

限界上下文可以作為粗粒度的微服務邊界,但落地時往往不得不考慮更多其他因素,比如彈性邊界、安全需求、軟體包大小、團隊溝通效率、技術異構等等。
這個案例中我們直接將限界上下文作為微服務邊界。

戰術設計

以上,戰略設計部分就完成了。在戰略設計過程中,進行了場景分析,建立了領域模型,確定了微服務邊界。接下來開始戰術設計過程。

分析微服務領域物件

領域模型包含很多領域物件,如何把這些領域物件與程式碼設計結構相對應,是這個戰術設計關注的重點。這一步會細化領域物件,有助於發現事件風暴過程中可能遺漏的業務、技術細節。
具體步驟為:

  1. 根據命令設計應用服務,確定應用服務的功能,服務集合、編排方式。服務集合中的服務包括領域服務或其它微服務的應用服務;
  2. 根據應用服務功能要求設計領域服務,定義領域服務;
  3. 根據領域服務的功能,確定領域服務內的實體以及功能;
  4. 設計實體的基本屬性和方法。

以提交審批這個動作為例,提交審批的過程為:

  1. 根據請假型別、時長,查詢請假審批規則,獲取下一步審批人的角色;
  2. 根據審批角色從人員組織關係中查詢下一審批人;
  3. 為請假單分配審批人,並將請假規則儲存到請假單;

設計應用服務、領域服務

首先進行第1、2步;基於以上流程,需要設計以下服務、方法:

  • 應用層:提交審批應用服務;
  • 領域層:
    • 請假聚合:查詢審批規則、修改請假流程資訊服務。請假單實體提供修改請假流程資訊方法,審批規則值物件有查詢審批規則方法;
    • 人員聚合:根據規則查詢審批人服務;人員實體提供根據審批規則查詢審批人方法。

整體關係如下圖:

設計聚合中的物件

在請假單聚合中,請假單是聚合根。請假單經多級審批後,會產生多條審批意見,為了便於查詢,可以將審批意見設計為實體。請假審批通過後,會產生請假審批通過的領域事件,因此還會有請假事件實體。
接下來分析請假單聚合中的值物件。請假人和下一審批人資料來源於人員組織關係聚合中的人員實體,可以設計為值物件。人員型別、請假型別、審批狀態時列舉值型別,可以設計為值物件;請假審批規則也可作為值物件。
綜上,請假聚合物件關係圖如下:

人員組織關係聚合中,聚合根為人員,實體為組織關係,組織關係實體中包括設計審批領導、組織關係類似,兩者都可以設計為值物件。

在確定各聚合領域物件的屬性後,可以接著設計各領域物件在程式碼模型中的程式碼物件,建立領域物件對程式碼物件的對映關係。這個過程可以選擇用表格的形式,來確定物件所屬的名稱空間、類名,以及方法名等等,就不再詳述了。

設計微服務程式碼結構

應用層程式碼結構

應用層包括應用服務、DTO以及事件釋出相關的程式碼。

領域層程式碼結構

在領域層,一個聚合對應一個聚合程式碼目錄,聚合之間在程式碼上完全隔離,只能通過應用層互動。這樣做的好處是控制耦合,如果日後需要拆分微服務,則可以直接將聚合獨立出來,再對應用層稍加改造就可以了。

參考資料: 歐創新 《DDD實戰課》