【DDD】學習筆記-聚合和聚合根:怎樣設計聚合?

今天我們來學習聚合(Aggregate)和聚合根(AggregateRoot)。

我們先回顧下上一講,在事件風暴中,我們會根據一些業務操作和行為找出實體(Entity)或值對象(ValueObject),進而將業務關聯緊密的實體和值對象進行組合,構成聚合,再根據業務語義將多個聚合劃定到同一個限界上下文(Bounded Context)中,并在限界上下文內完成領域建模。

那你知道為什么要在限界上下文和實體之間增加聚合和聚合根這兩個概念嗎?它們的作用是什么?怎么設計聚合?這就是我們這一講重點要關注的問題。

聚合

在 DDD 中,實體和值對象是很基礎的領域對象。實體一般對應業務對象,它具有業務屬性和業務行為;而值對象主要是屬性集合,對實體的狀態和特征進行描述。但實體和值對象都只是個體化的對象,它們的行為表現出來的是個體的能力。

那聚合在其中起什么作用呢?

舉個例子。社會是由一個個的個體組成的,象征著我們每一個人。隨著社會的發展,慢慢出現了社團、機構、部門等組織,我們開始從個人變成了組織的一員,大家可以協同一致的工作,朝著一個最大的目標前進,發揮出更大的力量。

領域模型內的實體和值對象就好比個體,而能讓實體和值對象協同工作的組織就是聚合,它用來確保這些領域對象在實現共同的業務邏輯時,能保證數據的一致性。

你可以這么理解,聚合就是由業務和邏輯緊密關聯的實體和值對象組合而成的,聚合是數據修改和持久化的基本單元,每一個聚合對應一個倉儲,實現數據的持久化。

聚合有一個聚合根和上下文邊界,這個邊界根據業務單一職責和高內聚原則,定義了聚合內部應該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設計出來的微服務很自然就是“高內聚、低耦合”的。

聚合在 DDD 分層架構里屬于領域層,領域層包含了多個聚合,共同實現核心業務邏輯。聚合內實體以充血模型實現個體業務能力,以及業務邏輯的高內聚。跨多個實體的業務邏輯通過領域服務來實現,跨多個聚合的業務邏輯通過應用服務來實現。比如有的業務場景需要同一個聚合的 A 和 B 兩個實體來共同完成,我們就可以將這段業務邏輯用領域服務來實現;而有的業務邏輯需要聚合 C 和聚合 D 中的兩個服務共同完成,這時你就可以用應用服務來組合這兩個服務。

聚合根

聚合根的主要目的是為了避免由于復雜數據模型缺少統一的業務規則控制,而導致聚合、實體之間數據不一致性的問題。

傳統數據模型中的每一個實體都是對等的,如果任由實體進行無控制地調用和數據修改,很可能會導致實體之間數據邏輯的不一致。而如果采用鎖的方式則會增加軟件的復雜度,也會降低系統的性能。

如果把聚合比作組織,那聚合根就是這個組織的負責人。聚合根也稱為根實體,它不僅是實體,還是聚合的管理者。

首先它作為實體本身,擁有實體的屬性和業務行為,實現自身的業務邏輯。

其次它作為聚合的管理者,在聚合內部負責協調實體和值對象按照固定的業務規則協同完成共同的業務邏輯。

最后在聚合之間,它還是聚合對外的接口人,以聚合根 ID 關聯的方式接受外部任務和請求,在上下文內實現聚合之間的業務協同。也就是說,聚合之間通過聚合根 ID 關聯引用,如果需要訪問其它聚合的實體,就要先訪問聚合根,再導航到聚合內部實體,外部對象不能直接訪問聚合內實體。

怎樣設計聚合?

DDD 領域建模通常采用事件風暴,它通常采用用例分析、場景分析和用戶旅程分析等方法,通過頭腦風暴列出所有可能的業務行為和事件,然后找出產生這些行為的領域對象,并梳理領域對象之間的關系,找出聚合根,找出與聚合根業務緊密關聯的實體和值對象,再將聚合根、實體和值對象組合,構建聚合。

下面我們以保險的投保業務場景為例,看一下聚合的構建過程主要都包括哪些步驟。

img

第 1 步:采用事件風暴,根據業務行為,梳理出在投保過程中發生這些行為的所有的實體和值對象,比如投保單、標的、客戶、被保人等等。

