Categories
程式開發

領域驅動實戰思考(三):DDD的分段式協作設計


前言

在我的上一篇文章中,給大家介紹了我在實踐中對於DDD設計過程進行梳理的思考。本篇則是向大家整體介紹一下我的“DDD分段式協作設計”的步驟和內容。

同時,該方法的基準化操作手冊,也在曾經的一篇文章中公開提供了下載,可以作為更細化的內容進行參考和使用。

由於DDD的相關概念和設計方法極多,相比其他市場上所能見到的操作手法,我在這裡向大家所介紹的方法,更加聚焦如何能夠確保達到“絕大多數人的60分”,而非“極少數人的100分”,也就是說,我更加註重的是:

  • 步驟間的連貫性
  • 方法的可操作性
  • 實踐的可落地性
  • 與新技術的匹配性(例如雲原生)

為此,我盡可能的通過實戰檢驗,在一些需要憑藉經驗進行綜合判斷的場景,盡可能的提供雖然不完美但是可以降低選擇成本的唯一選項或解釋,從而爭取讓一線實施人員避免“二選一”或“看具體情況再說”這樣莫能兩可的糾結。

需要說明的是,不同的諮詢師在實施DDD的設計過程中手法都不一樣,我僅是從我所實施過的諮詢項目出發,提供了一種經反複驗證可工作的方式,並不代表本方法是唯一正確的。

在這裡僅供參考,也歡迎大家進行交流。

在介紹分段式設計之前,讓我們回顧一下DDD希望解決的問題:

軟件核心複雜性

所謂“複雜”,我根據實際觀察總結的理解是:

  • 業務場景多
  • 業務流程長
  • 業務概念多

而具備以上這種特徵的業務問題,其複雜度往往都會超出任何單獨一個人的大腦所能夠理解和處理的範圍。

在過去的單體架構時代,由於業務複雜度和技術複雜度都還處於與今天相比更加“簡單”的階段,所以很多時候,我們可能能夠依賴少數“聰明的”系統架構師或者“十倍工程師”來通過“拍腦袋”解決問題。

而在今天,這個邁向“第四次工業革命”的時代,當“唯一不變的就是變化”所帶來的高業務響應力要求,使得業務問題的複雜度越來越高,而技術複雜度也隨著雲原生和微服務架構產生了幾何增長,甚至催生出了DevOps這樣的運動時。對於一個要應對複雜業務挑戰的大規模企業來說,依賴少數“聰明人”通過“拍腦袋”來解決問題則變得越來越不現實。

那怎麼辦才好呢?

這時候,我們需要做的事情,就是通過集體的力量來解決問題:

  • 通過協作的方式消除部門牆和角色牆,共同應對和分析複雜業務問題,設計解決方案,避免少數人“拍腦袋”;
  • 通過領域驅動的方式,依據業務問題的邊界(業務變化的邊界)、人員溝通一致性的邊界(概念變化的邊界)以及彈性伸縮的邊界(雲原生需求的邊界)來架構軟件系統,實現業務架構和技術架構相匹配,從而通過業務變化來拉動架構,通過架構來響應業務變化。

這種設計方式所提倡的方法,與過去的“傳統設計方法”(嗯,沒錯,說的就是瀑布式或者偽敏捷)的區別,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 1

而聚焦到DDD本身,DDD首先強調的就是我們應當從過去一上來就先考慮“數據模型和數據庫表怎麼設計”這種“面向技術進行架構”的方式,改為優先考慮“我們要解決的問題是什麼”這種“面向業務進行架構”的方式。

為此,Eric Evans提出瞭如下三個DDD的核心原則,原文(《Domain-Driven Design Reference: Definitions and Pattern Summaries》)和我的解釋圖如下:

  1. Focus on the core domain.(聚焦核心域)
  2. Explore models in a creative collaboration of domain practitioners and software practitioners.(領域專家和軟件專家通過創造性的協作探索模型)
  3. Speak a ubiquitous language within an explicitly bounded context.(利用明確且有邊界的上下文統一語言)

領域驅動實戰思考(三):DDD的分段式協作設計 2

