歡迎光臨
每天分享高質量文章

領域驅動設計,讓程式員心中有碼(六)

領域驅動設計-聚合,一種極簡的思維樣式

引言

作為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內部保持唯一性。

        3Aggregate外部的物件不能取用除根Entity之外的其他內部物件。根可以將內部物件的取用傳遞給外部物件,但是外部物件只能使用,而不能保持取用更不能操作。這也意味著,根可以將值物件的副本傳遞給外部物件,因為它只是一個屬性值,而不是一個完整的生命週期物件。

        4、只有Aggregate的物件才能透過資料庫查詢直接完成,而其他物件應該在建立後,透過根的物件遍歷關聯來發現。

        5Aggregate內部物件,可以取用外部的Aggregate根物件的取用(不能反過來)

        6、刪除操作,應該一次性刪除Aggregate邊界內的所有物件。

        7、對Aggregate內部任何物件的操作,必須保證上述規則都得到滿足。

總結

  Aggregate物件實際上是透過劃分一個界限清晰的範圍,確保在Aggregate物件的生命週期內,對範圍內物件每個階段的操作都滿足規定規則。

  對Aggregate物件的定義和分析是一件非常細緻的工作,我們應該根據實際應用場景,將物體和值物件分別聚集到Aggregate中,定義好邊界和根後,透過根Entity來控制對邊界內部其他物件的訪問。只允許外部物件取用根,併在一次操作中,臨時取用內部成員。但不能透過根來修改內部物件,這種設計有利於Aggregate內部的物件滿足規則,也能保證它本身能夠作為一個整體滿足規則。而對Aggregate物件上的操作,是透過下一篇提到的Factory和Repository來實現的,它們分別在不同的階段,實現了物件轉化的複雜性封裝。

贊(0)

分享創造快樂