第 2 步:從眾多實體中選出適合作為對象管理者的根實體,也就是聚合根。判斷一個實體是否是聚合根,你可以結合以下場景分析:是否有獨立的生命周期?是否有全局唯一 ID?是否可以創建或修改其它對象?是否有專門的模塊來管這個實體。圖中的聚合根分別是投保單和客戶實體。

第 3 步:根據業務單一職責和高內聚原則,找出與聚合根關聯的所有緊密依賴的實體和值對象。構建出 1 個包含聚合根(唯一)、多個實體和值對象的對象集合,這個集合就是聚合。在圖中我們構建了客戶和投保這兩個聚合。

第 4 步:在聚合內根據聚合根、實體和值對象的依賴關系,畫出對象的引用和依賴模型。這里我需要說明一下:投保人和被保人的數據,是通過關聯客戶 ID 從客戶聚合中獲取的,在投保聚合里它們是投保單的值對象,這些值對象的數據是客戶的冗余數據,即使未來客戶聚合的數據發生了變更,也不會影響投保單的值對象數據。從圖中我們還可以看出實體之間的引用關系,比如在投保聚合里投保單聚合根引用了報價單實體,報價單實體則引用了報價規則子實體。

第 5 步:多個聚合根據業務語義和上下文一起劃分到同一個限界上下文內。

這就是一個聚合誕生的完整過程了。

聚合的一些設計原則

我們不妨先看一下《實現領域驅動設計》一書中對聚合設計原則的描述,原文是有點不太好理解的,我來給你解釋一下。

1. 在一致性邊界內建模真正的不變條件。聚合用來封裝真正的不變性,而不是簡單地將對象組合在一起。聚合內有一套不變的業務規則,各實體和值對象按照統一的業務規則運行,實現對象數據的一致性,邊界之外的任何東西都與該聚合無關,這就是聚合能實現業務高內聚的原因。

2. 設計小聚合。如果聚合設計得過大,聚合會因為包含過多的實體,導致實體之間的管理過于復雜,高頻操作時會出現并發沖突或者數據庫鎖,最終導致系統可用性變差。而小聚合設計則可以降低由于業務過大導致聚合重構的可能性,讓領域模型更能適應業務的變化。

3. 通過唯一標識引用其它聚合。聚合之間是通過關聯外部聚合根 ID 的方式引用,而不是直接對象引用的方式。外部聚合的對象放在聚合邊界內管理,容易導致聚合的邊界不清晰,也會增加聚合之間的耦合度。

4. 在邊界之外使用最終一致性。聚合內數據強一致性,而聚合之間數據最終一致性。在一次事務中,最多只能更改一個聚合的狀態。如果一次業務操作涉及多個聚合狀態的更改,應采用領域事件的方式異步修改相關的聚合,實現聚合之間的解耦(相關內容我會在領域事件部分詳解)。

5. 通過應用層實現跨聚合的服務調用。為實現微服務內聚合之間的解耦,以及未來以聚合為單位的微服務組合和拆分,應避免跨聚合的領域服務調用和跨聚合的數據庫表關聯。

上面的這些原則是 DDD 的一些通用的設計原則,還是那句話:“適合自己的才是最好的。”在系統設計過程時,你一定要考慮項目的具體情況,如果面臨使用的便利性、高性能要求、技術能力缺失和全局事務管理等影響因素,這些原則也并不是不能突破的,總之一切以解決實際問題為出發點。

總結

在這里總結下聚合、聚合根、實體和值對象它們之間的聯系和區別。

聚合的特點:高內聚、低耦合,它是領域模型中最底層的邊界,可以作為拆分微服務的最小單位,但我不建議你對微服務過度拆分。但在對性能有極致要求的場景中,聚合可以獨立作為一個微服務,以滿足版本的高頻發布和極致的彈性伸縮能力。

一個微服務可以包含多個聚合,聚合之間的邊界是微服務內天然的邏輯邊界。有了這個邏輯邊界,在微服務架構演進時就可以以聚合為單位進行拆分和組合了,微服務的架構演進也就不再是一件難事了。

聚合根的特點:聚合根是實體,有實體的特點,具有全局唯一標識,有獨立的生命周期。一個聚合只有一個聚合根,聚合根在聚合內對實體和值對象采用直接對象引用的方式進行組織和協調,聚合根與聚合根之間通過 ID 關聯的方式實現聚合之間的協同。