而在DDD思想出現之前,人們進行基於面向對象思想(OO)的系統架構設計的時候,更多的是通過“用例分析法+ SOLID原則+ UML”的方式,基於業務描述中的名詞和動詞,利用近似“拍腦袋”和“憑經驗”的方式來進行建模的,相信但凡長期從事面向對象設計的同行都會有所體會。

而在DDD思想出現和逐漸發展後,該思想使得人們能夠從業務抽象、統一語言和問題域劃分等多維度進行“更有套路”的面向對象分析,所以DDD被業內人士評價為:OO Done Right(正確的完成面向對象設計),如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 3

DDD的分段式協作設計

在回顧了DDD所解決的問題和辦法之後,讓我們來看一看如何通過分段式的協作設計來驅動DDD在設計階段的落地。

在我所使用的方法中,核心思想是:

通過協作設計,從問題澄清和統一共識出發,逐層遞進和細化,實現從業務抽象,到領域建模,再到技術實現的階段性落地。

由此思想出發,我將分段式設計過程拆分為以下幾個階段:

  • 戰略設計階段:業務驅動,忽略技術實現細節,為系統架構設計提供業務邊界對齊、彈性邊界對齊和投資優先級的信息輸入。
  • 戰術設計階段:基於戰略設計的信息輸出,進行進一步的抽象設計,作為戰略設計和技術實現的過渡階段,為技術實現提供基於抽像模型和業務服務劃分的輸入。
  • 技術實現階段:基於戰略設計和戰術設計的信息輸出,進行技術實現相關的設計,例如:技術性組件補全,技術選型,業務API詳細設計,分層架構設計,領域模型類設計,數據庫設計,制定測試策略… …等等。

接下來,我來分別說一說各個階段的內容。

戰略設計

在戰略設計階段,我們優先關注的是:開發團隊對於業務問題理解上的一致性

在這個過程中,“開發團隊”指的是包含了“領域專家”在內的所有軟件開發相關的關鍵角色,例如:客戶、產品經理/ 項目經理、用戶體驗設計師、系統架構師、開發工程師、測試工程師、運維工程師、數據庫管理員、安全專家……等等。

而“領域專家”,則是一個能力上的稱呼,而非職務上的某種角色,“領域專家”指的是:對需要解決的業務問題具備最豐富的領域知識,能夠幫助和指導開發團隊進行分析和設計的那個人(也可能是多個人的組合)

之所以需要這麼完備的人員構成,是因為協作設計的核心是需要將設計過程“前置(Shift Left)”到軟件開發價值流的早期,通過深入的碰撞、討論來形成共識,通過共識驅動整個開發的價值流,減少因為缺乏共識導致的扯皮、返工等浪費。

在該階段,這些人聚在一起,忽略技術實現細節,通過通力協作關注以下幾件事:

  • 業務梳理和抽象:通過事件風暴工作坊,對現實業務流程進行以系統實現為目的的抽象。
  • 限界上下文識別:通過統一語言(消除語言二義性),對抽象概念進行澄清、分類和查漏補缺,從而識別業務邊界。
  • 問題子域識別:通過對問題域進行識別和澄清,劃分問題邊界和問題域類型,對架構設計提供投資優先級的參考。

業務梳理和抽象

首先,團隊需要保證所有人對於業務所要解決的問題、業務場景和業務流程的理解是一致的,而這一點在實際中恰恰是絕大多數團隊最為明顯的“軟肋”。在現實中,每個人腦子裡面對於業務的理解都是不一致的,而每個人又無法看到對方的理解長什麼樣。所以,我們需要通過一種可視化的協作設計方式,利用不同顏色的便利貼、馬克筆在大白紙上進行溝通和交流(俗稱“糊牆”,我們這些DDD顧問經常自嘲為“糊牆師”) 。

業務抽象可視化的方式有很多種,其中比較著名的是“四色建模(Color Modeling in Color)”“事件風暴(Event Storming)”。在這裡,我所使用的是入門門檻更低,更“傻瓜”的事件風暴。

事件風暴是一種用於DDD的協作設計方法。該方法基於現實業務流程,以系統實現為視角,基於領域事件的發生時間線,通過一次只關註一個維度(分離關注點)的分層抽象方式,將現實業務流程進行抽象並轉化為系統實現的業務邏輯,這些步驟包括:

  • 識別領域事件
  • 識別決策命令
  • 識別領域名詞

