Thursday, March 25, 2021

[Domain-Driven Design] 用 Ubiquitous Language 溝通

現代軟體的複雜特性,沒有一個人或是團隊可以單獨掌握所有的知識細節,甚至連領域專家的理解都可能有所缺漏。

為了要盡可能獲取知識的全貌,我們會將溝通所得到的知識提煉出來達成共識後,建立 Ubiquitous Language (通用語言),減少溝通的成本。


(開發與商業團隊交流後,發現其實系統需要的是訂單、商品與會員管理三個功能,並且規定大家要用 Ubiquitous Language 互相溝通)

整個過程可以參考這張圖:


至於該怎麼與領域專家做到有效溝通並建立 Ubiquitous Language ,會在之後的章節介紹 Event Storming 事件風暴。

兩大設計模式類別

與領域專家討論出 Ubiquitous Language 後,就可以開始套用 DDD 的各種設計模式,而這些模式大致上可以分為兩類:Strategic Design (戰略設計) 與 Tactical Design (戰術設計)。

  • Strategic Design 戰略設計:利用與領域專家溝通的結果,拆分問題成數個子領域後,定義解決方案(系統)的邊界與關係。
  • Tactical Design 戰術設計:又稱 Model-Driven Design ,在 Strategic Design 的一個個解決方案邊界內,用一系列設計模式寫程式實踐業務邏輯。

簡單來說, Strategic Design 打嘴砲設計大架構與方向, Tactical Design 捲起袖口開始寫程式。

以上兩個模式就是之後本系列的主軸

[Domain-Driven Design]-Introduction

程式設計的重點並不在於使用哪個框架技術或優化幾個百分比,而是在於是否能

忠實解決業務的需求 

Eric Evans 發明了領域驅動設計 (Domain-Driven Design ,之後簡稱 DDD) ,提倡開發人員也需要與領域專家合作以獲取足夠的業務知識 (business knowledge),接著將領域知識與業務邏輯注入進程式碼模型之中,達成「程式即設計、設計即程式」的境界

運用了這套模式

一來程式碼功能一目瞭然

二來可以有效保護我們的業務邏輯不被竄改,甚至可以適應未來業務邏輯的變化與成長

 

DDD 最大的價值之一就是把將商業領域的知識映照到程式碼中,解放「程式歸程式,業務歸業務」的傳統思維,在過程中甚至可以打破商業團隊與工程團隊間的藩籬,甚至會讓人感覺到:

開發其實是一場學習的過程,程式碼只是過程的副產物。

DDD 是什麼

 介紹 DDD 是什麼之前,我們先定義領域 (Domain) 是什麼。廣泛來說, domain (knowledge) 是指「一塊知識的範圍」。實務上,就是指「你工作上所需的一切知識集合」,包含「問題」以及「解決方案」。

由此可見, DDD 是一種基於領域知識來解決複雜業務問題的軟體開發方法論。

它有以下三個重點:

跟領域專家 (domain expert) 密切合作來定義出 domain 的範圍及相關解決的方案。

切分領域出數個子領域,並專注在核心子領域。

透過一系列設計模式,將領域知識注入進程式模型 (model) 中。 

https://ithelp.ithome.com.tw/articles/10216792

http://teddy-chen-tw.blogspot.com/2019/06/5aggregate.html

複雜軟件設計之道-領域驅動設計

1. 領域驅動設計基礎

2. 領域驅動戰略設計

3. 聚合設計

4. 實體和值對象

5. CQRS 架構

6. 事件溯源

7.  貨物運輸系統


Event Storming

Event Storming 概覽

Event Storming 是一個透過高度互動的方式,將企業或系統的商業流程視覺化,最終協助建立軟體模型的工作坊。簡單來說就是探索功能、找出盲點、建立共識。

跑完後,與會者們都將會獲得:

  • 對於商業流程的共同理解,包含名詞使用、責任範圍、使用者體驗等。
  • 一份整體流程的概覽圖 (Big Picture),事後也可以數位化製成文件。
  • 可以找出商業流程中的核心價值、風險與機會。
  • 一個導入 DDD 的好起點。

適合的對象

如果你的工作流程出現以下症狀,請儘早服用 Event Storming:

  • 規格書的知識瑣碎、難以理解、沒有明確架構。
  • 討論時常陷入細節的爭論,或是過多的技術詞彙讓主題失焦。
  • 開發到一半還在理解功能在幹嘛。
  • 等到上線了才發現一堆 Bug。

Event Storming 應用場景

使用的場景點從巨觀到微觀:


橘色(正方形 76*76):Event 事件 (過去式表示)

說故事的方式,是否順


Event Storming Part 1 - 簡介與事前準備 

Event Storming Part 2 - 風暴展開

Event Storming Part 3 - 軟體設計

Event Storming Part 4 - 可以做的更好

Monday, March 8, 2021

[複雜軟件設計之道-領域驅動設計] - 貨物運輸系統

[複雜軟件設計之道-領域驅動設計] - 事件溯源

[複雜軟件設計之道-領域驅動設計] - CQRS 架構

[複雜軟件設計之道-領域驅動設計] - 實體和值對象


[複雜軟件設計之道-領域驅動設計] - 聚合設計

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】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能