DDD 落地:有讚的生產項目,DDD 如何落地?
一、有贊教育線索資源管理項目背景
作者:程英傑,有贊教育
在教育行業中,業務流程涵蓋了招生開發、潛在學員信息管理、教務調度、學員溝通、互動輔導以及口碑傳播。首先,在招生開發階段,通過網絡營銷或線下推廣活動收集潛在學員信息,並將其納入信息管理系統。在潛在學員信息管理環節,利用信息資源管理系統對收集的數據進行統一管理,並將潛在學員轉化爲實際學員,爲後續教務調度提供數據支持。顯然,潛在學員信息管理在教育行業中起着承前啓後的作用,其重要性不言而喻。
整個項目的業務場景如圖 1-1 所示,項目分爲兩大業務領域,即線索領域和配置中心領域。線索領域主要負責線索收集、線索信息管理等職責;配置中心領域則負責整合公共配置資源,如線索相關的標籤、來源等。
在實際項目中,我們可以根據需求複雜程度和業務特點,靈活運用四色原型圖、用例圖、時序圖等工具,結合 DDD 思想,全面、準確地描述業務需求,構建符合業務需求的優秀領域模型。只有經過嚴謹的分析和設計,我們才能開發出高質量的軟件系統,爲教育行業的發展貢獻力量。此外,在項目實施過程中,還需關注潛在學員信息管理的實時動態,以便爲招生開發、教務調度等環節提供準確的數據支持,確保整個業務流程的順暢進行。
二、領域驅動基礎概念介紹
在介紹 DDD 相關概念前,我們先探討一下爲何要採用領域驅動設計。在非領域驅動設計的項目中,我們通常會先進行數據庫表的設計,然後根據表結構推導出相應的實體對象。這些實體對象僅僅是數據的載體,缺乏實際的行爲。在這種設計模式下,業務流程實現仍屬於面向過程,以數據爲中心的過程式思想,開發過程可以理解爲對數據進行移動、處理和實現的過程。而如果採用 DDD 的思想去設計,我們將構建一個基於面向對象原則的系統。接下來,我先介紹 DDD 的標準分層架構,然後介紹下需求分析階段非常有用的四色原型分析模式,最後簡要介紹下方案設計階段常用到的幾個 DDD 領域概念。
2.1 領域驅動設計標準分層架構
當前,業界較爲通用的 DDD 架構採用的是四層模型,從下到上依次爲基礎設施層、領域層、應用層和用戶界面層。具體的分層架構見圖 2-1。
2.1.1 基礎設施層
基礎設施層主要負責爲其他層提供通用技術能力,如消息發送、領域持久化等。在實際項目應用中,這一層主要處理數據持久化操作,將領域對象序列化到各種存儲介質中,如數據庫、Hbase、MongoDB、ES 等,並從這些存儲介質中讀取數據,組裝成領域對象。這一層通常採用倉儲機制實現領域持久化能力。
2.1.2 領域層
領域層,也稱爲模型層,是業務系統中最核心的一層,幾乎所有的業務邏輯都在這一層實現。領域層主要包括領域模型和領域服務。
(1)領域模型
領域模型用於抽象複雜的業務邏輯,將其轉換爲便於理解的概念圖模型,一般由實體和值對象構成。它與數據模型的區別在於:數據模型描述的是對象的持久化方式,而領域模型表述的是領域中各個類以及各類之間的關係。
(2)領域服務
領域服務可以認爲是領域模型的一種補充,因爲在實際建模過程中,一些概念本質上是一些操作,涉及多個領域對象,並需要協調這些對象完成操作。若將這些操作硬性歸類到某個對象,可能導致對象職責不明確。此時,就需要領域服務來承載這些操作,串聯多個領域對象。例如,在線索管理項目中,線索詳情頁信息包括 “線索基礎信息”、“標籤信息”、“來源信息” 和“線索處理日誌信息”。在建模時,我們考慮到合理性將這四者定義爲四個單獨的實體。然而,在獲取線索詳情時,如何整合完整的線索信息成爲一個問題。爲了解決這個問題,我們引入了領域服務,負責承載線索信息聚合操作。
2.1.3 應用層
應用層負責提供應用服務,主要負責業務用例的編排和組裝。與應用層的主要區別在於是否處理業務邏輯。應用層主要協調領域層與用戶界面層之間的關係,對外提供各種應用功能,對內調用領域層的領域對象或領域服務完成業務編排和組裝。
2.1.4 用戶界面層
用戶界面層主要負責展示用戶信息。具體來說,就是請求應用層獲取所需展示的數據,併發送命令給應用層,要求其執行特定用戶命令。在實際應用中,這一層可以不存在。例如,在教育團隊早期的項目中,前端通過 http 方式調用後端服務。在這一層,我們通過提供 REST 服務與前端進行交互。之後,統一採用 RPC 調用方式,減弱了這一層的存在感。在這一層聲明二方服務接口與前端 node 層交互,然後在應用層實現具體接口。
2.1.5 線索管理應用工程結構簡單介紹
本小節將簡要介紹線索管理項目涉及的應用工程目錄結構,並對比四層架構。首先,來看工程目錄結構,如圖 2-2 所示。
出於商業保密性,實際工程結構中部分模塊做了隱藏
目錄中各模塊的定義如下:
-
demo-api:接口層,負責系統間或對外的接口聲明。通過 RPC 調用方式對外提供二方服務。
-
demo-biz:應用服務、領域服務處理層,接口層所聲明接口的具體實現。
-
demo-dependency:外部系統的調用封裝,比如,系統需要調用商品中心的服務,則需要在本 module 中封裝 client。
-
demo-domain:領域層,系統領域的一些 model、上下文對象、倉儲接口定義等。
-
demo-web:對外的 REST 接口。
-
demo-dal:基礎設施層,數據持久化。
與 DDD 四層構架的對應關係見下表。
通過以上內容,我們可以瞭解到領域驅動設計的基本概念和分層架構。在實際項目中,我們可以根據需求複雜程度和業務特點,靈活運用四色原型圖、用例圖、時序圖等工具,結合 DDD 思想,全面、準確地描述業務需求,構建符合業務需求的優秀領域模型。只有經過嚴謹的分析和設計,我們才能開發出高質量的軟件系統,爲教育領域的發展貢獻力量。同時,在項目實施過程中,我們需要關注潛在學員信息管理的實時動態,以便爲招生開發、教務調度等環節提供準確的數據支持,確保整個業務流程的順暢進行。
2.2 需求分析利器 — 四色原型圖
對於簡單的需求,用例圖往往足以闡述清楚。當需求變得複雜時,我們可以添加時序圖、狀態圖等來進一步說明。然而,當業務流程異常複雜時,如何找出關鍵點以及各點之間的關聯呢?是否存在一種科學的理論來指導我們進行分析呢?這時,我們可以考慮使用四色原型分析模式。它主要應用於業務分析階段,有助於我們分析業務行爲、參與對象以及業務對象之間的關係。
那什麼是四色原型圖呢?我們先來看下它的四個構成元素,具體如下:
(1)時刻 - 時間段原型(Moment-Interval Archetype)
原型簡稱 MI,表述的是某刻或某段時間內發生的一件事,比如:租房合同簽署,是在某個時刻簽署的,它有發生日期、行爲人;租房行爲是在一段時間內發生的,它有開始、結束時間和退租行爲。這些我們都是可以通過此原型來表達的。在畫原型圖時,採用粉紅色表示。
(2)參與方 - 地點 - 物品原型(Part-Place-Thing Archetype)
原型簡稱 PPT,用來表示參與某個活動的人或物,地點則是活動的發生地。比如簽署租房合同這個行爲,合同、承租人分別對應這裏的物、人,中介辦公室對應這裏的地點。在畫原型圖時,使用綠色表示。
(3)描述原型(Description Archetype)
原型簡稱 DESC,是對 PPT 公共屬性的描述,拿 “簽署租房合同” 這個場景爲例,在合同中會有一些租期、租金、押金、違約條件等約定,這些約定信息便可採用 DESC 原型來描述。繪製原型圖時,採用藍色表示。
(4)角色原型(Role Archetype)
原型簡稱 Role,這裏的角色,指的是我們通常理解的 “身份”。在簽署租房合同場景中,行爲人包括承租人和中介工作人員,這裏的角色便是指“承租人” 和“中介工作人員”。繪製原型圖時,用黃色表示。
總結:如果必須要用一句話來概括四色原型的話,那就是:一個什麼樣的人或物以某種角色在某個時刻或某段時間內在某個地點參與某個活動。其中 “什麼樣的” 就是 DESC,“人或物”、“地點”就是 PPT,“角色”就是 Role,而”某個時刻或某段時間內的某個活動 " 就是 MI。
2.3 DDD 幾個核心領域概念
2.3.1 實體
實體是一個具有身份和連貫性的概念,它具有以下幾個特徵:
-
實體是數據(屬性)和行爲(業務邏輯關係)的結合體;
-
每個實體都有自己的唯一標識,判斷兩個實體對象是否相等,是通過唯一標識來判斷的。比如,兩個實體對象,如果唯一標識相等,即使其他屬性不相等,這兩個實體也會認爲是同一個。實體的其他屬性不相等,表徵的是同一個實體在其生命週期的不同階段。
-
實體的唯一標識屬性值是不可變的,其他屬性值是可變的。
舉個例子簡單說明下,比如在有贊精選內容平臺(類似於小紅書的電商導購平臺)這個業務域中,每一篇 “博文” 就是一個業務實體,可以採用 “博文 id” 作爲實體的唯一標識,然後這個博文實體擁有着屬性(標題、作者、發表時間、內容等)和行爲(更新博文、刪除博文、關聯導購商品等),同時,屬性是會隨着行爲而不斷變化的。
2.3.2 值對象
值對象一般會作爲一個屬性存放於一個實體內部,它具有以下幾個特徵:
-
值對象不需要唯一標識,判斷兩個值對象是否相等,是通過值對象內部所有屬性值是否相等來判斷的。
-
值對象的屬性值不允許更改,即在創建後,其實體將保持不變。若要更改屬性值,需先刪除對象,然後重新創建一個新對象。
以 “有贊精選內容平臺” 爲例,用戶可以在博文下留言,我們會挑選部分留言置頂。對於“置頂留言”,可以將其定義爲值對象,並作爲博文實體的屬性。當置頂留言發生變化時,只需創建新的值對象,並將其賦值給博文實體的相應屬性。
2.3.3 聚合
聚合是一組具有內在聯繫的領域對象(包括實體和值對象)的集合,其中一個或多個實體組成。每個聚合都有一個根實體(又稱聚合根),主要負責與外部交互。外部對象若要訪問聚合內的實體,必須先訪問聚合根,再由聚合根與內部實體交互。
還是拿 “有贊精選內容平臺” 舉例說明,一篇博文中,它包含博文基礎信息(內容、標題等)、關聯的商品信息、關聯的標籤信息等,這一組合構成一個聚合,其中 “博文基礎信息” 可作爲聚合根。
2.3.4 倉儲
首先說明,倉儲被設計出來的初衷,在領域模型中,對象被創建出來後一般會在內存中活動,待其不活動了後,需要將其進行持久化存儲。然後,當我們需要重建對象時,需要根據對象當前狀態進行重建。可見這整個過程中,會頻繁的與數據庫(廣義的數據庫,包括關係型數據庫、NoSql 數據庫等)打交道,進行對象的創建、組裝等。因而,能否提供一種機制,幫助我們管理領域對象以及做對象持久化,倉儲並應運而生了。
倉儲,又稱資源庫,它具有以下幾個特徵:
-
倉儲作爲領域層與基礎設施層的橋樑,將倉儲接口定義放在領域層,具體實現放在基礎設施層。這種解耦有助於減輕領域層與 ORM 之間的關聯,任何 ORM 變更只需修改倉儲實現,領域層接口定義無需修改。
-
倉儲存儲的對象一定是聚合,因爲領域模型以聚合劃分業務邊界。因此,我們只對聚合設計倉儲。同樣,在進行數據更新、刪除等操作時,應以聚合爲單位進行操作,而非僅操作聚合內的某一個實體。
三、線索資源管理 DDD 實戰
結合四色原型圖,設計領域模型的步驟可概括爲以下幾步:
-
根據需求,採用四色原型分析法建立一個初步的領域模型;
-
進一步分析領域模型,識別出哪些是實體,哪些是值對象,哪些是領域服務;
-
對實體、值對象進行關聯和聚合,提煉出聚合邊界和聚合根;
-
爲聚合根設計倉儲(通常情況下,一個聚合對應一個倉儲),同時考慮實體、值對象的創建方式,是通過工廠創建還是直接使用構造函數;
-
走查需求場景,驗證設計的領域模型的合理性。
在圖 1-1(線索管理業務總覽圖)中,我們可以看到 “線索域” 是核心部分。接下來,我將重點針對“線索域”,按照上述步驟一步步推導出其領域模型。
3.1 場景分析提煉四色原型圖
無論是線上營銷工具渠道還是線下地推渠道,線索收集環節最終觸發的業務場景都是線索新增。而在線索管理環節,主要業務場景包括:新增 / 更新線索、查詢線索、分配線索、跟蹤線索和放棄線索等。這些業務場景在圖 3-1 中可見。
(1)新增 / 更新線索四色原型圖
根據業務規定,只有高級管理員、課程顧問、普通管理員等有權操作線索。依據四色原型的 “一個什麼樣的人或物以某種角色在某個時刻或某段時間內在某個地點參與某個活動” 的原則,我們可以得出操作過程中的參與方原型爲商家,參與方角色包括商家高級管理員、課程顧問、普通管理員等;物品原型爲線索,包括線索基礎信息、線索標籤、線索來源;參與的活動爲“新增 / 更新線索”。提煉出的四色原型圖見圖 3-2。
(2)查詢線索
參與方原型爲商家,參與方角色包括商家高級管理員、課程顧問、普通管理員等;物品原型爲線索基礎信息、線索標籤、線索來源;參與的活動爲 “查詢線索”。提煉出的四色原型圖見圖 3-3。
(3)分配線索
每個線索若需分配跟進人,必須指定一個跟進者。如果當前跟進人無法完成線索跟蹤,可以將線索轉讓給其他人(線索分配者、線索承接人、線索原跟進人的身份均爲 “高級管理員、課程顧問、普通管理員” 之一)。根據上述場景,參與方原型爲商家,參與方角色包括商家高級管理員、課程顧問、普通管理員等;物品原型爲線索基礎信息、線索標籤、線索來源;參與的活動爲“分配線索”。提煉出的四色原型圖見圖 3-4。
(4)跟蹤線索
課程顧問在獲得分配的線索後,需要進行線索跟蹤。在跟蹤過程中,課程顧問可以記錄相關的跟蹤信息。此時,參與方原型爲商家,參與方角色是課程顧問;物品原型爲跟蹤記錄;參與的活動爲 “添加跟蹤記錄”。歸納出的四色原型見圖 3-5。
(5)放棄線索
如果課程顧問認爲當前線索難以跟進,可以選擇放棄該線索。從這一場景中,我們可以看出:參與方原型是商家,參與方角色是課程顧問;物品原型是線索(包括基礎信息、標籤、來源);參與的活動是 “放棄線索”。提煉出的四色原型圖見圖 3-6。
綜合以上所有場景,可得出圖 3-7 所示的 “線索域” 四色原型圖。
3.2 領域模型中實體 / 值對象 / 領域服務 / 聚合識別
通常,我們可以將四色原型圖中的原型與 DDD 進行簡單映射。例如:PPT 原型表示活動中的唯一個體,可對應 DDD 中的實體;Role 原型描述實體在不同狀態下的表現,通常將其放入實體中,共同構成帶狀態的完整實體;DESC 原型表示 PPT 的公共屬性,一般作爲值對象存儲;MI 原型描述特定活動,可間接對應領域服務。
我們回過來看下圖 3-7,在圖中,有 “商家、線索基礎信息、跟蹤記錄、來源信息、標籤信息”5 個 PPT,我們可以據此定義 5 個實體,“高級管理員”、“課程顧問”、“普通管理員” 可以認爲是商家在不同身份下的表現,可在商家對象中使用一個標識符來描述。於是,我們可以總結出以下實體,見圖 3-8。
實際的線索信息比圖 3-8 中定義的要複雜,出於商業保密性,這裏僅列出部分字段,且部分字段採用 xxx 來表示。
接着,我們進一步分析實體間的關係,提煉出聚合邊界和聚合根,並定義出倉儲。
在線索域中,線索是核心,很明顯 ClueEntity 與 SourceEntity、RecordEntity、TagEntity、UserEntity 是相關聯的,而後四者間是沒有聯繫的。首先,來看下 ClueEntity 和 UserEntity,線索在創建之初是可以沒有跟進人(用戶)的,但在之後被跟進的過程中,需要強制綁定一個跟進人(用戶),而用戶脫離線索是不具有存在價值的。同時,本項目中,用戶信息僅作爲線索的歸屬屬性存在,最終我們將 UserEntity(改名爲 UserVO)作爲值對象放置於 ClueEntity 內,且令線索信息實體爲聚合根;然後,分析下 ClueEntity 和 SourceEntity、TagEntity、RecordEntity,主要從兩個方面考慮是否需要組成聚合:
(1)聚合應具備內部一致性,即聚合內對象要麼一起獲取,要麼一起更新,要麼一起刪除。若聚合內任意對象在保存時被修改,都應視爲聚合被修改,此時保存失敗。因此,在定義聚合時,保證合理性的前提下,儘量設計較小聚合。在線索管理中,線索管理人員頻繁爲線索關聯標籤、來源、跟進記錄等信息,從內部一致性角度考慮,三者分開更好。
(2)聚合內聚合根和對象間要保持不變性。何爲不變性?簡單來說,對象之間存在某種不變的規則。舉個例子說明下,x=y+5,如果規定 y 大於 1,那麼 x 一定大於 6。回到線索管理,ClueEntity 和 SourceEntity、TagEntity、RecordEntity 間並不存在這種不變性,因爲任意一個來源、標籤、跟蹤記錄一定有一條對應的線索,但一條線索可以沒有來源、標籤、動態記錄,同時,來源、標籤、跟蹤記錄均可以在各自領域被單獨訪問到。
綜合以上兩點,我們採取如下策略:SourceEntity、TagEntity、RecordEntity 各自定義爲一個聚合,本身作爲聚合根。然而,在查詢線索詳情時,線索包含來源、標籤、跟蹤記錄信息,而 ClueEntity 聚合內不包含這些信息,如何實現信息聚合?我們採用領域服務來實現領域對象間的聚合。
最後,定義倉儲。我們遵循一個聚合對應一個倉儲的原則來定義。最終,我們得到了如下表所示的領域模型。
對應的類圖可見圖 3-9。
通過以上步驟,我們可以構建並完善線索管理的領域模型。在實際項目中,根據需求複雜程度,我們可以靈活運用四色原型圖,並與用例圖、時序圖、狀態圖等相結合,全面準確地描述業務需求。同時,不斷審視和調整領域模型,以確保其合理性和有效性。只有經過嚴謹的分析和設計,我們才能構建出一個符合業務需求的優秀領域模型。
四、總結與思考
本文以 “線索資源管理” 實際項目爲背景,詳細闡述了從需求分析到方案設計階段,如何運用 DDD 理念逐步構建領域模型。首先,在第一章中,我們重點介紹了項目背景及其在教育領域的重要性,並給出了主要業務場景,以便讀者對項目有一個全面瞭解。接下來,在第二章中,我們詳細介紹了 DDD 的分層結構、需求階段可用的四色原型分析法以及方案設計階段所需的幾個 DDD 領域概念。最後,在第三章中,結合前兩章的項目背景和領域概念,我們一步一步地構建了本次項目的領域模型。
在實際項目中,我們可以根據需求複雜程度和項目特點,靈活運用四色原型圖、用例圖、時序圖等工具,結合 DDD 思想,全面、準確地描述業務需求,構建出符合業務需求的優秀領域模型。只有經過嚴謹的分析和設計,我們才能打造出高質量的軟件系統,爲教育領域的發展貢獻力量。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Uf99sKJZ_moi8ygkDlAPGw