通過使用事件風暴對業務進行梳理和抽象,能夠統一團隊認知,並為戰術設計階段的領域建模提供直接輸入。

我所使用的事件風暴實施過程,由於經過了大量的實戰檢驗、總結和持續調整,已經與事件風暴創造者Alberto Brandolini先生的現有方法大不相同,有關於Alberto版本的事件風暴相關信息,可以查看他的網站。

業務梳理和抽象的產出物,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 4

限界上下文識別

在業務梳理和抽象完成後,團隊接下來需要做的,是將事件風暴工作坊過程中產出的領域名詞和外部系統拿出來,根據概念的相關性和理解上的“二義性”,將它們分門別類,按照“限界上下文(Bounded Context)”劃分清楚他們的概念邊界。

限界上下文,是概念的邊界,在該邊界內,當我們去交流某個業務概念時,不會產生理解和認知上的歧義(二義性),限界上下文是統一語言的重要保證,同時也是業務問題最小粒度的劃分。

然後,團隊需要根據限界上下文間概念的依賴關係,對限界上下文進行進一步分析,畫出它們之間的依賴關係,以便發現和識別一些典型的“設計壞味道”,例如:

  • 雙向依賴:上下文之間缺少一層未被澄清的上下文,或者兩個上下文其實可被合為一個;
  • 循環依賴:任何一個上下文發生變更,依賴鏈條上的上下文均需要改變;
  • 過深的依賴:自身依賴的信息不能直接從依賴者獲取到,需要通過依賴者從其依賴的上下文獲取並傳遞,依賴鏈路過長,依賴鏈條上的任何一個上下文發生變更,其鏈條後的任何一個上下文均可能需要改變;

通過對於限界上下文的劃分和依賴關係的識別,團隊能夠實現軟件架構在概念邊界上的內聚和解耦。

我使用了限界上下文依賴關係,代替了人們常用的“限界上下文映射(Context Mapping)”,因為限界上下文的7種上下游映射關係,所反映的是團隊間的各種協作關係,這一步是一個非常細化的分析過程,在戰略設計這種宏觀分析階段,實際中非常難以提前識別和分析,因為很多時候團隊都還沒有。而絕大多數情況下,我們做限界上下文分析的時候,都是為了能夠快速的指導系統業務模塊或服務的劃分,所以我從C4模型(C4 Model)中找到了靈感,進行了簡化和代替。

限界上下文識別過程的產出物,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 5

問題子域識別

在戰略設計階段的最後,團隊需要根據限界上下文識別的產出,按照“一個子域負責解決具有一個獨立業務價值的問題”的原則,將限界上下文以“切蛋糕”的方式,劃分到不同的問題子域(Subdomain)中,並按照以下三種不同的子域類型進行標註:

  • 核心域(Core Domain):是當前產品的核心差異化競爭力,是整個業務的盈利來源和基石,如果核心域不存在,那麼整個業務就不能運作。對於核心域,需要投入最優勢的資源(包括能力高的人),和做嚴謹良好的設計。
  • 通用子域(Generic Subdomain):該類問題在界內非常常見,所以很可能有現成的解決方案,通過購買或簡單修改的方式就可以使用。
  • 支撐子域(Supporting Subdomain):該類問題解決的是支撐核心域運作的問題,其重要程度不如核心域,又不屬於通用子域,具備強烈的個性化需求,難以在業內找到現成的解決方案,需要專門的團隊定制開發。

問題子域,是對業務問題的澄清和劃分,同時也是對於資源投入優先級的重要參考,相對限界上下文來說,是對業務問題更大粒度的劃分。

通過對於子域進行識別、劃分和類型標註,團隊能夠實現軟件架構在業務邊界上的內聚和解耦。

在DDD的概念中,限界上下文和問題子域是兩個不同維度的概念,理論上來說並沒有相互的依賴關係,為了能夠方便操作和降低落地成本,依據實踐效果,我刻意的選擇了“一個子域包含多個限界上下文,一個上下文不得存在於多個子域”的方式。 *

問題子域識別過程的產出物,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 6

戰術設計