實體的特點:有 ID 標識,通過 ID 判斷相等性,ID 在聚合內唯一即可。狀態可變,它依附于聚合根,其生命周期由聚合根管理。實體一般會持久化,但與數據庫持久化對象不一定是一對一的關系。實體可以引用聚合內的聚合根、實體和值對象。

值對象的特點:無 ID,不可變,無生命周期,用完即扔。值對象之間通過屬性值判斷相等性。它的核心本質是值,是一組概念完整的屬性組成的集合,用于描述實體的狀態和特征。值對象盡量只引用值對象。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/719221.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/719221.shtml
英文地址,請注明出處:http://en.pswp.cn/news/719221.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

47、WEB攻防——通用漏洞Java反序列化EXP生成數據提取組件安全

文章目錄 序列化和反序列化的概念: 序列化:把java對象轉換成字節流的過程;反序列化:把字節流恢復為java對象的過程。 對象的序列化主要有兩種用途: 把對象的字節流永久的保存在硬盤上,通常存放在一個文件…

網絡分析工具簡介及匯總

文章目錄 一、網絡分析工具軟件是什么二、網絡分析工具軟件作用三、常見的網絡分析工具 一、網絡分析工具軟件是什么 網絡分析工具軟件是一種用于捕獲、分析和解釋網絡數據包的工具。它們可以直接從網絡接口上捕獲數據包,并提供詳細的信息和統計數據,以幫…

xss.haozi.me:0x0B

<svg><script>(1)</script>

洛谷 B3620 x 進制轉 10 進制

題目描述 給一個小整數 x 和一個 x 進制的數 S。將 S 轉為 10 進制數。對于超過十進制的數碼&#xff0c;用 A&#xff0c;B&#xff0c;…… 表示。 輸入格式 第一行一個整數 x; 第二行一個字符串 S。 輸出格式 輸出僅包含一個整數&#xff0c;表示答案。 輸入輸出樣例…

【JavaScript】面試手撕淺拷貝

【JavaScript】面試手撕淺拷貝 引入 淺拷貝和深拷貝應該是面試時非常常見的問題了&#xff0c;為了能將這兩者說清楚&#xff0c;于是打算用兩篇文章分別解釋下深淺拷貝。 PS: 我第一次聽到拷貝這個詞&#xff0c;有種莫名的熟悉感&#xff0c;感覺跟某個英文很相似&#xff…

Linux文本處理三劍客:awk(內置函數詳解筆記)

Linux系統中&#xff0c;AWK 是一個非常強大的文本處理工具&#xff0c;它的內置函數使得對文本數據進行處理更加高效和便捷。 本文將介紹 AWK 內置函數的幾種主要類型&#xff1a; 算數函數字符串函數時間函數位操作函數其他常用函數 我們將使用一個示例文本文件來演示這些函…

局域網如何搭建服務器?

隨著網絡的普及和應用場景的不斷拓展&#xff0c;局域網搭建服務器逐漸成為大家關注的話題。在日常生活和工作中&#xff0c;我們經常需要通過局域網和互聯網進行文件共享、資源訪問等操作&#xff0c;而搭建服務器則是實現這些功能的重要手段之一。本文將針對局域網搭建服務器…

SwiftUI 如何在運行時從底層動態獲取任何 NSObject 對象實例

概覽 眾所周知,SwiftUI 的推出極大地方便了我們這些禿頭碼農們搭建 App 界面。不過,有時我們仍然需要和底層的 UIKit 對象打交道。比如:用 SwiftUI 未暴露出對象的接口改變視圖的行為或外觀。 從上圖可以看到,我們 SwiftUI 代碼并沒有設置視圖的背景色,那么它是怎么變成綠…

vscode 本地/遠程添加python解釋器

文章目錄 1. 背景2. 增加python解釋器 1. 背景 我們在使用 vscode 去遠程調試代碼時&#xff0c;如果環境存在多個 Python 版本&#xff08;如用 conda 管理&#xff09;&#xff0c;沒有選擇正確的 Python 解釋器會導致少包、庫不適配等各種問題 2. 增加python解釋器 windo…

鴻蒙系統適配的流程

