為什么需要DDD?
沒有實施DDD的情況下,我們經常會遇到什么問題?
開發人員熱衷于技術而不是深入了解業務。這是技術人員的職責使然,一個不高級的開發,通常他的業務經驗不重要;一個高級的開發,通常因為競業,也無法繼續干類似的業務。所以開發人員對業務天然的沒有足夠的興趣。但是開發過程中,對業務不夠熟悉,很容易發現開發做了半天得到的,并不是用戶和產品想要的;或者下次再有需求的時候,技術上的改動特別大,成本很高。
業務協作不暢,一個需求提到好幾個團隊都能做,但是開發過程你推我我推你,需求一拖再拖,有時候項目中期還得找其他團隊求資源。
對項目工時的估計占用了不少精力,還不準確。估時這件事能成為管理層和開發人員之間的拉鋸戰。
服務之間緊耦合,牽一發而動全身,一個非核心的業務抖一抖,客戶都說沒法用。
那么 DDD 怎么解決這些問題?
找到邊界:讓設計系統的人知道一個業務的邊界在哪里。只有知道邊界在哪里,才能在需求到來的時候,輕易地找到相關團隊,各個業務之間也才能真正解耦,降低非核心功能對核心功能的影響。
知識獲取:保證設計系統的人能夠低成本地了解業務,讓大家在怎么做,怎么驗收方面達成一致。在此基礎上,還可以免費得到一款評估時間的工具,項目的交付就更有把握。
但是在此我要多說一下,我們現實實踐中已經有一部分領域概念的影子了——誰能說他不知道自己的組織是干啥的?或者說哪個組織沒有業務重心呢?可是為什么大家沒有獲得上邊說的這諸多好處呢?那是因為DDD實踐過程中巧妙地將設計模型落地成為開發的模型,讓需求方和實施方說一種語言,才能真正跨越了需求和實現之間的鴻溝。所以實踐DDD,絕不是只有開發寫寫代碼就行,而是要跟產品,設計以及領域專家一起完成設計,才能得到DDD的好處。
DDD是什么呢?
我們先看幾個*DD:
TDD 驅動測試開發
BDD 行為驅動開發
DDD 領域驅動設計
前面幾個概念落腳點都是開發,而DDD,是設計。
它有三個關鍵詞:領域,驅動,設計。領域,是要探索業務的邊界;驅動,表示前者是后者的決定性因素;設計,包括產品設計,UIUE設計,軟件設計。它不僅僅是開發架構的方案,而是完整的解決方案實施思路。正是因為它是完整的方案,才能讓領域專家,產品和研發真正在同一個角度去思考和溝通,避免推諉扯皮,含糊不清。
那么怎么做DDD呢?
實施DDD一般有兩步,并且需要開發,產品和領域專家的通力合作。為了實施速度有所保障,還有一些項目加速和項目管理工具:
戰略設計
戰術設計
戰略設計
戰略設計可以說是搭建了業務思想上的框架。這個階段要做這么幾件事:
使用限界上下文分離領域模型
在限界上下文發展通用語言
使用子域處理遺留系統
使用上下文映射來集成多個限界上下文
限界上下文分離領域模型
限界上下文這個名字乍一看,每個字我都認識,但是這個詞是啥意思?原文說它是語義和語境上的邊界,我的理解是,它是在描述組織交付出來,面向客戶的交付邊界。如果是在SaaS場景,一個限界上下文應該是一個獨立交付的軟件;在PaaS場景,它應該說的是一個獨立售賣的模塊。它的含義是找到一個邊界,要把這個邊界以外的當成是無法改變的客觀環境,不要幻想這個邊界以外的人會配合你一起完成交付。那這一步設計就很好理解了,就是找到你業務對外承諾的邊界,你要發展的業務在這個邊界內,而不在此之外。如果你是對內交付的系統,那么你對其他同事交付的業務邊界,就是你的業務限界上下文。
一個組織里,最核心的限界上下文被稱為核心域。通常除了它,還有通用子域和支撐子域。通用子域是很成熟的業務,通常可以外包或者購買現成的解決方案,比如搜索子域可以通過ES來支持;支撐子域通常沒有現成產品,但是它沒有核心域重要,因此也可以一定程度的外包,避免在核心域之外浪費資源,比如大多數公司的數據庫中間件是在開源產品上做了一些定制開發和維護。
限界上下文這個概念的目的是為了在業務擴展的時候,防止向領域內注入概念,導致業務變得沒有邊界,糾纏在一起。
在做這一步的時候,DDD要求以領域專家意見為準,正所謂領域驅動嘛。當實施了DDD方法以后,不論是領域專家還是開發,都應該拒絕向領域注入與業務無關的概念,比如存儲方式等。這與我們日常工作從如何存儲開始構建業務系統是完全不同的。只有把這些技術概念放到業務之外,我們的業務核心往往才能足夠集中,易于遷移,而且不論采用什么東西存儲,用什么東西展示,它的邏輯都可以不變。
這個過程中我們通常可以得到這樣一個模型:
限界上下文發展通用語言
當我們有了業務的限界上下文以后,就需要在這個限界上下文中發展一種語言用于表達軟件模型,這個語言就叫做這個限界上下文里的通用語言。它可以是任何計算機語言、人類語言或者圖形,只要能讓團隊內的每個人都能看懂。
通用語言不止是名詞,它應該使用一系列具體的模型場景來描述領域模型。它描述了各種業務組件(不是技術組件)做什么,而不是用例或者用戶故事。
比如微信朋友圈點贊這個場景,通用語言可能是:用戶可以通過點贊,使得某個朋友圈的Feed發出人收到被點贊的通知,達到互動的目的。
但是到這一步,我們怎么能驗證領域模型能與領域專家的心智保持一致呢?那就是為這個模型寫驗收測試,并交由領域專家評估。一種做法是驗收測試采用given-when-then語法(中文可以用假如-當-那么),便于閱讀理解。驗收測試也可以用腦圖,文字來描述,甚至DDD不反對采用單元測試框架寫驗收測試,只要領域專家能夠閱讀并理解寫出的驗收測試。
這一步做完后我們的模型圖形其實沒什么變化,但是現在,開發能夠更充分的了解業務了。
使用子域處理遺留系統 我們代碼不是在真空里運行,它們免不了會跟一些遺留系統打交道,這些遺留系統的邊界并不清晰。因此我們會將遺留系統放到一個子域里,把它們的問題放到我們的設計之外。這一步做完后我們的圖案與之前沒有本質上的區別,無非是多了一點子域。
使用上下文映射來集成多個限界上下文
上下文映射是兩個限界上下文之間的連線,表示了這兩個概念之間的關系,也表示這這兩個概念的通用語言的翻譯。通常來說,不同的限界上下文是不同的團隊在維護,那么此時它也代表著兩個團隊之間合作的關系。
我們常見的映射關系是RPC接口。然而在領域設計里,限界上下文之間使用RPC是有風險的方案,因為會承受網絡風險,還意味著兩個限界上下文之間存在緊耦合。如果系統A阻塞請求系統B,B又請求C,就很容易導致集成火車事故:火車里某一節車廂有問題就會變成整列火車的問題。
最好的限界上下文映射關系采用事件的訂閱,但是這要求領域專家在設計的時候就考慮不同領域之間通知的延遲對于業務的影響,以及如何消除影響。如果不采用DDD的方式,領域專家通常無法意識到領域之間的同步成本,技術人員也很容易一頭撞進集成火車里。這就是我說的:DDD的目標是找到邊界和促進學習知識,不僅僅是開發學習業務,領域專家也是在學習系統的邊界與設計。
戰術設計
在戰術設計階段包括如下設計:
把一些實體和值對象放一起,稱為聚合。
利用領域事件通知相關系統。
聚合怎么設計
一個限界上下文里通常有多個聚合,聚合邏輯上是相對獨立的。怎么理解聚合的概念呢?在DDD實踐中,聚合是事務的邊界;聚合之間并不保證事務,只能用最終一致性。任何需要事務保護的邏輯都應該在一個聚合內。在限界上下文里,將其他聚合能力整合在一起對外提供能力的聚合,被稱為聚合根;其他聚合也被稱為實體。
此外,一個限界上下文里還有值對象,它也代表了某種相對獨立的概念。怎么區分實體和值對象呢?這取決于業務。如果一個名詞,具有多種動詞去操作它,那么它應該是一個實體;如果一個名詞,在系統里只是被傳遞而沒有業務邏輯,那么它就是值對象。
由于聚合是事務的邊界,那么每個聚合在設計階段,最重要的是找到業務的不變性,也就是說,在事務提交前后,數據的約束條件。比如說,你在知乎對一條回答點贊,那么這條回答的點贊數量必須立刻多1,那么點贊的動作和點贊的計數,就應當在一個聚合內。
領域專家必然希望任何事情都能在觸發后立刻完成,所以在溝通的過程中要不斷質疑,如果不實時地做一件事,會不會有問題。甚至可以用一個夸張到顯然無法接受的時間長度來質疑,以促成領域專家對此認真思考。
在聚合被設計出來以后,我們的模型圖看起來會是這樣的:
領域事件怎么設計
我們說聚合之間要采用最終一致性,而通常的做法是采用領域事件實現最終一致性。領域事件的名稱應該采用通用語言命名,才能符合領域專家的心智。完整的時間名詞應該是名詞和動詞構成的,動詞應該是過去時。領域事件的名字和屬性應該能夠完整描述這個事件的含義。
事件里通常至少包含業務動作和其業務參數,也可以增加更多的下游關注的事件信息,避免下游為了完成處理還需查詢。
領域事件會持久保存在專門的數據表中,用來表示領域事件的因果關系。
有一種專門的存儲方式是事件溯源,它不需要存儲數據當前是什么,而是從歷史事件中按順序應用重建,得到當前的數據。這樣寫入時的成本只有校驗后持久化,也沒有增加和刪除的能力。如果事件很多,性能問題很大,也可以加上緩存和快照,優化性能。這種方案通常會與CQRS方案一起做。
進度加速和項目管理工具
在這本小冊子里,Evans提出了兩個工具,分別用于加速設計階段和評估工時。
事件風暴
事件風暴是快速的設計技術,讓領域專家和開發人員都可以參與學習,目的是在有限的時間里盡可能多地完成設計,也就是加速設計階段。
事件風暴要先做如下準備:
邀請領域專家和開發人員
每個成員都應該以開放的心態參與討論,不必追求正確和速度。
各種顏色便利貼,正方形的。一般一個便利貼只會寫幾個詞。
每個人都有黑色的馬克筆。
最好有一面至少10米長的墻并且鋪上白紙。最好建模的幾天時間內保持每次討論的結果一直保留并供下次討論使用。
事件風暴的基本步驟:
在便利貼上寫領域事件,梳理出業務流程,一般是橘色。
創建領域事件強調我們首要關注的是業務流程,而不是數據和結構
把每個領域事件寫在一張便利貼上,應該是動詞的過去式。
把寫好的便利貼按照時間順序放到建模平面上,從左往右逐步發生。
并行發生的領域可以上下排列,不明白時機的事件可以單放在某個單獨的位置。
如果發現了問題點,可以用紅色的便利貼上,并用一段文字解釋是什么問題。
領域事件最終會觸發一個執行的流程,每個流程都應該命名并記錄在淺紫色的便利貼。需要從領域事件畫個箭頭指向這個流程。支隊核心域中非常重要的細粒度事件進行建模。
創建導致領域事件發生的命令,命令應該是指令式的。
創建領域事件的便利貼是淺藍色的。
觸發事件的便利貼放在觸發的事件左邊,會有很多成對出現的命令和事件。但是也有不是命令觸發的事件,比如時間觸發的事件。
如果存在一個執行動作的特定角色,那么可以在命令左下角使用亮黃色的便利貼記錄角色名稱。
命令也可以觸發流程。
在命令和事件之間畫出線條
按照時間順序,將命令和事件的關系處理好
一個命令可以帶來多個事件
把命令和領域事件通過實體、聚合聯系起來。由于建模沒完,因此沒有真正的實體和聚合,而是領域專家思想里的業務概念和概念群。用淡黃色的便利貼來表示聚合,其左下角是命令,右下角是事件。聚合的名字應該是名詞。
在建模平面上畫出邊界和事件流動的箭頭。
識別用戶執行操作所需的各種視圖,以及客戶不同用戶的關鍵角色。
4和5是事件風暴的關鍵。
時間評估工具
時間評估工具是如下的一個經驗表格:
領域內的組件類型 | 簡單 | 適中 | 復雜 |
---|---|---|---|
領域事件 | 0.1 | 0.2 | 0.3 |
命令 | 0.1 | 0.2 | 0.3 |
聚合 | 1 | 2 | 3 |
… |
作者Evans在原始表格里使用的單位是人時,然而根據我的經驗,這個地方用人天還差不多……
這個表格的好處是系統里關于業務的部分都很明確了,雖然時間還是經驗得出的,但是實際上已經相對精細了,而且在領域內的部分,估時會更準確一些, 而且它的復雜度與業務方的預估不會差太多。
常見的DDD誤區:
DDD一定要用微服務?不,其實多個域在同一個進程也沒問題,只要滿足一個聚合在一個事務內保護就沒有問題。
DDD的架構是穩定的?這么問的人一定沒有理解什么叫做領域驅動。當領域發生演化的時候,系統的改變肯定不會小。比如電商系統里收貨地址,可能一開始只是沒有業務意義的值對象,但是后續有了管理,比如家庭,公司,然后反過來繪制畫像,精準推薦……地址有了管理系統,那就不再是值對象了。
但是DDD能保證在每期迭代中,需要做的工作都是最貼合當前需求的,并且當下一迭代到來的時候,做的改造工作量也是各方可以理解的。與之相反的所謂提前規劃,通常會演化為把之后若干迭代的部分放到當前迭代,而且當未來沒有按照預定的方式改變時,這些工作可能還是無效的。
審核編輯:劉清
-
API
+關注
關注
2文章
1485瀏覽量
61816 -
RPC
+關注
關注
0文章
111瀏覽量
11512 -
ddd
+關注
關注
0文章
23瀏覽量
2918
原文標題:走近DDD
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論