在戰術設計階段,我們優先關注的是:通過抽像模型和業務模塊劃分來承載業務抽象,為DDD從戰略到實現進行過渡

這時候,團隊已經在戰略設計階段完成了對於業務問題的澄清和抽象,需要深入建模細節,探討軟件架構的更細粒度的設計。

在該階段,開發團隊需要繼續忽略技術實現細節,做以下幾件事:

  • 領域建模:針對核心域內的業務概念進行領域建模,通過聚合、實體、值對象的識別,為技術實現中的面向對象設計提供參考。
  • 業務服務劃分:基於戰略設計的輸出,結合“雲原生思想”、“康威定律”和“逆康威定律”,劃分具體的業務服務單元。
  • 業務服務接口能力識別:根據領域建模和業務服務劃分的輸出,確定每一個業務服務單元對外暴露的“必要”接口能力清單(忽略具體的協議、地址和數據結構)。

領域建模

領域建模,是通過將業務抽象為以下幾種抽像模型的方式,利用模型承載和響應業務的變化:

  • 聚合(Aggregate):

    負責封裝業務邏輯,通過一致性邊界和統一語言,內聚決策命令和領域事件,容納並識別領域名詞為以下不同的抽像模型:

    • 實體(Entity):是聚合的主幹,具有唯一標識和生命週期。
    • 聚合根(Aggregate Root):是一種實體,是聚合的根節點。
    • 值對象(Value Object):是實體的附加業務概念,用來描述實體所包含的業務信息。

以上抽像模型,同屬領域模型(Domain Model),是對業務的高度抽象,利用抽像模型作為業務和系統實現的核心聯繫,領域模型封裝和承載了全部的業務邏輯,並通過聚合的方式保持業務的“高內聚,低耦合”。

在後續的技術實現過程中,聚合就是一種文件目錄結構(例如:包、命名空間、模塊),裡面存放了領域模型相關組件及其他的領域層組件,例如:領域服務(Domain Service),工廠(Factory),倉儲接口(Repository)等。

領域建模中的聚合,在承載業務邏輯的同時,是對業務問題最細粒度的澄清和劃分,一個限界上下文可能包含多個聚合,一個聚合不能存在於多個限界上下文。

通過領域建模和對聚合的設計,團隊能夠實現軟件架構在模型層面上的內聚和解耦。

領域建模過程的產出物,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 7

業務服務識別

業務服務識別,是為後續系統實現進行的基於業務邊界的模塊拆分分析,業內常見的拆分方法有:

  • 基於限界上下文進行拆分:每個限界上下文為一個服務,優點是每個服務都很小,代碼量少;缺點是拆分粒度太細,導致服務數量過多,增加架構設計的複雜度和運維成本。
  • 基於子域進行拆分:每個子域為一個服務,優點是服務數量相對較少,架構複雜度和運維成本相對更低;缺點是拆分粒度在某些場景下會非常大,導致單個服務變成“小單體” ,增加開發成本和代碼分層複雜度。
  • 基於彈性邊界進行拆分:這個方式是雲原生時代新的拆分方式,通過針對服務實例的彈性伸縮的功能性需求或非功能性需求,以彈性邊界為決定性參考,結合子域和限界上下文的分析,進行模塊拆分。這種方式的優點是,服務粒度和數量適中,更貼近實際需要,開發和運維成本均衡;缺點是引入了一個更貼近運營需求和技術實現的參考維度,增加了系統架構的能力要求和復雜度(這種方法我取自於ThoughtWorks中國區CTO徐昊)。

從我的實戰檢驗來看,我刻意的選擇“基於彈性邊界進行拆分”的方式,因為這種方式的說服力更高,性價比也最高,至於對於架構能力和復雜度的要求嘛……對於一個技術顧問和雲原生時代的架構師來說,這應該都不是問題才對……

通過對於業務服務進行劃分,團隊能夠獲得對軟件架構模塊拆分的直接指導,並且還能夠依據“逆康威定律”依據架構結果進行開發團隊的劃分和組建。

業務服務識別過程的產出物,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 8

業務服務接口能力識別

