如何做 DDD 領域驅動設計?

由於 DDD 的內容多且細,本文會嘗試以精簡且容易理解的方式講述 DDD 的重點概念及用法,目的是幫你對 DDD 形成一個體感上的認知。

本文篇幅略長,希望你可以耐心閱讀。如果時間緊張,你可以先做收藏。

01 什麼是 DDD

DDD 的全稱爲 domain-driven design,也就是領域驅動設計。

DDD 誕生於 2003 年,當時並沒有受到業界特別的關注,主要原因是當時的軟件開發方法不注重業務模型的設計和建模,而更加偏向功能導向和技術導向。隨着微服務、雲計算等技術的興起,對於複雜業務的切分和組織變得更爲關鍵,這就需要更優秀的業務架構設計,而 DDD 則提供了一種有效的業務架構設計方法,逐漸受到大家的關注和認可。

雖然這些年 DDD 大行其道,但大家對 DDD 的評價卻褒貶不一。一方面,DDD 可以幫助構建業務規則複雜的系統,並使其保持良好的擴展性。但另一方面,DDD 整體的理論比較抽象,概念偏多,這使得很多人運用的時候往往不得其法,浪費了很多時間。

所以,系統設計是否使用 DDD 需要一事一議。但不可否認,DDD 中蘊含的設計思想是非常值得我們學習和借鑑的。

02 戰略設計與戰術設計

爲了更好地介紹 DDD,我們先構建一個虛擬的業務場景。我們後面所有的內容都會圍繞這個業務場景展開。

我們假設有一個小型的電商網站,賣家可以在上面出售商品,買家選擇商品併購買,購買後網站把款項結算給賣家。

沒錯,這就是互聯網最典型的應用場景。

在 DDD 中,可以將整體的設計分爲**【戰略設計】**和**【戰術設計】**。兩者在設計的側重點上不同,我們將分別展開說明。在整個說明過程中,重點的概念也會逐一登場。

01 戰略設計

戰略設計的目的是:幫助開發人員和業務人員建立共同理解的業務模型,劃分業務邊界

這裏有兩個重點:【1】對齊技術人員和業務人員對於業務流程、業務規則、業務關鍵概念的認知,這樣的好處不言而喻。【2】劃分業務邊界可以指導工作內容拆分。在微服務背景下,戰略設計的產出可以直接指導微服務劃分。

戰略設計的主要產出內容如下:

我們來介紹下其中的幾個概念:

【領域】 是指 一個具有獨立性和自治性的問題空間。聽着比較抽象,實際上沒那麼複雜。就拿我們的虛擬場景來說,領域就是 “電商”。對於【領域】來說,最重要的是兩個概念:範圍 + 知識體系。“範圍” 告訴我們,領域是有邊界的。“知識體系” 告訴我們,領域內的概念和規則是有專業性的且相互關聯的。

【子域】 顧名思義,就是將領域進行劃分,變成一塊塊更小的 “領域”。每個“小領域” 也必須具有 “範圍 + 知識體系” 的特徵。子域劃分使用了分而治之的經典思想。在我們的虛擬場景中就會包括 “商品域”、“支付域”、“結算域” 等等。

【限界上下文】 是針對子域更小的劃分,其中包含了多個具體的領域模型。限界上下文的作用是將業務緊密關聯的領域模型放在一起,並且提供統一的語境(各種概念在限界上下文中有統一的含義)。例如虛擬場景中,一個商品上單時我們將用戶輸入的賣價稱之爲原價,售賣時則將用戶不使用任何優惠活動的價格稱之爲原價。

“限界上下文” 對微服務拆分有重要的指導意義。

【領域模型】 在 “事件風暴”(我們馬上會講到)中找到的和業務相關的各種模型。例如各種單據模型、角色模型、商品模型、權限模型等等。其中,很多模型之間有非常緊密的關係。

這四個概念我們是從大到小來說的,但在做戰略設計的時候,其實是反過來的,這個我們後面就會看到。

02 戰術設計

戰術設計是指:基於戰略設計的結果,進一步分析領域模型並補充細節,接着將領域模型轉換爲具體的代碼。其中主要包括【領域服務的識別】和【代碼層次的劃分】。我們在後面的小結馬上可以看到,在這裏我們先對一些關鍵的概念做一些瞭解。

【實體】 指在業務領域中具有生命週期和業務行爲並能產生變化的對象,如訂單、客戶等。

【值對象】 是指在業務領域中用來描述某些特定屬性或屬性組合的對象,通常是不可變的,如地址、手機號等。

【聚合與聚合根】 是用來組織 “實體” 和“值對象”的一種模式。聚合定義了一個業務邏輯上的整體,其中包含多個 “實體” 和“值對象”。聚合根是聚合中的一個實體,負責維護聚合內的完整性和一致性。

【領域服務】 是指處理業務邏輯的函數,沒有 “實體” 或“值對象”,它負責處理需要多個對象協作才能完成的複雜業務場景。

