Monday, March 8, 2021
[複雜軟件設計之道-領域驅動設計] - 聚合設計
3.1 聚合設計的概念
3.1.1 高聚合低關聯
3.1.2 聚合的邏輯一致性
3.2 設計聚合的幾種方法
3.2.1 改變主謂賓順序
3.2.2 根據領域事件設計聚合
3.2.3 根據單一職責設計聚合
3.2.4 按時間邊界設計聚合
3.2.5 通過事務邊界設計聚合
3.2.6 通過 ER 模型設計聚合
3.3 實例解析:訂單系統中的聚合設計
業務邏輯
整體物件規則
[複雜軟件設計之道-領域驅動設計] - 領域驅動戰略設計
領域驅動戰略設計分 2 部分
1. 戰略設計 (策略設計)
宏觀角度著眼於領域的分析設計,屬於系統分析階段,注重如何從有界上下文中尋找領域模型
由有界上下文、無所不在的語言和上下文映射組成
2. 戰術設計
設計代碼階段,使用聚合、實體、值對象等對象類型概念表達領域模型。
2.1 有界上下文
上下文: 一個語境、語意
每個「有界上下文」都有自己一套「統一語言」
有界上下文是根據邏輯一致性劃分軟件的模式。
有界 上下文是人根據客觀事物中的一致性邏輯去劃分軟件
注意上下文不 是軟件中的模塊,而是一個微服務
有界上下文是主觀和客觀結合的一套方法,客觀事物是一直變化 運動的,沒有靜止的一條邊界,DDD 中的邊界是人為的一種劃分,但是這種劃分也不是隨意主觀的,而是根據客觀事物的邏輯來劃分,應保持邏輯一致性
有界上下文是指在空間或時間上有邊界的一段環境背景,它確定 了每個模型的適用範圍,模型體現了這個範圍內的邏輯一致性。
2.1.1 統一語言:統一項目中的交流語言
語言是DDD的核心,DDD使用語言來表達想法、探索問題並定義 解決方案。
2.1.2 如何發現有界上下文和統一語言?
1)首先,可以通過畫圖的方式去發現。畫圖簡單扼要,可以在白板上表達自己的業務領域,不要擔心它們是否為正式設計;也可以事 先使用UML工具畫出UML圖來表達自己的理解。最好的軟件是沒有軟 件,代碼實現非常昂貴,因為代碼實現涉及太多細節,一旦整個思考 方向發生變化,所有的細節就要全部摧毀,重新來一次,而使用畫圖 方式則很便宜。
2)其次,通過專家小組會議創建詞彙表,定義所有所需術語的詞 彙表。注意這個詞彙表應該是在徵詢意見的基礎上,切不可由上而下 強行推廣。改變人們的語言習慣很難,如同改掉方言口音一樣。
3)最後,可以通過事件風暴的方式發現統一語言和有界上下文, 領域專家和開發人員可以實現對業務流程迭代學習的快速循環,從而 促進統一語言的發展。
在發現有界上下文和統一語言的過程中可以發現業務規則,而業務規則是一種業務邏輯的強有力的約束表示,業務規則的強邏輯性更 容易發現和表達。
很多公司和組織已經意識到這個問題,因此會將一個大型項目劃 分為一個個小項目,分配給一個個小團隊去完成。
關鍵問題是,依據 什麼標準將一個大型項目劃分為小項目?
答案是根據領域邊界去劃分,一個子域對應一個團隊;也可以按照解決方案中的有界上下文去 劃分。子域劃分比較明顯,但是有界上下文劃分就可能不是那麼明 顯,這需要多年經驗或比較強的分析能力,當然如果管理效率高,就 可以根據上下文的調整而及時調整團隊人員和組織結構。
2.1.3 有界上下文之間的關係
有界上下文之間關係的處理基本原則是以鬆耦合、解耦合為主, 因為不同的有界上下文有不同的團隊、代碼庫、技術體系,如果兩兩 之間過於耦合,就會發生兩個團隊經常在一起開會溝通、影響效率的 情況,也可能是兩個上下文的邊界劃分得不清晰,需要重新審視。
如果兩兩 之間過於耦合,就會發生兩個團隊經常在一起開會溝通、影響效率的情況,也可能是兩個上下文的邊界劃分得不清晰,需要重新審視。
一個大型系統還是需要集成不同的上下文
上下文之間的關係有很多類型,這裡只討論常用的幾種
1)共享內核:指兩個有界上下文共同使用一份代碼內核(例如一 個庫)。這種方式已經很少使用,因為共享一份代碼,如同共享一個 數據庫一樣,單點風險大。
2)開放主機服務也是一種上下文關係的映射,也稱為上下游關係 映射或API調用,一個上下文通過RPC等同步方式調用另外一個上下文 的API,調用者是被調用者的客戶端。
這種方式在如今的微服務架構中比較普及。
兩個上下文通過服務 接口耦合在一起,如果一方服務接口改動,那麼調用這個服務接口的 另一方代碼也需要改動,這就要求團隊之間溝通合作緊密(康威定律),而微服務的設計目標是團隊之間應該儘量少溝通、少開會,因 為這種跨團隊溝通的效率是很低的,個別程序員之間私下商量後可能 無意中做出影響整個數據設計原則的事情,而架構師無法參與討論, 無法瞭解具體的實現情況有沒有問題。
這種模式在新舊上下文系統之間使用時,需要引入防腐層,防止 兩個上下文系統直接耦合,舊的上下文系統會影響新上下文系統的代 碼編寫思路,因此必須引入第三層解耦。
3)發佈/訂閱模式:一個有界上下文是發佈者,另外一個有界上下 文訂閱這個發佈者,當發佈者有事件發生時,及時將事件發佈給所有 訂閱者(這非常類似微信,當你訂閱公眾號以後,它們有更新時會及 時通知你),這樣兩個上下文之間不再互相依賴,而是隻依賴事件或 消息,使得兩者之間實現最大化的鬆耦合,這也是集成模式的主要實 現方式。
4)發佈的語言(Published Language):兩個有界上下文中的模型 需要一種共同語言進行相互翻譯轉換,如同兩個不同語言的人常常需 要選擇英語進行交流一樣。所謂發佈的語言是指一種大家都能夠理 解、解釋的語言,很多行業基於XML建立各自行業的標準語言,如美 國健康醫療領域的HL7標準,為支持臨床電子健康信息的交換、集成、 共享和檢索提供了全面的框架和相關標準。
2.1.4 核心子域、支持子域與通用子域
什麼是子域?為了實現公司最終的大目標,必須在不同的小目標 範圍中工作,它們被稱為子域,因為它們自身並不足以使公司取得成 功,合起來以後共同構成了公司的業務領域。
子域分為核心子域、支持子域和通用子域。
1)核心子域:這是必須盡最大努力的地方,正是它使公司發揮作 用,為公司帶來價值,使公司在競爭中脫穎而出。它是最重點的地 方,業務策略和規則的重點實施地。
2)支持(輔助)子域:是核心子域的輔助支持,介於核心與通用 之間,如果沒有它,核心子域無法成功,因此,它也是非常重要的; 需要內部開發或外包,因為沒有現成的解決方案來實現。
Page 76
2.2 按時間線發現有界上下文
2.3 通過領域故事或流程發現有界上下文
2.4 通過事件風暴會議發現有界上下文
2.5 業務平台與中台設計
2.6 總結與拓展
[複雜軟件設計之道-領域驅動設計] - 領域驅動設計基礎
1.1 領域驅動設計的起源與發展
DDD 是一種複雜軟件如何快速應對各種變化的解決之道。
1.1.1 程序員為難之處
軟件開發初期,有時沒有足夠時間收集所有的需求,即使收集,也不是從軟件角度去描述的。
需求帶有個人知識偏見和邏輯漏洞。
有些人開玩笑的說,程序員其實不是在編寫代碼,而是在摸索業務領域知識。
1.1.2 技術負債與軟件質量
技術負債就像技術前進途中的累贅一樣,會像滾雪球那樣越滾越大,不斷拖延增加新功能的步伐,最終可能無法再為系統添加新功能。因此,技術負債的存在是導致軟件質量下降的重要原因。軟件質量下降以後,系統難以維護和修復,就會導致項目失敗或者必須重寫代碼。
一個都不修復系統反而能正常運行;修復Bug時牽一動百,修改一處卻引起其他地方的連鎖故障反應……這些都是軟件質量低下的外在表現。
那麼如何降低技術負債呢?這裡存在一個適度問題。首先,代碼越多,複雜性越高,技術負債肯定越高,那麼就需要惜墨如金。有時為了寫出正確可運行的簡潔代碼,可能要刪除數十倍的代碼,但也不是代碼越少越好。有的代碼只是考慮功能的實現,沒有考慮到功能的對接或擴展,那麼當需要對功能實現擴展時,就發現難以下手,甚至需要採取黑客破解的方式強行入侵修改,這些都是原來代碼過於簡單僵化的表現。
1.1.3 ER 數據建摸與面向對象建模
ER數據建模 (實體-關係模型)的優點
ER模型往往依賴於數據庫技術,甚至與後者非常緊密地耦合在一起,雖然帶來了效率的提升,但是高效率不代表高質量,而軟件高質量卻能帶來高效率。
實體-關係模型的缺點
遺失業務上下文
依賴資料庫技術
物件導向的優點
物件導向的缺點
學習曲線較高
不適用所有類型的問題
分析和設計落差很多
1.1.4 DDD 的誕生和發展
DDD是有關語言的建模技術,需要對語言有一定敏感性,有一定文科背景再結合邏輯推理能力則非常適合。
DDD需要對語言進行主謂賓分類,然後捕捉其中的謂語動詞或行為,將這些發生的動作抽象為命令模型,將發生過的事實抽象為領域事件模型,用領域事件替代狀態分析,例如音樂播放器有三種狀態:停止、播放、暫停,這是從狀態名詞角度分析的,而從領域事件角度分析則有:已停止、已播放和已暫停。當然中文中也常說“已停止狀態”,將事件和狀態混合在一起,其實兩者可以互相替代,只是動詞和名詞的區別。
領域驅動設計全貌
複雜性問題
1.2 領域驅動設計的特點
1.2.1 發現和理解問題
1.2.2 領域即邊界
BC 包含 Ubiquitous Language
名稱是認識萬物的第一步,從“無名”跨越到“有名”,其中的重要一步就是劃分邊界。因此,“名稱”和“邊界”兩者是互為聯繫的,當確定事物的邊界後才能給它命名,也可從名稱本身大概判斷其邊界或作用範圍。
編程語言中有一個術語稱為“作用域”,英文為scope(其實也是邊界的意思),它是指變量的作用邊界是多大,從哪裡開始、從哪裡結束,如果作用域是函數方法內,那麼就是從函數方法開始執行到其結束這段範圍內。
語言界限
科學上下文 蕃茄 -> 水果
享飪上下文 蕃茄 -> 蔬菜
1.2.3 解決複雜性
1.2.4 新的數據結構設計方式
1.2.5 需要注重產品的程序員
1.3 領域驅動設計的難點
1.3.1 業務策略和業務規則
1.3.2 統一語言與有界上下文
1.3.3 領域摸型的提煉
1.4 領域驅動設計的應用場景
1.4.1 哪些應用不適合
太簡單的系統
傳統 CRUD 系統
演示系統或教學案例
尚未理解領域的系統
1.4.2 適合微服務架構
n8n index
【n8n免費本地端部署】Windows版|程式安裝x指令大補帖 【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能
-
模板直到实例化时才生成代码,所以获得模板代码编译错误的时机较晚。编译器在3个阶段报告错误: 1. 第一阶段是编译模板本身时,此时一般错误很少,只是 检查语法错误 ,不检查依赖于类型的代码 第二个阶段是遇到使用模板时,对函数模板调用编译器会 检查实参数目是否正确 。还要检查 参数类...
-
编译器遇到一个模板定义时,并不生成代码。只有当实例化模板时编译器才生成代码。 当调用一个函数时, 编译器只需要掌握函数的声明,函数定义不必已经出现 ,即使不定义,编译也会通过, 最终会在链接时才发现undefined symbol这个熟悉的错误 。 但对于模板来说却不同,生成一...
-
iostream 标准库,它提供了 cin 和 cout 方法分别用于 从标准输入读取流和向标准输出写入流 。 如何从 文件读取流和向文件写入流 。这就需要用到 C++ 中另一个标准库 fstream,它定义了三个新的数据类型: 数据类型 描述 ofstream 该数据类型表...
