小編在 2016 年初次接觸領域驅動設計,可能因為之前沒有留意,感覺它還不怎麼被大家認可,實踐 1 年多的時間以來,伴随着業務對微服務的渴求,也越來越多的看到有關微服務的文章裡在提倡采用 DDD 領域驅動設計來實作模型設計,架構設計。領域驅動設計包括戰略模組化(即架構設計)和戰術模組化(即模型設計)。戰術模型中包含領域,子域,限界上下文,聚合,實體,值對象,領域服務,領域事件,子產品,工廠,資源庫等等。本 chat 的核心是分享領域驅動設計中戰術模型設計的聚合,實體的設計,這也是采用 DDD 最為核心的内容,分為以下幾個方面:
- 什麼是聚合
- 如何建立聚合
- 聚合的設計原則
什麼是聚合?
首先談什麼是領域,小編第一次分享這個話題的時候,有人問到什麼是領域?小編被問到了,讓百科解決下吧,領域,一種專門活動或事業的範圍、部類或部門。例如,一個電商系統,一個線上教育平台等都是一個領域,領域中存在問題空間和解決問題空間,問題空間由子域組成,解決問題空間則由多個限界上下文,即一組特定的軟體模型。我們的戰術模型設計也就産生在每個限界上下文的範圍内。 聚合是一種特殊的實體,實體的概念是:一個唯一的東西,并且可以再相當長的一段時間内持續地變化; 聚合包裝一組高度相關的對象,作為一個資料修改的單元。聚合最外層的對象稱為聚合根,它是一個實體。 聚合根劃分出一個清晰的邊界,聚合根外部的對象,不能直接通路聚合根内部對象,如果需要通路内部對象,必須首先通路聚合根,再導航到聚合的内部對象。
如何建立聚合?
根據聚合的定義我們可以擷取以下資訊:
聚合要有唯一辨別;
一段時間内持續變化;
組裝高度相關的對象;
建立一個新聚合時,我們希望通過構造函數來初始化足夠多的實體狀态,哪些内容需要傳給構造函數呢? 唯一辨別,能夠唯一的确定一個對象;對象的一個或多個不變條件,不變條件即使在整個實體生命周期中都必須保持事務一緻性的狀态。比如使用者必須有名字,性别。或者說,如果實體的不變條件要求改實體所包含的對象都不能為 null 狀态,或者由其他狀态計算所得,那麼這些狀态需要作為參數傳遞給構造函數。 并通過修飾符(private,default,protect,public)來保證對象資料的一緻性和不變性。 另外,建立聚合時,聚合内部通過驗證的方式來保證資料的有效性。是以聚合中會提供一些驗證方法,驗證一般有屬性驗證,整體對象驗證,組合對象驗證等。
聚合的設計原則:
聚合内事務一緻性
設計盡可能的小聚合
通過唯一辨別引用其它聚合
聚合外使用最終一緻性
優先使用值對象
聚合中避免使用依賴注入
迪米特法則
1、聚合内事務一緻性
即一個業務規則内,該規則應該總是保持一緻的。事務一緻性可分為立即性,原子性。聚合邊界之内的所有内容組成了一套不變的業務規則,任何操作都不能違背這些規則。邊界之外的任何東西與該聚合都是不相關的。是以,聚合表達了與事務一緻性邊界相同的意思。在一個事務中隻能修改一個聚合執行個體,這是設計聚合的重要經驗原則,也是業務為什麼要使用聚合的原因。
2、設計盡可能的小聚合
聚合的設計盡量小到不可再拆分,聚合的修改要保證在一個事務邊界内,大聚合中很難保證,聚合的事務原子一緻性也隻能通過最終一緻性來保證,大聚合在查詢時需要加載的資料,涉及到的實體較多,會降低查詢的性能。
3、通過唯一辨別引用其他聚合
引用聚合和被引用聚合不可以在同一個事務中進行修改。如果你試圖在單個事務中修改多個聚合,這往往意味着此時的一緻性邊界是錯誤的。發生這樣的情況通常是因為我們遺漏了某些模組化點或者尚未發現通用語言中的某個概念。如訂單中隻會留有貨品的 ID。
4、聚合外使用最終一緻性
經過一段時間後要求能通路到更新後的資料,則是最終一緻性。任何跨聚合的業務規則都不能總是保持處于最新狀态。通過事件處理、批處理或者其它的更新機制,我們可以在一定時間之内處理好依賴。這個原則的意思是說:聚合内事務一緻性,聚合之間最終一緻性。
5、優先使用值對象
我們應該盡量的将聚合根所包含的其它聚合模組化成值對象,而不是實體。在不至于對模型或基礎設施造成明顯影響的情況下,采用值對象全部替換的方式是最好的選擇。(值對象,隻作為資料傳輸對象,無狀态變化,且一旦建立不再修改)
6、聚合中避免使用依賴注入
在一個高吞吐量、高性能的領域中,記憶體吃緊,垃圾回收周期漫長,那麼我們就不應該給系統增加不必要的負擔。比如,我們可以在調用聚合指令方法之前查找到所依賴的對象。當然,以上隻是告誡大家不要在聚合中注入資源庫和領域服務(資源庫,負責資料存儲對接存儲系統;領域服務,負責聚合間的業務邏輯處理)。
7、迪米特法則
迪米特法則:強調了“最小知識”原則。 在用戶端對象使用服務對象時,它應該盡量少的知道服務對象的内部結構。 用戶端對象不應該知道任何關于服務對象屬性的資訊。 用戶端對象可以根據表層接口調用服務對象的指令方法。