領域驅動設計理解&總結 這篇文章主要是通讀《實現領域驅動設計》之後自己的理解和總結(同時也參照一些博文的分析來加深自己的理解); 有些疑問是自定義內容,雖然有自己的理解,但依然感覺較為抽象,後續會通過實踐來理解其中的精妙之處。 ...
領域驅動設計理解&總結 這篇文章主要是通讀《實現領域驅動設計》之後自己的理解和總結(同時也參照一些博文的分析來加深自己的理解);
有些疑問是自定義內容,雖然有自己的理解,但依然感覺較為抽象,後續會通過實踐來理解其中的精妙之處。
領域驅動設計指引
-
領域驅動設計 作為一種軟體開發方法,提供了戰略上(思考方式) 和 戰術上(落地方式) 的建模工具來幫助我們 設計高質量的軟體模型;
-
領域驅動設計 不是關於技術的,而是關於討論、聆聽、理解、發現業務價值 的,目的是將 知識 集中起來,形成 通用語言(Ubiquitous Language);
-
在 領域驅動設計 中,技術也重要,但更重要的是要掌握 領域建模 中更高層次的概念;
領域建模
在領域中構建模型,什麼是 領域模型?
-
關於 某個特定業務領域的軟體模型。通常,領域模型通過 對象模型 來實現,這些對象包含了數據和行為,並且表達了準確的業務含義。
為什麼要使用DDD
Vaughn Vernon(沃恩.弗農) 在他的書里(《實現領域驅動設計》)闡述了很多,總結3點:
-
領域專家、開發者、業務人員等都掌握同樣的軟體知識,大家都使用相同的語言進行交流,每個人互相理解彼此在說什麼;
-
設計就是代碼,代碼就是設計,軟體能夠表達大家所理解的意思;
-
DDD持續關註業務,會產生一些業務價值
-
獲得一個有用的 領域模型;
-
業務得到更準確的 定義和理解;
-
領域專家可以為領域設計做出貢獻;
-
更好的用戶體驗(軟體本身容易上手,減少培訓,提高效率)
-
清晰的模型邊界
-
良好的企業架構
-
敏捷、迭代式和持續建模
-
使用 戰略和戰術工具
-
DDD推進過程中存在的挑戰
-
創建通用語言(所有人達成一致的某個業務術語的統一認知)消耗額外的時間和精力
-
持續的將領域專家引入項目
-
改變開發者 對領域的思考方式
如何使用DDD
DDD 提供了戰略上 和 戰術上 的建模工具來幫助我們 設計高質量的軟體模型,戰略設計 側重於高層次、巨集觀上去劃分和集成限界上下文,而 戰術設計 則關註更具體使用建模工具來細化上下文。
戰略上
-
限界上下文(Bounded Context) 為團隊創建一個建模邊界;
-
成員在邊界內部為特定的 業務領域 創建 解決方案;
-
單個限界上下文中團隊成員共用一套 通用語言(Ubiquitous Language);
-
不同團隊各自負責一個限界上下文,可以使用 上下文映射圖 對限界上下文進行 界分和集成;
戰術上
-
實體(Entity)
-
值對象(Value Object)
-
聚合(Aggregate)
-
領域服務(Domain Service)
-
領域事件(Domain Event)
-
資源庫(Repository)
下邊我針對 戰略和戰術 兩個方面進行講解
DDD之戰略
領域
廣義上講:領域 是一個組織所做的事情以及其中所包含的一切(為某個組織開發軟體時,你面對的就是這個組織的 領域)。
軟體開發中:領域 既可以表示 整個業務系統,也可以表示系統中的 核心域 或者 支撐子域。
在DDD中:領域 被劃分為若幹 子域,領域模型 在 限界上下文 中完成開發。
領域 包括下邊三種 子域:
-
核心域(業務成功的主要促成因素,是企業的核心競爭力,應該給予最高的優先順序、最資深的領域專家和最優秀的開發團隊,實施DDD的過程中主要關註於 核心域);
-
支撐子域(不是核心,對應業務的 某些重要方面,有時我們會創建或者購買某個支撐子域);
-
通用子域(不是核心,但被整個業務系統所使用);
現實世界中的領域
現實世界中的領域包括 問題空間(Problem Space)和 解決方案空間(Solution Space):
-
問題空間:是核心域和其他子域的組合,思考的是 業務面臨的挑戰
-
解決方案空間:一組特定的 軟體模型,包括一個或多個限界上下文,思考的是如何實現軟體(限界上下文 即是一個 特定的解決方案,通過軟體的方式實現解決方案)以 解決這些業務挑戰
限界上下文
-
一個 顯式的邊界(主要是一個語義上的邊界),領域模型便存在於這個邊界之內;每一個模型概念(包括它的屬性和操作)在邊界之內都具有特殊的含義;
-
一個 給定的業務領域 會包含多個限界上下文,想與一個限界上下文溝通,則需要通過顯示邊界進行通信;系統通過確定的限界上下文來進行解耦,而每一個上下文內部緊密組織,職責明確,具有較高的內聚性;
-
一個很形象的隱喻:細胞質所以能夠存在,是因為細胞膜限定了什麼在細胞內,什麼在細胞外,並且確定了什麼物質可以通過細胞膜(引用);
與技術組件保持一致
將限界上下文想象成技術組件是可以的,但是技術組件並 不能來定義(是說不能定義概念?) 限界上下文,有幾種做法:
-
在使用 IntelliJ IDEA 時,一個 限界上下文 通常就是一個工程項目;
-
在使用Java時,頂層包名通常表示 限界上下文中頂層模塊 的名字;
-
一個團隊,一個限界上下文(即便項目按分層架構模塊劃分,團隊依然應該只工作在一個限界上下文中);
上下文映射圖
確定了單個限界上下文之後,有時還需要確定多個限界上下文之間的關係,這時就需要上下文映射圖
一個項目的 上下文映射圖 可以用兩種方式來表示:
-
畫一個簡單的框圖來表示 兩個或多個 限界上下文 之間的 映射關係(該框圖表示了不同的限界上下文在 解決方案空間 中是如何通過集成相互關聯的);
-
通過 限界上下文 集成的源代碼實現來表示;
限界上下文界分和集成
康威定律 告訴我們,系統結構 應儘量與 組織結構 保持一致
-
這裡認為團隊結構(無論是內部組織還是團隊間組織)就是 組織結構,限界上下文 就是 系統結構;
-
因此,團隊結構 應該和 限界上下文 保持一致。
梳理清楚上下文之間的關係,從 團隊內部 的關係來看,有如下好處:
-
任務更好拆分(一個開發人員可以全身心的投入到相關的一個單獨的上下文中);
-
溝通更加順暢(一個上下文可以明確自己對其他上下文的依賴關係,從而使得團隊內開發直接更好的對接);
從 團隊間 的關係來看,明確的上下文關係能夠帶來如下幫助:
-
每個團隊在它的限界上下文中能夠更加明確自己領域內的概念(因為限界上下文是領域的 解決方案空間);
-
對於限界上下文之間發生交互,團隊與限界上下文的一致性,能夠保證我們明確對接的團隊和依賴的上下游;
限界上下文之間的映射關係
-
合作關係(Partnership):兩個限界上下文建立起來的一種 緊密合作關係,要麼一起成功,要麼一起失敗;
-
共用內核(Shared Kernel):兩個限界上下文緊密依賴共用的 部分模型和代碼;
-
客戶方-供應方開發(Customer-Supplier Development):兩個限界上下文有計劃的 產生相互依賴(當兩個團隊處於上下游關係時,下游團隊開發會受到上游開發的影響,上游團隊計劃應該估計下游團隊的需求);
-
遵奉者(Conformist):下游限界上下文只能 盲目依賴 上游限界上下文的現象;
-
防腐層(Anticorruption Layer):一個限界上下文通過轉換和翻譯與其他的限界上下文進行交互;
-
開放主機服務(Open Host Service):定義一種協議,讓其他限界上下文通過該協議對本限界上下文進行訪問;
-
發佈語言(Published Language):兩個限界上下文之間翻譯模型所需要的公用語言,通常與開放主機服務一起使用;
-
另謀他路(Separate Way):兩個限界上下文之間不存在任何關係,尋找另外更簡單、更專業的方法來解決問題;
-
大泥球(Big Ball of Mud):混雜在一起的、邊界非常模糊的限界上下文關係;
領域/上下文劃分的原則
在劃分的過程中,經常糾結的一個問題是:這個模型(概念或數據)看起來放這個領域合適,放另一個也合適,如何抉擇 呢?
-
依據該模型與邊界內其他模型或角色 關係的緊密程度(比如,是否當該模型變化時,其他模型也需要進行變化;該數據是否通常由當前上下文中的角色在當前活動範圍內使用);
-
服務邊界內的 業務能力職責應單一,不是完成同一業務能力的模型不放在同一個上下文中;
-
劃分的子域和服務需滿足 正交原則(模塊的獨立性,領功能變數名稱字代表的自然語言上下文保持互相獨立);
-
組織中 業務部分的劃分 也是一種參考(組織架構,一個業務部門的存在往往有其獨特的業務價值);
簡單打個比方,同一個領域上下文中的模型要保持 近親關係,五福以內,同一血統(業務)。
DDD之戰術
實體
當一個對象由其 唯一的身份標誌 區分、具有可變的特性,這種對象即為實體。
-
實體屬性的驗證可以放在實體內部進行
值對象
將領域概念建模成 值對象 的時候,應該將通用語言考慮在內,這是前提。(為什麼將通用語言考慮在內?值對象 是領域里的一個概念,大家要統一認知,用通用語言作為標準,可以達到共同理解的目的)
構建值對象,要瞭解 值對象 以下的特點:
-
它度量或者描述了 領域中的一件東西;
-
它可以作為 不變數;
-
它將不同的相關的屬性組合成一個 概念整體;
-
當度量和概念改變時,可以用另一個值對象予以 替換;
-
它可以和其他值對象進行相等性 比較;
-
它不會對協作對象造成任何副作用;
在實踐中,需要保證值對象創建後就不能被修改,即不允許外部再修改其屬性(如:在訂單上下文中如果你只關註下單時商品信息快照,那麼將商品對象視為值對象是很好的選擇)
聚合
-
聚合(Aggregate)是一組相關對象的集合,作為一個整體被外界訪問,它由 實體 和 值對象 在 一致性邊界之內 組成,聚合根(Aggregate Root)是這個聚合的根節點。
聚合的設計原則
-
在設計聚合時,我們需要慎重的考慮 一致性
-
關註聚合的 一致性邊界,在一致性邊界之內建模真正的 不變條件(不變條件 指的是業務規則)
-
同一個事務之內不能修改多個 聚合實例
-
-
在邊界之外使用 最終一致性
-
-
設計 小聚合;(“小” 的極端意思是指 一個聚合只擁有全局標識和單個屬性;這種做法不推薦)
-
通過唯一標識來引用其他聚合或實體:當存在對象之間的關聯時,建議引用其唯一標識而非引用其整體對象(如果是外部上下文中的實體,引用其唯一標識或將需要的屬性構造值對象)
註:如果聚合創建複雜,推薦使用 工廠方法 來屏蔽內部複雜的創建邏輯
領域服務
-
當領域中的某個操作過程或轉換過程不是實體或者值對象的職責時,將該操作放在一個單獨的介面中,即 領域服務。
領域服務的特點
-
領域服務 和通用語言一致,表示 無狀態 的操作,它用於實現特定於某個領域的任務;
-
某個操作不適合放在實體(聚合)與值對象上時,適合 領域服務;
-
執行一個顯著的業務操作過程;
-
對領域對象進行轉換;
-
以多個領域對象作為輸入進行計算,結果產生一個值對象;
-
-
領域服務 是用來處理業務邏輯的(我們不能將業務邏輯放到應用層,即使非常簡單,它依然是業務邏輯)
領域事件
-
對 領域中 所發生的事件(領域專家所關心的發生在領域中的一些事件)進行建模,即 領域事件(領域模型 的組成部分)
領域事件的特點
-
領域事件 用來捕獲領域中發生的一些事情,開始使用領域事件時,要 對不同的事件進行定義;
-
“當...時,請通知我” 等等場景
領域事件發佈方法
-
限界上下文內,觀察者模式 是一種簡單高效的發佈領域事件的方法;
-
限界上下文外,利用 消息機制 將本地限界上下文產生的事件發送到 遠程限界上下文 中(我們要保證 所有限界上下文 的最終一致性);
資源庫
-
對領域的存儲和訪問進行統一管理的對象,即 資源庫(Repository);
資源庫的特點
-
通常我們將 聚合實例 存放在資源庫中,之後再通過資源庫獲取相同的實例;
-
通常來說,聚合類型 和 資源庫之間存在著 一對一的關係;
-
當兩個或多個聚合位於同一個對象層級中時,他們可以共用同一個資源庫;
-
註:資源庫和 DAO是不同的,一個DAO主要從資料庫表的角度來看待問題,並且提供 CRUD 操作
DDD之架構
極簡化架構設計主要從下邊三個角度出發:
-
業務架構:根據業務需求設計業務模塊及交互關係;
-
系統架構:根據業務需求設計系統和子系統的模塊;
-
技術架構:根據業務需求決定採用的技術及框架;
DDD的核心訴求 就是能夠讓 業務架構 和 系統架構 形成綁定關係,從而當我們去響應 業務變化 調整業務架構時,系統架構的改變是隨之自發的
這個 業務變化 的結果有兩個:
-
業務架構 的梳理和 系統架構 的梳理是同步漸進的,其結果是劃分出的 業務上下文 和 系統模塊結構 是綁定的;
-
技術架構 是解耦的,可以根據劃分出來的業務上下文的系統架構選擇最合適的實現技術;
架構類型
分層架構
-
所有架構的始祖,支持N層架構系統,將一個應用程式或者系統分為不同的層次
DDD使用的傳統分層架構:
分層架構原則:每層只能與其下方的層發生耦合(分層架構 分為 嚴格分層架構 和 鬆散分層架構)。
嚴格分層架構:每層只能和直接位於其下方的層發生耦合。
鬆散分層架構:任意上方層與任意下方層發生耦合。
六邊形架構
六邊形結構(埠與適配器架構、onion架構):一種具有對稱性特征的架構風格。
為什麼是6邊形?不是4邊形或8邊形?
六邊形架構視角:架構中存在兩個區域,“外部區域”和“內部區域”,外部區域 供給客戶提交輸入,內部區域 獲取持久化數據、對數據進行存儲或轉發。
面向服務架構
服務設計原則
-
服務契約:通過契約文檔,服務闡述自身的目的與功能;
-
松耦合:服務將依賴關係最小化;
-
服務抽象:服務只發佈契約,隱藏內部邏輯;
-
服務重用性:一種服務可被其他所有服務重用;
-
服務自治性:服務自行控制環境與資源以保持獨立性;
-
服務無狀態性:服務負責消費方的狀態管理;
-
服務可發現性:客戶可通過服務元數據來查找服務和理解服務;
-
服務組合性:一種服務可用由其他服務組合而成,不用管其他服務的大小和複雜性如何;