一. 背景
本文預算管控服務建設作為一個DDD設計的例子介紹,目标是是呈現一次DDD設計的過程,為了減少繪圖和描述的工作量,文中會對預算管控業務需求和功能做簡化。請重點關注設計的流程,這是我們想傳達的重點,忽略設計細節的合理性。
另外,對于預算管控服務來講,不一定要用DDD來進行分析設計,基于傳統的資料驅動就完全可以滿足需求,但作為介紹DDD實施過程,預算管控是一個不錯的例子(不需要畫太多的圖)。在這裡我們不讨論什麼類型項目合适DDD,可以參考:
大緻的共識為複雜度高的業務适合DDD。而複雜度一般展現在:
業務流程長
業務場景多
業務概念多
業務系統幹系人多
業務系統需要長期維護且持續有變更
業務背景
需要設計一個适用于本地生活場景的資源預算規劃和管控服務,業務需求上主要包括兩方面的用例:
- 品牌發放權益需要有一定的限額,不能無限制的發放。包括品牌、門店、活動、人群、權益等次元
- 個人消費者參與活動領取權益有次數的限制,不能無限額領取或使用。包括在活動、品牌、門店、商品等次元
目前各業務線針對以上需求,各自實作了部分能力,整體上看較零碎、不完善、不統一。本次目标是設計一個統一的平台為各業務方提供基礎能力
二. 戰略設計
2.1 業務梳理
2.1.1 業務定位&目标分析
協同分析階段,需要各幹系方共同參與,如,業務營運,業務産品,營運産品,平台架構,業務系統方的技術等。
目标:聚焦業務需求和平台定位,确定平台的能力範圍和服務方式
輸出需求文檔:
- 提供一個統一的記賬能力,以平台的方式為各個系統提供記賬服務。
主要功能:
- 記賬
- 銷賬
- 各次元的查賬
- 庫存建立
- 庫存扣減、查詢
- 庫存縮擴容
細化要求:
- 作為平台能為客戶提供邏輯上的資料隔離,即A産品方預設不能通路B産品方資料。如需要通路需要經過授權同意
- 作為平台需要提供同步記賬能力和異步記賬能力,并提供明确的“能力範圍”承諾
- 作為平台需要為産品方提供方案避免重複記賬
- 除記賬之外,需要提供對應的銷賬能力
- 需要提供常用的記賬周期(賬期),如時賬,日賬,周賬,月賬,季度賬,年度賬,終身賬。
- 需要提供自定義記賬周期(賬期)的能力
- 需要提供一單多賬記賬能力,即一張單據,需要同時記錄日賬和終身賬
- 需要提供多元度的查賬能力,如按産品方,記賬主體,産品,賬期時間,以及基于這些次元的組合條件查賬
- 需要提供批量查賬能力,如主體下單一産品的批量賬期時間,主體下的批量産品的單一賬期時間,及其它可能的批量組合
- 技術上需要保證賬單存儲和記賬動作的事務
- 技術上需要保證分庫分表的資料存儲均衡性
- 技術上需要盡量保證分庫分表的資料庫讀寫均衡分布,對可能出現的資料傾斜場景,需要給出明确的說明,和使用限制性規範
- 能提供性能基準承諾,由測試團隊對典型場景壓測給出《平台性能報告》,作為平台對外服務的一部分
- 庫存的建立,扣減,查詢,擴容,縮容(縮容量不能少于剩餘庫存)
- 庫存當機,解凍
- 庫存管理
- 庫存擴容
- 庫存縮容
- 庫存扣減
- 庫存回補
- 庫存查詢
- 滿足去UMP的所有要求(去UMP為一個内部項目,各種限定型規則在此不細列)
2.1.2 業務抽象可視化
通過事件風暴或四色模組化法來可視化。我們這裡選擇事件風暴法。過程主要涉及
- 識别領域名詞(示意,不包括全部)
- 識别領域指令(示意,不包括全部)
這裡列了主要的指令
- 場景分析:
主要是識别發出指令的主體是誰,如C端消費者,B端消費者還是某個系統。主要是通個主體在具體Usecase中去串聯指令對于領域對象(對應領域名詞)的影響。串聯業務流程完成領域分析
- 識别領域事件
在指令發出後對一個領域對象(聚合根)将産生影響,往往對内(聚合根)會生成資料或發生狀态變更;對外(向其他聚合根)發送消息或觸發事件。
這些事件是業務專家重點關心的結果
這裡是先識别領域事件,還是先識别指令可以根據設計者的習慣和熟悉度,自行選擇
最後,整合指令,領域對象和領域事件的關系,得到業務梳理的輸出文檔(實際指令可能比圖中多,如庫存當機和預扣等):
2.2 統一領域語言(示意,不包括全部)
2.1章中幾個階段是一個來回讨論的階段,通常需要經過很多輪的修改和妥協,以至于早期列出的領域名詞、領域事件和指令遠多于上面的圖例,但最後大家需要統一确定其中關鍵的領域名詞、領域事件,并統一領域語言,在後續的讨論和設計階段均使用統一語言模組化。這裡我們用下面的統一語言僅示例産品賬:
術語 | 描述 |
記賬主體(principal)(mainPrincipal)(subPrincipal) | 記賬主體(id),如,抽獎活動中的消費者記賬,則為cid |
賬單(accounting document)(accounting doc) | 名詞,一次記賬請求送出的資料為一條記錄。指産品方送出給記賬平台的原始單據資料 |
記賬(keep account) | 動詞,記錄record的過程 |
銷賬(write off account) | 動詞組,記賬的反向操作 |
金額(amount) | 記賬的數量 |
賬(account) | 按賬期 統計的在該周期内的數額總和相關資料 |
賬期(account cycle) | 賬期(會計周期)的類型,如,日賬,月賬,終身賬等 |
賬期值(account cycle value) | 賬期值。如對于自然日類型的賬期,賬期值可以是“20210415”代表4月15這天的賬 |
記賬類型(operate type) | 操作類型指,記賬或銷賬 |
2.3 限界上文識别
最後,當領域名詞、領域事件和指令都統一并清理好之後,我們需要圈定合适領域出來,這裡要注意,并沒有統一的最佳答案,圈定原則隻是遵循現實世界的松緊耦合關系,某些場景下可能有多種選擇,本例較簡單,示例如下
2.4 問題子域識别
在戰略設計階段的最後,按“一個子域負責解決一個獨立業務價值的問題”的原則,将限界上下文劃分到不同的問題子域(Subdomain)中,同時還需要從更大的域視角來俯覽全局,并按照以下三種類型進行标注:
- 核心域(Core Domain):是目前産品的核心差異化競争力,是整個業務的盈利來源和基石,如果核心域不存在,那麼整個業務就不能運作。對于核心域,需要投入最優勢的資源(包括能力高的人),和做嚴謹良好的設計。
- 通用子域(Generic Subdomain):該類問題在界内非常常見,是以很可能有現成的解決方案,通過購買或簡單修改的方式就可以使用。
- 支撐子域(Supporting Subdomain):該類問題解決的是支撐核心域運作的問題,其重要程度不如核心域,又不屬于通用子域,具備強烈的個性化需求,難以在業内找到現成的解決方案,需要專門的團隊定制開發。
問題子域,是對業務問題的進一步澄清和劃分,同時也是對于資源投入優先級的重要參考,相對限界上下文來說,問題子域是對業務問題更大粒度的劃分,是在限界上下文識别後與問題域比對的一個過程。
通過對于子域進行識别、劃分和類型标注,團隊能夠實作軟體架構在業務邊界上的内聚和解耦,便于逆向應用“康威定律”。
在 DDD 的概念中,限界上下文和問題子域是兩個不同次元的概念,限界上下文可能隻是真實問題子域的一部分表達,也可能限界上下文中的一些領域名詞超出實際問題子域的範圍,理論上來說沒有絕對的依賴關系。需要根據實際需求和成本綜合考慮,既要保證便資源配置設定的合理,又要在降低落地成本的同時保證後期演進的适度相容。
問題子域識别過程的産出物,如下圖所示:
2.5 限界上下文映射(示意,不包括全部)
這裡隻示例産品賬的。明确限界上下文映射關系,是為了更明确各context之間的關系,在IDDD中給出了9種關系,在本例種隻涉及到3種,實際項目中可能比這個複雜的多,尤其是涉及內建和遺留系統的場景。
明确contex之間關系,有助于後續保證系統之間的依賴關系,為後續架構模式的補充子產品做好準備。
三. 戰術設計
3.1領域模組化
3.1.1 領域對象提取(聚合/實體識别)
偷個懶,這裡隻示意産品賬的實體和部分值對象
3.2 業務服務識别
業務服務識别,是為後續系統實作進行的基于業務邊界的子產品拆分分析,常見的拆分方法有:
- 基于限界上下文進行拆分:每個限界上下文為一個服務,優點是每個服務都很小,代碼量少;缺點是拆分粒度太細,導緻服務數量過多,增加架構設計的複雜度和運維成本。
- 基于子域進行拆分:每個子域為一個服務,優點是服務數量相對較少,架構複雜度和運維成本相對更低;缺點是拆分粒度在某些場景下會非常大,導緻單個服務變成“小單體”,增加開發成本和代碼分層複雜度。
通過對于業務服務進行劃分,團隊能夠獲得對軟體架構子產品拆分的直接指導,并且還能夠依據“逆康威定律”依據架構結果進行開發團隊的劃分群組建。
下面是預算管控子域的服務拆分示例
子域 | 服務 |
預算管控子域 | 庫存服務 |
産品賬服務 |
3.3 業務服務接口識别
單獨對業務服務的接口能力進行識别,是符合面向接口程式設計原則的,提前定義服務的概要設計方案,可以讓後續團隊成員更快開展工作,也友善後續接口的詳細設計
這裡提前識别服務接口,是為了避開技術實作細節的影響。我們在基于具體技術實作的情況下設計接口,通常會幹擾領域驅動的設計。我們試想下基于swagger文檔,設計API時,我們是否容易保證API的歸屬正确領域服務。是以提前的概要識别和設定很重要
下面是庫存和賬服務接口識别示例:
聚合根/實體 | 接口能力 | 讀寫 | |
賬上下文 | 賬單 | 寫 | |
賬本 | 單主體單産品單賬期查賬 | 讀 | |
單主體批量産品單賬期查賬 | |||
庫存上下文 | 庫存 | 建立庫存 | |
扣減庫存 | |||
縮擴容庫存 | |||
查詢庫存 |
四. 技術實作
在完成了戰略設計和戰術設計之後,就可以考慮具體的技術詳設,這個階段會設計到具體的架構模式選擇,架構風格和基礎技術,存儲等的選擇。
包括且不限于:
- 架構風格選擇,單體,soa,微服務,restful,rpc,webservice,ODATA等
- 架構模式選擇,傳統分層,六邊形,簡潔,洋蔥等
- 補全元件,如rpc用戶端,mtop,gatway,acl等,這裡要厘清應用層,,基礎設施和領域
- 技術架構選型,技術棧,服務治理體系
- API設計,openapi,swagger,blueprint等
- 領域模型類設計,參考領域模型設計類圖
- 持久化選擇,這裡要考慮哪些需要存儲RDB,哪些用Nosql,哪些隻需要記憶體中。在上例産品賬中的賬本就不需要持久化
- 應用層設計模式選擇,因應用需要,或營運政策需要支援能力要考慮合适的模式支援
- 考慮其他需求的實作,易測試性,性能,易維護和運維,安全等
在本例裡隻示例産品賬的領域模型參考:
其中賬本(accountbook)不需要持久化,其他領域對象均需要持久化
五. 總結
最後需要時刻提醒的。沒到最後實作階段之前應該杜絕提前考慮技術細節和技術實作,否則很容易偏離DDD