一個(gè)電商供應(yīng)鏈系統(tǒng)的DDD實(shí)戰(zhàn)
閱讀本文大概需要 4.5 分鐘。
作者 | 武清明
編輯 | 王一鵬
任何一套業(yè)務(wù)架構(gòu)都可能存在一定的歷史問題,這是業(yè)務(wù)在不同階段做技術(shù)選型必然出現(xiàn)的狀況,如何用新的、合適的架構(gòu)思想做恰到好處地改造,則是架構(gòu)師們的必備能力。本文是 Keep 利用 DDD 改造電商供應(yīng)鏈系統(tǒng)的一次精彩實(shí)戰(zhàn),InfoQ 架構(gòu)頭條獨(dú)家分享,以供大家參考交流。
文章作者:武清明,目前他在 Keep 負(fù)責(zé)商業(yè)化業(yè)務(wù)中臺(tái)研發(fā)和規(guī)劃工作,擅長電商業(yè)務(wù)系統(tǒng)架構(gòu)設(shè)計(jì),采用 DDD 合理簡單化設(shè)計(jì)復(fù)雜電商系統(tǒng),提升系統(tǒng)功能模塊的復(fù)用性和擴(kuò)展性。
今天的主角是供應(yīng)鏈系統(tǒng),又被稱為進(jìn)銷存系統(tǒng)。這個(gè)系統(tǒng)主要是針對(duì)采購(進(jìn))—>入庫(存)—>銷售(銷)動(dòng)態(tài)鏈條的管理系統(tǒng),核心能力是管理倉庫貨物庫存,在電商體系中起到承上啟下的作用,下圖中的 Skynet 系統(tǒng)和 ERP 系統(tǒng)分別扮演著供應(yīng)鏈系統(tǒng)的核心角色,負(fù)責(zé)訂單發(fā)貨、售后退貨、采購補(bǔ)貨、倉間調(diào)撥以及特殊出入庫等核心流程。

Skynet 系統(tǒng)和 ERP 系統(tǒng)作為元老級(jí)系統(tǒng),自 Keep 開啟電商賽道時(shí)開始建設(shè),經(jīng)過多年需求快速迭代,期間系統(tǒng)包袱越來越重,運(yùn)營過程中的問題也越來越多。供應(yīng)鏈系統(tǒng)相對(duì)于 Keep 電商業(yè)務(wù)發(fā)展明顯滯后,甚至有可能進(jìn)一步阻礙 Keep 電商業(yè)務(wù)發(fā)展,而當(dāng)時(shí)的供應(yīng)鏈系統(tǒng)因缺乏系統(tǒng)性規(guī)劃、代碼缺少規(guī)范,導(dǎo)致這個(gè)元老級(jí)系統(tǒng)積重難返。當(dāng)時(shí)面臨的主要問題如下:
系統(tǒng)邊界不清晰
架構(gòu)混亂,系統(tǒng)內(nèi)部分層不清晰
越來越模糊 usecase,導(dǎo)致代碼邊界和事務(wù)不清晰; 分層后各層職責(zé)和接口職責(zé)不清晰,導(dǎo)致接口依賴混亂,甚至出現(xiàn)循環(huán)依賴。

庫存不準(zhǔn),庫存變更上下文不清晰 庫存不準(zhǔn),超賣甚至少賣情況頻繁 庫存變更日志不規(guī)范,上下文不清晰,出現(xiàn)庫存問題時(shí),查找原因困難重重 庫存與庫存變更日志無法自證正確
業(yè)務(wù)新要求 庫存準(zhǔn)確率保障 履約率保障 提升運(yùn)營效率 店鋪庫存分配自動(dòng)化 智能采購
種種問題重壓,在老系統(tǒng)上修改已無法根除系統(tǒng)問題,且無法滿足未來業(yè)務(wù)發(fā)展需求,導(dǎo)致供應(yīng)鏈系統(tǒng)正式提上日程。
重構(gòu)思路主要包括三大類梳理,分別是:
梳理庫存業(yè)務(wù)場(chǎng)景

梳理限界上下文

梳理庫存模型 占用庫存:已售賣未出庫庫存數(shù) 可用庫存:倉庫實(shí)物庫存 - 占用庫存 實(shí)物庫存:倉庫中的實(shí)際庫存數(shù) 在途庫存:已采購未入庫庫存數(shù) 凍結(jié)庫存:因秒殺等促銷活動(dòng)或倉間調(diào)撥等預(yù)占的庫存數(shù)
梳理清楚之后,關(guān)于 DDD 架構(gòu)選型也是要重點(diǎn)考慮的內(nèi)容:
梳理領(lǐng)域模型與非領(lǐng)域模型之間關(guān)系 - 六邊形架構(gòu)

保證核心領(lǐng)域模型的穩(wěn)定性
分層設(shè)計(jì)采用依賴倒置原則,保證核心領(lǐng)域模型的穩(wěn)定性,領(lǐng)域?qū)硬灰蕾嚾魏纹渌麑樱讓臃?wù)可以依賴高層服務(wù)所提供的接口。

