領域驅動設計-聚合,一種極簡的思維模式
引言
作為IT技術產業飛速發展的產物,軟件工程學已經成為當今時代非常重要的一個學科。作為一名資深的軟件開發從業者,我們需要學習的東西實際上已經遠遠超出了原本在大學教育階段所接受的知識深度和廣度,領域驅動設計更是如此。當然必須承認的是大學階段開了很多扇窗,直到今天才深刻體會那些平時看起來毫不起眼的學科(如圖論、概率論、高等代數),實際上對軟件領域的影響已經遠遠超出了我們的想象,例如,如果想做AI,沒有扎實的數學和圖論基礎,顯然只能成為工具的使用者,而非技術專家。
有許多讀者提到,筆者的內容缺乏實際例子,在具體閱讀時,很難形成帶入感。主要是因為領域驅動設計思想本身體系龐大,細節非常多,這需要在日常學習之余多加思考,細細的品味知識中的奧妙,筆者也是按照同樣的思路來指導自己的學習過程的。坦率而言,要把這些概念的名詞都記住,顯然很容易,但是要理解這些名詞的具體含義以及實際應用場景,卻需要更多的思考,這也同樣是軟件工程博大精深的奧妙所在。
領域生命周期的復雜性是如何影響設計的
我們都清楚領域驅動設計,作為應對復雜情形下的軟件工程思路,實際上受到了傳統軟件思維的廣泛影響,例如之前提到的實體和值對象、以及服務和包(模塊)實際上在非領域驅動設計中同樣普遍存在。但是聚合和聚合根的思想,應該屬於領域驅動設計中獨特的知識點,進一步加強這個知識點的認識,將有助於我們更好的進行聚合設計,從而更好的設計一個符合實際應用場景的應用系統。
在上一篇文章中,我們了解到,領域驅動的五個基本部分(關聯,實體,值對象,服務和模塊),他們是構成軟件體系的最基礎元素。在一個簡單的軟件系統中,往往只需使用這些元素的簡單組合即可完成單個模塊功能的開發,而且顯然速度非常迅速。但是我們也將同樣面對一些對象,他們具有更長的生命周期,也許有相當一部分時間,是通過復雜的數據持久化處理機制、甚至是跨數據源、跨服務來完成,這意味着不是單純的依靠一塊內存空間來度過的。它們與其他對象有着復雜的依賴關系,在它們漫長的生命周期中,會根據不同場景的規則、經歷許多次狀態的變化。對於這些對象的操作,稍不留心,就會導致代碼間的耦合度急劇提升,甚至成為軟件系統中最難以維護的代碼塊,這實際上偏離了模型驅動設計的理想軌道,成為經驗設計史上的一大典型問題。
領域驅動設計認為,這種復雜過程的操作對模型驅動設計帶來的影響主要包括以下兩個方面:
1、維護對象間,在整個生命周期中的完整性:對象依賴不同的數據源或存儲機制或內存單元時,完整性將難以保障。
2、陷入管理生命周期復雜性造成的困境中。同上,要維護這套具有復雜體系的模型結果,本身成為一個問題。
聚合,讓設計簡化
領域驅動設計思想針對這兩種場景,設計了聚合(Aggregate)對象來解決這個問題,並使用工廠對象和倉儲對象來對生命周期進行管理,由於時間和篇幅的關系,我這一篇先介紹聚合對象和聚合根,下一篇在介紹工廠對象和倉儲對象。
實際上我們很容易就設計出一個具有復雜關系的對象,例如,Person對象,實際上可能關聯了地址和工作等不同的實體或者值對象,如果要對數據進行刪除,可能傾向於直接刪除Person對象,而保留其他對象;或者刪除Person對象時,同時刪除地址對象。但是這兩種方式都並非非常合理的策略,在於方式一,會在數據庫中形成冗余數據,不利於后期數據的維護管理;而后者則可能導致依賴於地址的其他Person出現異常。
即使是再簡單的場景,遇到並發訪問時,也會存在問題。由於不同的用戶對系統中的數據的訪問是隨機分布的,意味着有可能會造成多個用戶同時修改相互依賴的對象,進而造成系統可用性的急劇下降。
因此,在具有復雜關聯的模型中,要想保證數據更改的一致性是很困難的。不僅互不關聯的對象需要遵守一定的規則,而且緊密關聯的對象間操作同樣也存在規則。經常使用的一種方式可能是事務鎖,但是設計謹慎的鎖機制,固然可以解決這個問題,但是可能導致用戶間的操作不可控,系統變得不可用。事實上數據庫層面的行鎖和表鎖,也是為了解決這些問題提供的思路,但是這種方案實際上分散了人們對於模型的注意力,使得系統流程的設計過程本身就相當臃腫。這也是古老的系統用戶體驗不佳的一個主要原因。
領域驅動設計認為,表面上看是對數據操作層面的技術問題,但是它的根源依然是由於模型的設計依然是基於實體關系模型的設計,而缺乏明確定義的邊界。認為通過一個合理的模型的設計,可以是模型更加理解,並且使設計過程更易於溝通。當模型被修改時,它也將引導我們對實現進行修改。
這種模式,就是聚合模式(Aggregate)。這種來源於制造業體系中的模型,簡單但嚴格,但是可以提供新的思路。
領域驅動設計中,認為實現這個聚合模型,應當包含以下要素:
1、通過一個頂層抽象來封裝模型中的引用。使用Aggregate對象,實現一組相關對象的集合,作為數據修改的單元。
2、每個Aggreate對象具有一個根和邊界。邊界,用以定義Aggreate內部都有什么。而根是Aggregate對外暴露的特定實體。對Aggregate而言,外部對象只可以引用根,而邊界內部的對象則可以相互引用。
3、除根之外的所有實體,在Aggregate內部都有唯一標識,但外部對象只能看到根實體而無法看到其他實體。
對Aggregate的操作,應該按照一定的規則,確保數據變化時,能夠保持一致性。而任何跨越Aggregate的規則,則不要求每時每刻都保持最新狀態,跨越通過事件處理、批處理或者其他更新機制,使依賴項在規定的時間內得到解決。但是在Aggregate內部,規則必須得到滿足。
這意味着,對於這個Aggregate的操作,必須應用更加具體的規則,包括但不限定於以下內容。
1、聚合根Entity,具有全局標識,代表整個Aggregate對外提供服務,並最終負責檢查規則。
2、邊界內的對象具有本地標識,但僅限於Aggregate內部保持唯一性。
3、Aggregate外部的對象不能引用除根Entity之外的其他內部對象。根可以將內部對象的引用傳遞給外部對象,但是外部對象只能使用,而不能保持引用更不能操作。這也意味着,根可以將值對象的副本傳遞給外部對象,因為它只是一個屬性值,而不是一個完整的生命周期對象。
4、只有Aggregate的對象才能通過數據庫查詢直接完成,而其他對象應該在創建后,通過根的對象遍歷關聯來發現。
5、Aggregate內部對象,可以引用外部的Aggregate根對象的引用(不能反過來)
6、刪除操作,應該一次性刪除Aggregate邊界內的所有對象。
7、對Aggregate內部任何對象的操作,必須保證上述規則都得到滿足。
總結
Aggregate對象實際上是通過划分一個界限清晰的范圍,確保在Aggregate對象的生命周期內,對范圍內對象每個階段的操作都滿足規定規則。
對Aggregate對象的定義和分析是一件非常細致的工作,我們應該根據實際應用場景,將實體和值對象分別聚集到Aggregate中,定義好邊界和根后,通過根Entity來控制對邊界內部其他對象的訪問。只允許外部對象引用根,並在一次操作中,臨時引用內部成員。但不能通過根來修改內部對象,這種設計有利於Aggregate內部的對象滿足規則,也能保證它本身能夠作為一個整體滿足規則。而對Aggregate對象上的操作,是通過下一篇提到的Factory和Repository來實現的,它們分別在不同的階段,實現了對象轉化的復雜性封裝。
領域驅動設計思想是一個博大精深,內涵豐富的策略,這個系列主要是我在閱讀相關文獻過程中的筆記的積累,同時也是期望能夠將相關內容梳理、形成自己的設計思想的一部分,如果大家想了解更多的知識,強烈推薦大家閱讀《領域驅動設計-軟件核心復雜性應對之道》。