在識別和劃分了業務服務之後,我們需要針對每一個業務服務,基於領域建模時聚合的決策命令,補全和定義每一個業務服務對外暴露的接口能力,這種接口能力一般就是兩類:

  • 寫類型的接口能力
  • 讀類型的接口能力

而在這個過程中,由於我們還是處於協作設計的階段,忽略具體技術實現細節,所以我們並不關心接口的實現方式,例如:接口的設計風格、接口的協議、接口的地址、接口的數據結構、是否是事件驅動、是否用消息隊列、是同步接口還是異步接口等。這些東西都是在後續技術實現階段進行的更詳細的設計過程。

通過對業務服務的接口能力進行識別,團隊能夠提前定義業務服務的接口概要設計方案,為後續負責具體開發的團隊提供直接的輸入,方便他們進行接口的詳細設計。

很多老的DDD設計方式,在這時候往往都會使用Swagger來定義基於RESTful Web API的接口,來實現“API驅動開發(API Driven Development)”。一方面正如前述所說,這都是技術實現細節,提前考慮技術實現細節一方面會增加協作設計階段的成本,另一方面會干擾領域驅動設計的過程。另一方面,當今的微服務,RESTful Web API這種同步接口的設計,已經不再是最優的默認接口設計風格,內部服務間通過gRPC或消息隊列進行通信,對前端服務使用GraphQL等技術實現查詢式接口,已經逐漸成為了新的趨勢,而RESTful Web API則主要用於對外部服務暴露開放接口的場合。所以,我改為了只對接口能力進行識別,忽略技術實現細節。

業務服務接口能力識別過程的產出物,如下圖所示:

領域驅動實戰思考(三):DDD的分段式協作設計 9

技術實現

在完成了戰略設計和戰術設計之後,需要眾多關鍵角色集中投入的協作設計過程,就告一段落了。剩下的與具體技術要求更緊密的詳細設計過程,刻意交由具體負責業務服務開發的團隊去進行後續的設計和實現。

技術實現階段要做的事情,包括且不限於:

  • 補全技術組件:補全為了支撐系統實現的關鍵技術型組件,例如客戶端、BFF(Backend for Frontend)、ACL(Anticorruption Layer,防腐層)等,完善系統應用類組件(需要分清應用和基礎設施的區別)。
  • 技術選型:選擇適合的技術棧或工具。
  • API詳細設計:選擇適合的通訊方式、API設計風格和開發框架,利用契約測試或可視化文檔對API進行詳細設計。
  • 分層架構設計:採用符合領域驅動設計風格(或者其它符合整潔架構思想的)的分層架構思想,設計單個服務的架構。
  • 領域模型類設計:參考領域模型的設計,利用面向對象的語言設計具體的類。
  • 持久化設計:參考領域模型的設計和實際的持久化相關指標,對持久化組件(例如數據庫)進行選型和設計。
  • 制定測試策略:針對實際需要和性價比,設計適合的測試策略,以守護架構設計。
  • ……(其他)

需要特別注意的是:哪些東西屬於技術實現細節?哪些東西屬於抽象業務?這個判斷將會貫穿整個DDD的分段式協作設計過程,任何過早進行技術實現細節的思考和討論,都有極大的風險導致領域驅動走偏。

所以,在實戰過程中,我通常採用一個非常有效的方式來提醒大家:

拉一個寫著“絕不提前考慮技術實現”的橫幅貼在牆上……

以上內容,就是關於我的“DDD分段式協作設計”的介紹,具體的操作步驟和注意事項,歡迎參考先前文章中提供的操作手冊

作者簡介

胡皓,ThoughtWorks 首席諮詢師。十年以上軟件開發工作經驗。從士兵成長起來的軟件技術顧問,從事過廣泛的業務分析,項目管理,全棧軟件開發、培訓和技術諮詢等工作。當前,作為ThoughtWorks 中國區諮詢BU 數字化架構能力團隊的負責人,正深耕於以演進式架構、領域驅動設計、雲原生微服務為代表的數字化架構能力,幫助客戶實現軟件工程能力提升和數字化轉型的目標。

相關閱讀

領域驅動實戰思考(一):用 TDD 思想對 DDD 的協作設計過程進行基準化

領域驅動實戰思考(二):用分段思想改進那些混亂的戰略設計和戰術設計