鴻蒙系統適配的流程通常涉及以下關鍵步驟&#xff0c;以下是鴻蒙系統適配的一般流程&#xff0c;具體流程可能會根據項目的具體需求和開發團隊的情況進行調整和優化。北京木奇移動技術有限公司&#xff0c;專業的軟件外包開發公司&#xff0c;歡迎交流合作。 1. 準備工作&#…

超越脆弱:用否定法策略優化考研復試準備

很多情況下,你的想象力會為當前的世界添加一些東西。很抱歉,我會在本章中告訴你,這種方法完全是落后的方法:根據脆弱性和反脆弱性的概念,嚴格來說,正確的想象就是從未來中排除或削減不屬于未來的東西,采用否定法,脆弱的事物終將破碎。——《反脆弱:從不確定性中獲益》…

盤點:國家智能算力中心

文章目錄 1. Main2. My thoughtsReference 1. Main 按照《中國算力白皮書&#xff08;2022年&#xff09;》的定義&#xff0c;算力主要分為四部分&#xff1a;通用算力、智能算力、超算算力、邊緣算力。通用算力以CPU芯片輸出的計算能力為主&#xff1b;智能算力以GPU、FPGA、…

【一起學習Arcade】(6):屬性規則實例_約束規則和驗證規則

一、約束規則 約束規則用于指定要素上允許的屬性配置和一般關系。 與計算規則不同&#xff0c;約束規則不用于填充屬性&#xff0c;而是用于確保要素滿足特定條件。 簡單理解&#xff0c;約束規則就是約束你的編輯操作在什么情況下可執行。 如果出現不符合規則的操作&#…

CGI中使用Cookie

文章目錄 CGI中使用Cookiecookie的語法設置Cookie獲取Cookie檢索Cookie信息文件上傳實例1. 創建HTML表單2. 編寫Python腳本處理上傳 文件下載對話框 CGI中使用Cookie 在CGI&#xff08;Common Gateway Interface&#xff09;中使用Cookie涉及設置和獲取由Web服務器發送到瀏覽器…

java導出多個xml文件的壓縮zip

代碼&#xff1a; // 設置響應頭response.setCharacterEncoding("UTF-8");//設置響應的字符編碼為UTF-8response.setContentType("application/octet-stream");//設置響應的內容類型為二進制流&#xff0c;通常用于文件下載。response.setHeader("Con…

解釋一下前端框架中的虛擬DOM(virtual DOM)和實際DOM(real DOM)之間的關系。

聚沙成塔每天進步一點點 ? 專欄簡介 前端入門之旅&#xff1a;探索Web開發的奇妙世界 歡迎來到前端入門之旅&#xff01;感興趣的可以訂閱本專欄哦&#xff01;這個專欄是為那些對Web開發感興趣、剛剛踏入前端領域的朋友們量身打造的。無論你是完全的新手還是有一些基礎的開發…

leetcode日記(36)全排列

想思路想了很久……思路對了應該會很好做。 我的思路是這樣的&#xff1a;只變化前n個數字&#xff0c;不斷增加n&#xff0c;由2到nums.size()&#xff0c;使用遞歸直到得到所有結果 代碼如下&#xff1a; class Solution { public:vector<vector<int>> permut…

正信法律:借款糾紛的民事起訴狀怎么寫

在借款糾紛中&#xff0c;當協商無果時&#xff0c;訴諸法律成為債權人追回債務的有效途徑。而民事起訴狀作為啟動訴訟程序的法律文書&#xff0c;其撰寫質量直接關系到案件的受理與判決。本文旨在簡明扼要地闡述如何撰寫一份規范的借款糾紛民事起訴狀。 起訴狀需包含以下幾個關…

阿克曼轉向車型導航末段位姿調整控制

1目標 分析RPP算法時控制器算法學習1-RPP受控純追蹤算法發現,在終點時如果角度還有較大偏差,該算法無法進行很好的調整,故開始嘗試在末端接近目標點時,用自己的控制算法去調整位姿,姑且命名為TEA算法(Target-End-Adjust Algorithm for Ackermann) 2控制思路 step1. 將…

【百度】B端數據中臺組_大數據研發工程師(J68899)

北京市技術3人2023-12-14 工作職責&#xff1a; 負責百家號和Feed相關大數據的基礎建設負責數倉引擎、Spark底層的優化和基礎建設為百度系APP相關業務分析及策略優化提供大數據支持參與百度系APP日志數據架構開發和APP端性能分析平臺建設 職責要求&#xff1a; 本科及以上學…