在DDD中建立領域模型
在前文《當我們談論DDD時我們在談論什麼》中我們討論了DDD的戰略設計和戰術設計。在本文中我們將繼續探討領域模型。
在實際專案中,模型設計者往往過早陷入具體構造塊型別的識別,比如實體、聚合、領域服務,而忽略了領域模型表達領域概念的目的。我們應該基於領域概念設計領域模型,然後再採用合適的模式降低領域模型的複雜度,進一步增加領域模型的表達能力。
領域模型的作用,一方面是關聯程式碼實現,一方面是關聯通用語言。我們對於模型和實現的關聯輕車熟路,但是對於語言和模型關聯往往有待提升。在溝通中刻意使用通用語言可以幫助我們驗證模型的合理性。
我們以一個題目為例,方便後續討論。
第一步是根據需求分析模型。
我們可以找到以下概念:活動、參與資格、權益。其中參與資格是擴充套件點。
對於需求「一個使用者只能參加一次活動的」,需要記錄使用者是否參與過活動,所以需要「活動參與記錄」的概念。
參與活動的結果可能有2種:符合參與資格則返回權益,不符合則返回「不符合」。所以我們用了Optional<權益>型別。
我們到這裡只識別了各種名詞,需要走查用例,尋找缺失的概念:
用例1,運營人員可以建立並修改活動
用例2,使用者可以參與活動並獲得權益
對於用例1,建立和修改活動,目前模型已經滿足了需求。
對於用例2,這裡有一個模型之外的規則:「一個使用者只能參加一次活動」。這是所有活動都需要遵守的規則,我們將其稱為「活動通用規則」。
雖然只是一個很簡單的邏輯,但是提取「活動通用規則」這個概念非常有用。如果沒有這個概念,那麼每次去描述這個概念,只能用「一個使用者只能參加一次活動的規則」去表示,非常繁瑣;也讓概念沒有安身之地,容易被隨便放到萬能的Service中。
我們將其加入領域模型。
PS:這裡故意省略了參與資格的實現。
我們沒有把「活動通用規則」放到活動概念裡,一部分原因是這個判斷邏輯不需要具體活動的資訊。
有了領域模型,就有了通用語言。使用通用語言重新描述需求,並儘量在溝通中使用通用語言。
這裡去掉了「開始時間」、「資格」、「獎品」等模糊不清晰的描述。使用基於領域模型的語言,讓需求描述清晰沒有歧義。
到目前為止,主要的領域模型都已經分析出來。所有的模型都對應明確的領域概念,不多也不少。
在分析了領域模型後,我們再來分析構造塊型別。
我們透過是否有狀態來做區分。
首先識別有狀態的物件:活動、各種參與資格、權益、活動參與記錄、使用者。一般有狀態的物件都是事物,對應的構造塊型別也就是實體或者值物件。
其次判斷其狀態是否會改變:
活動會被修改,所以狀態會被改變;
參與資格會被修改,但是參與資格從屬於活動,修改後可以直接使用新的物件替換舊的,所以可以設計成狀態不變;
權益和參與資格一樣,也可以設計成狀態不變;
活動參與記錄,狀態可能發生變化;
使用者在這個模型中只是臨時存在,狀態不會變化。
狀態會改變的是實體,包括活動和活動參與記錄;狀態不變的就是值物件,包括參與資格、權益和使用者。
最後剩下的就是無狀態的物件:活動通用規則。對應的構造塊型別是領域服務。
這裡的無狀態物件大都可以轉化成有狀態的物件,例如活動通用規則,可以將方法引數的Optional<活動參與記錄>變成成員變數。只是這裡我們選擇了無狀態的設計方法。
由於領域服務沒有狀態,所以可以在應用啟動時就建立出來,也可以在使用時才建立。
經過分析,我們的領域模型都有了型別。
首先識別生命週期長的領域物件:在一個操作中被建立出來,操作結束後仍會被其他操作使用的物件。活動、參與資格、權益和活動參與記錄都是生命週期長的物件。
其他有狀態的物件都是臨時物件:在一個操作中被建立出來,操作結束後就不會再被使用。模型中的使用者,在一次操作中從其他服務獲取,使用後即被丟棄。
這裡我們總結下各構造塊型別的特點:
實體 | 值物件 | 領域服務 | |
是否有狀態 | 有且狀態可變 | 有且狀態不可變 | 無 |
生命週期 | 長 | 長或者短 | 長短均可 |
在生命週期的長的物件中,我們要設計聚合。聚合作為操作單元,主要解決以下幾個問題:
整個模型往往龐大複雜,為了降低知識負載,需要將其分解成多個小且簡單的模型,劃分清晰的邊界
部分模型物件之間存在一致性規則,例如需要被一起刪除,所以需要放在一個操作中
多個使用者可能會併發操作模型,為了避免相互干擾,需要讓操作單元儘可能小
對於操作單元,需要將其頻繁載入到記憶體中,如果單元過大,往往不能滿足效能要求
根據對業務的瞭解,活動及參與資格、權益都是一起被建立和修改,可以放在一個聚合裡;活動和活動參與記錄之間沒有一致性規則,可以分開;因為活動參與記錄數量會很多,如果和活動在一個聚合中,會降低效能。
所以我們將活動、參與資格、權益設計成一個聚合,而活動參與記錄作為一個單獨的聚合。而活動和活動參與記錄分別作為這兩個聚合的聚合根。對應的,聚合都會配備其專屬的Repository。
同時加上遍歷方向箭頭。由於活動是聚合根,從活動可以遍歷到聚合內部的參與資格和權益。另外查詢活動參與記錄,可以透過其Repository,所以沒有活動到活動參與記錄的箭頭。
由於我們將活動和活動參與記錄之間劃分成不同聚合,那他們之間的關聯將使用聚合的ID來關聯,而不是聚合本身。
PS:如果使用了關聯物件,遍歷方向也可以是從活動到活動參與記錄。
領域模型已經建立完畢,我們來看如何使用領域模型以滿足用例。
運營人員建立活動基本資訊及其關聯的參與資格和權益。領域模型的客戶(一般來說是應用服務),使用運營人員輸入的引數構造出活動物件,再利用Repository將其儲存。
運營人員修改活動。應用服務利用Repository獲取需要修改的活動,再根據運營人員提供的引數修改活動,最後利用Repository儲存活動物件。
使用者參與活動。應用服務:
使用活動通用規則判斷使用者是否可以參加。由於活動通用規則需要用到活動參與記錄,因此應用服務會使用Repository獲取活動參與記錄;
如果可以參加,則執行活動的參與活動方法獲得結果。這需要利用Repository獲取使用者參與的活動,並構造使用者物件(可能需要呼叫使用者服務獲取使用者資訊,但是領域層並不關心這些邏輯);
如果結果是獲得權益,則建立活動參與記錄,並利用Repository儲存。
考慮到併發情況,應用服務可以在第1步前加鎖,並在第3步後釋放鎖。
配置和參與活動可否是兩個模型?
在實現運營人員配置活動的用例過程中,我們會發現可能找到了一個隱藏的領域概念,將輸入的引數轉換成領域模型的邏輯有些枯燥和複雜,同樣將領域模型和資料庫的資料模型之間轉換也如此。輸入引數和資料模型都是隻是扁平的資料資料,沒有繼承結構。如果使用另外一種面向資料的模型,也許這些用例實現起來會簡單得多。
每個模型都是為了解決某個問題。這裡運營人員配置和使用者參與活動是不同的問題,如果用一個模型來解決這兩個問題,可能會有些吃力。那麼幹脆設計成兩個模型,使用限界上下文的概念將這兩個模型限定在各自的上下文中,也許更加合理。兩個模型可以共享同一份資料庫資料,並加上一段(非領域層的)邏輯用於模型之間的轉換。
這實際上是一種配置-使用模式。在配置階段,注重配置型別和引數、審批等;在使用階段,注重邏輯計算和效能。
活動參與記錄是否可以建模成領域事件?
活動參與記錄實際上是不可變的,可以將其設計為領域事件。
使用者參與活動的用例裡,邏輯複雜,有洩漏領域概念的嫌疑?
如果發現應用服務裡邏輯變得複雜,可能意味著我們找到了一個隱藏的領域概念。我們可以定義一個「使用者參與活動邏輯」的概念:如果使用者透過了活動通用規則的判斷,則可以參與活動。將其加入模型和通用語言中,在溝通中驗證此概念是否合理。
很多專案雖然也使用了以領域模型為中心的架構,但是設計者仍然是資料模型/貧血領域模型的思考方式,把大量領域邏輯放置在了萬能的Service中,讓領域概念隱藏在了冗長的過程程式碼中,無法享受到DDD帶來的收益。
最後總結下本文想要強調的要點:
領域模型和領域概念一一對應
領域模型和實現關聯,也和通用語言關聯。刻意使用通用語言溝通以驗證模型是否合理
演示了一種設計領域模型的步驟
構造塊型別不是最重要的,領域模型本身更加重要
更多的使用可以表達業務含義的值物件和臨時值物件
聚合是一種設計,需要方法權衡
使用Repository、Factory獲取和建立領域模型是應用層的職責,領域層應該關注在表達領域概念
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2936412/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 運用領域模型——DDD模型
- 領域驅動設計(DDD)中模型的重要性 - Jeronimo模型
- 領域驅動模型DDD(一)——服務拆分策略模型
- 領域驅動模型DDD(二)——領域事件的訂閱/釋出實踐模型事件
- DDD-領域物件與領域服務物件
- DDD領域驅動設計:領域事件事件
- 領域驅動模型DDD(三)——使用Saga管理事務模型
- DDD之2領域概念
- DDD設計中領域模型是否可以依賴第三方? - Mathias Verraes模型
- DDD中如何藉助行業術語突破性發現領域模型? - Mathias行業模型
- DDD領域設計概念梳理
- DDD:不要洩露領域事件事件
- 領域驅動設計的DDD與ddd - nick
- 如何進行高質量的DDD領域建模?什麼是領域模型?如何捕捉?尺寸如何? - Manning模型
- 談DDD與貧血領域模型:再次為失血模型辯護 -Codecentric AG部落格模型
- 使用函式式語言來建立領域模型函式模型
- DDD領域驅動設計pdf
- DDD劃分領域、子域,核心域,支撐域的目的
- 聊一聊中臺和DDD(領域驅動設計)
- 基於COLA架構建立運輸微服務應用和DDD領域建模架構微服務
- DDD-領域驅動設計示例
- 淺談DDD(領域驅動設計)
- DDD學習(二)—— 領域建模重要概念
- ABP與DDD領域驅動關係
- 淺談 DDD 領域驅動設計
- DDD+Javascript領域建模示例 -Alex LawrenceJavaScript
- DDD領域驅動設計:倉儲
- 淺談領域模型模型
- 使用Spring Data JPA在更改實體時釋出DDD領域事件 - thorbenSpring事件ORB
- 領域驅動設計(DDD)入門&概要
- DDD-領域驅動設計簡談
- dayatang/dddlib:DDD領域驅動設計庫
- 領域本體與DDD的UL語言
- 領域驅動設計 (DDD) 簡介 - jannikwempe
- 領域驅動設計(DDD)高手養成記
- 領域驅動設計(DDD)實踐之路(一)
- 使用Typescript實現DDD領域建模 - Matthew de NobregaTypeScript
- 解決DDD最大難題-如何劃分領域