【領域事件】 是指在業務領域中發生的某些重要事件,它們會對業務流程產生影響。同時,事件還將通知所有對此事件感興趣的相關方,比如其他領域、系統或模塊。

我相信,直接看這些概念會覺得生澀難懂。沒關係,你只需要對這些詞有點印象,下面我們結合虛擬場景來做設計時,你會再逐一看到他們。

03 戰略設計之領域建模

如上所述,我們首先要進行戰略設計。戰略設計的目的是:幫助開發人員和業務人員建立共同理解的業務模型,劃分業務邊界。

01 事件風暴

業務模型不會直接躍然紙上,我們需要通過一些方法,將大家腦中的業務規則、業務概念、業務流程以及之間的各種關係呈現出來。更重要的是,大家需要對齊這些業務相關內容的認知。【事件風暴】 就是最常用的方式。

簡單地說,事件風暴就是把相關的同學拉到一起,按照一套特定的組織邏輯來分析業務過程,識別業務中的關鍵概念。

這套特定的組織邏輯包括了 “事件”、“參與方”、“命令”、“業務規則” 等幾個重要概念,以及他們之間的邏輯關係。這些內容指導我們開展事件風暴。

《圖片來源於 virtualDDD》

【參與方 Actor】:參與業務流程的角色,可以是人也可以是系統。他可以出發命令,同時可以在視圖上看到反饋。

【命令 Command】:參與方發起的行爲。命令可以觸發系統行爲,也可以稱之爲調用系統。

【外部系統 ExternalSystem】:不需要知道細節的服務提供方。提供服務後會產生事件。

【領域事件 DomainEvent】:業務過程中發生的重要事件。事件會激活策略。

【策略 Policy】:策略負責響應領域事件,可以觸發新的行爲。

【視圖 QueryModel】:視圖用來與參與方打交道,供參與方繼續觸發命令。

【熱點 Hotspot】:一些需要關注的問題,可能是業務問題(用戶是否可以重複購買同一個商品),也可能是技術問題(性能瓶頸)。需要進一步討論。

如果覺得生澀沒有關係,下面我們也還會看到這些。這裏你需要體會的是:分析業務有如上這些要素,他們之間相互依賴和影響。正是基於這種影響,我們才能夠分析出來完整的業務流程,繼而形成領域模型。

下面基於我們的虛擬場景,看看如何來做【事件風暴】。

【step1:準備工作】

工具準備。包括:一個方便大家來回走動的會議室、各種顏色的貼紙(不同顏色貼紙的作用與上面介紹的事件風暴組織圖一致)、筆、一塊大的黑板或者白板。

人員準備。包括:業務專家、產品、設計師、架構師等。這些人員或與系統建設相關、或瞭解業務規則概念、或與產品建設相關。

【step2:開場】

事件風暴過程中需要一個專職的主持人。主持人最重要的就是負責 “一個接一個問問題”,驅動在事件風暴過程中產出領域模型。

開場時,主持人需要交代事件風暴會議的背景、目標、內容、要求、貼紙的用法等。

【step3:梳理事件】

之所以從事件開始,是因爲事件代表某個行爲的結果,是業務的重點。從事件可以正推或者反推整個業務流程,並呈現其中的各種業務概念。

例如在我們的虛擬場景中,賣家上了商品後就會有 “商品已上架” 事件。我們可以沿着這個事件繼續思考,這個事件的前後還會有哪些事件?

“商品已上架”之前,商品肯定被創建了,所以肯定會有 “商品已創建” 事件。“商品已創建”後就上架了嗎?一般還會有審覈環節,所以會有 “商品已審覈” 事件......

“商品已上架”之後,用戶就可以進行購買。所以就會有 “用戶已支付” 事件。“用戶已支付”事件前肯定還會先創建訂單,所以有 “訂單已創建” 事件......

按照這個思路,我們就得到了如下這樣的視圖:

【step4:識別其他要素】

在梳理完領域事件之後,我們就可以以這些事件爲線索,把其他信息給補上。

補充命令。命令就是行爲,一定是有具體的行爲觸發了上面這些領域事件。例如 “商品已創建” 肯定是由 “創建商品” 這個命令觸發的。“訂單已創建”則是由於用戶選擇了商品後觸發的等等。

補充參與方。上面的這些命令一定是由參與方觸發的,可以標識出來。

補充策略。策略是對事件的響應。例如收到 “商品已審覈” 事件,就會有策略來驅動將商品放上貨架。收到 “用戶已支付” 事件後就會驅動生成物流單。策略更廣義地說就是業務規則。

其他還有補充視圖、熱點問題、第三方系統,我們不一一論述了,直接看下補充後的結果:

上圖中:橙色是事件、藍色是命令、粉色是策略、綠色是界面、黃色是參與方、紫色是三方系統。

當然,一個真正的電商網站比上述分析的內容要複雜得多。爲了方便敘述,我們就挑重點的一些來介紹了。

02 分析領域模型