防止定制化查詢腐化領(lǐng)域模型
我們通過引入 CQRS 模式,隔離命令與查詢領(lǐng)域模型。

防止與其他限界上下文交互導(dǎo)致領(lǐng)域模型腐化
如下圖所示采購上下文通過防腐層 (ACL) 將倉儲(chǔ)庫存核心上下文中的倉庫信息映射為自身上下文中的倉庫值對(duì)象,防止倉庫信息依賴腐化。

架構(gòu)最終落地 -COLA

庫存變更場(chǎng)景相關(guān)單據(jù)狀態(tài)一致性保障
從庫存變更場(chǎng)景中,可以看到圍繞庫存變更在不同的業(yè)務(wù)層存在不同的業(yè)務(wù)單據(jù),上層業(yè)務(wù)層單據(jù)狀態(tài)變更依賴底層倉儲(chǔ)核心單據(jù)狀態(tài)變更,如采購入庫單入庫狀態(tài)變更為入庫完成則采購單狀態(tài)也會(huì)變更為已完成,如銷售出庫單狀態(tài)變更為出庫完成則銷售發(fā)貨單狀態(tài)會(huì)變更為已發(fā)貨。
方案選擇

最終我們采用 EventStore 方案,使用 EventStore 數(shù)據(jù)流程如下:

上圖中黃色部分為領(lǐng)域事件異常處理。
發(fā)布領(lǐng)域事件代碼如下:



訂閱領(lǐng)域事件 注冊(cè)訂閱組

在訂閱組中聲明訂閱事件

在持續(xù)集成開發(fā)過程中如何同時(shí)保障效率和質(zhì)量 - 單元測(cè)試保駕護(hù)航 核心領(lǐng)域模型添加單元測(cè)試,對(duì)應(yīng) Domain 測(cè)試
核心業(yè)務(wù)接口場(chǎng)景添加單元測(cè)試,對(duì)應(yīng) CmdExe 測(cè)試
引入 Mockito 庫,mock 相關(guān)接口和數(shù)據(jù),驗(yàn)證流程環(huán)節(jié)是否正確
在單測(cè)代碼中造單測(cè)相關(guān)數(shù)據(jù),保證單測(cè)數(shù)據(jù)可靠性
單測(cè)采用 H2 數(shù)據(jù)庫,避免測(cè)試過后留痕,影響后續(xù)單測(cè),同時(shí)提升單測(cè)執(zhí)行效率
減少或不依賴其他中間件,如 Dubbo、Kafka 等,如依賴可考慮直接 Mock
git push 后 CI 開啟自動(dòng)單元測(cè)試
最終,回顧這次改造工作,我認(rèn)為收益可以分為五點(diǎn):
實(shí)際庫存準(zhǔn)確,徹底解決倉庫庫存不準(zhǔn)問題,同時(shí)為校準(zhǔn)銷售庫存提供基準(zhǔn)參考; 功能擴(kuò)展方便,如后續(xù)快速對(duì)接財(cái)務(wù)系統(tǒng); 快速定位問題(代碼結(jié)構(gòu)清晰,庫存變更有據(jù)可查且上下文清晰); 沉淀出較通用的事件組件 EventStore,后續(xù)在 Keep 電商內(nèi)部快速推廣復(fù)用; 沉淀出一套比較成熟的 DDD 最佳實(shí)踐,后續(xù)快速推廣至 Keep 電商庫存系統(tǒng)重構(gòu)、售后重構(gòu)。
可以看出,收益還是非常喜人的。大部分同學(xué)關(guān)注 DDD 是因?yàn)槲⒎?wù),沒錯(cuò),DDD 可以說是與微服務(wù)天生互補(bǔ)的,DDD 領(lǐng)域面向劃分業(yè)務(wù)模型邊界,微服務(wù)面向?qū)误w架構(gòu)拆分為多個(gè)微服務(wù),至于如何拆微服務(wù),DDD 領(lǐng)域拆分則是一個(gè)非常好的微服務(wù)拆分方式。
歡迎關(guān)于 DDD,如果你想進(jìn)一步交流探討,也可以在本文下留言,期待大家的分享能夠帶來更多的啟發(fā)。
作者介紹
武清明,從業(yè) 12 年,近 8 年一直在互聯(lián)網(wǎng)電商行業(yè)一線從事系統(tǒng)研發(fā),之前在京東和萬達(dá)電商負(fù)責(zé)過倉儲(chǔ)系統(tǒng)、訂單系統(tǒng)、促銷系統(tǒng)等研發(fā)工作。目前在 Keep 負(fù)責(zé)商業(yè)化業(yè)務(wù)中臺(tái)研發(fā)和規(guī)劃工作。擅長電商業(yè)務(wù)系統(tǒng)架構(gòu)設(shè)計(jì),采用 DDD 合理簡單化設(shè)計(jì)復(fù)雜電商系統(tǒng),提升系統(tǒng)功能模塊的復(fù)用性和擴(kuò)展性。
推薦閱讀:
Java 性能優(yōu)化的 50 個(gè)細(xì)節(jié)(珍藏版)
微信掃描二維碼,關(guān)注我的公眾號(hào)
朕已閱 