做完了事件風暴,下面就是根據事件風暴的結果來進行建模了。我們可以從事件、命令、策略中提取名詞,這些名詞往往就是【領域實體】或者【值對象】。下圖就是我們分析並提取的內容(我們在事件風暴基礎上做了一些補充):

接着,我們需要對這些 “實體” 及“值對象”做聚合。還記得上面聚合的定義嗎?忘了?沒關係,這裏我再貼一下。

聚合定義了一個業務邏輯上的整體,其中包含多個 “實體” 和“值對象”。而聚合這些 “實體” 和“值對象”的實體稱之爲聚合根。

下圖就是我們聚合後的結果,其中綠色的就是聚合根。

03 劃分限界上下文

在得到聚合和聚合根以後,我們就需要來劃分限界上下文。限界上下文指導我們拆分服務,所以限界上下文圈定的內容就會放在一個系統中建設。

限界上下文劃分的依據是:在一個限界上下文中有一個完整的業務子流程,並且提供了統一的語境。

根據這個指導方針,我們將上面的聚合做如下劃分:

上圖可以看到,在劃定限界上下文時,我們也同時標出了各種子域。

04 戰術設計之代碼實現

在完成限界上下文的劃定後,針對每一個限界上下文就進入了戰術設計階段。這個階段依然需要使用到我們上面分析獲得的【事件風暴】結果。

我們僅以【商品限界上下文】爲例來展開論述。

【step1:分析領域服務】

我們在戰略設計中根據事件、命令、策略中的名詞提取了領域模型中的實體和值對象。

“實體”和 “值對象” 偏重於表現數據,也就是這些實體包含了哪些信息。雖說實體本身也提供了業務規則相關的能力,但能力比較瑣碎。我們往往需要將多個實體聚合在一起對外提供能力,而這樣的能力我們就稱之爲 “領域服務”。而將“領域服務” 編排起來提供的能力就表現爲了系統對外的能力。

領域服務主要來自於 “事件風暴” 中的命令。我們可以看到,命令有:創建商品、編輯商品、提交審覈、審覈通過、商品上架。

【step2:劃分代碼層次】

接下來我們來講 DDD 經典的分層模型。

如上圖所示,在 DDD 分層設計中,主要分爲四個層次:

【接口層】:對出入參僅做格式上的校驗,不能涉及 “例如用戶是否在黑名單中” 這樣的校驗。

【服務層】:負責編排流程、處理 rpc 請求、控制同異步。不能涉及領域概念。

【領域層】:針對領域規則來實現具體的能力,不能跨領域。

【基礎層】:這一層嚴格意義上來說並不處於最下面一層。在上面三層中,所有的外部依賴(DB、緩存、中間件、服務調用)都使用接口的方式來避免和具體技術耦合。而基礎層提供了這些接口的具體實現。

很多文章都寫到:經典的 DDD 分層框架中,所有和外圍應用打交道都應該是應用層的邏輯。所以,多個系統間的交互是如下這樣:

不過根據實際經驗來看,很多時候領域層的領域服務往往也會直接做 RPC 調用和異步事件交互。

事實上,DDD 的分層及交互標準可以根據實際情況做一些修改,只要不破壞一些關鍵原則(例如依賴關係不能倒置、除了基礎層不能有對外部依賴的具體實現)。切記不要爲了 DDD 而 DDD。

【step3:具體代碼目錄】

結合上面提到的分層,我們最後來看下具體的代碼目錄結構是怎樣的。

爲了便於解釋各個目錄的作用,我就直接把說明標註在圖中了。

到這裏,我們就把 DDD 領域設計相關的內容都講完了。

今日小結

今天我們介紹了近些年炙手可熱的 DDD 領域驅動設計。結合我們給出的簡易電商場景,給大家介紹了 DDD 的關鍵概念以及如何做從戰略到戰術的各種設計。

最後,我有幾個點想要再說一下:

【1】真正的電商網站遠比我們的模擬場景要更復雜及更細節。我們提到的很多領域模型或者劃分的子域,事實上展開還有很多內容。本文重點在於闡述 DDD 的核心思想及方法,不做過多展開。

【2】DDD 其實也有非常多的細節,如果你在工作中想要使用 DDD 設計,你除了可以參考本文外,還建議你可以去看些更多的設計案例。僅僅看書你還是一頭霧水。

【3】DDD 的設計能力是在實踐中逐步積累的,不要害怕一上來不得其法,這是肯定的。如果工作中沒有這樣的機會,你可以給自己模擬一些場景來試試手。

【4】給你推薦兩篇我認爲寫得比較好的關於 DDD 的文章,本文也做了一些參考。

《領域驅動設計 DDD|從入門到代碼實踐》

《阿里技術專家詳解 DDD 系列》

近三篇文章,我們已經把系統設計方面的內容講完了。下面將開啓我們系統鐵三角方面的內容,也就是 “擴展性”、“性能”、“穩定性”。相信這些內容與你的工作息息相關,也是我們技術人員的基礎能力,期待你和我繼續同行。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/zYfjY633DZWVtHmFtfV2IQ