在重構業務系統時,應用領域驅動設計
- 2019 年 10 月 6 日
- 筆記
本文來自「得到」後端開發工程師韓宇斌的一線經歷,通過引入領域驅動設計解決了前期讓人絕望的問題。作者現就職於羅輯思維得到後端,曾效力於好大夫在線、雲智慧等。
一線負責過傳統軟體公司 ToB 類和互聯網公司 ToC 類的業務系統,理解體會過其中的相同與不同,擅長利用 DDD 和 OO 思想對業務需求進行分析建模與設計開發。
下文將圍繞著領域驅動設計中戰略部分的三個核心概念:領域通用語言(UBIQUITOUS LANGUAGE),領域模型(Domain)和限界上下文(Bounded Context),來分享下心得。

倘若說領域驅動設計難,我認為其中的癥結是沒有打通業務與開發溝通的橋樑,各自為政,導致開發對業務傻傻不了解,業務對開發則怨言滿滿。學習領域驅動設計相關的知識有一段時間了,但是一直苦惱於其中的一些概念無法理解透徹,導致無法落地實現甚至生根發芽。
機緣巧合,不久前的工作內容中,需要把之前分散在若干個業務系統中(微服務)的購買相關功能進行梳理重構,在這個重構的過程中,充分運用了領域驅動設計中戰略設計部分的思想,達成了目標。
01
系統居然不能完全解決業務的問題
訂單化系統的前世
入職得到後端不久,團隊交給我一份設計文檔和排期計劃,要求完成個開發任務,實現一個「訂單化」系統。文檔中,該系統的設計目標是:
實現一個代理服務,對接商城平台組的訂單系統和基礎平台組的支付系統,然後推動近若干個業務系統改造,把原來直接調用外部系統的方式,改成調用這個新的代理服務。
讓我們看下文檔中的架構圖,簡潔明了,而我的工作也似乎就是個「體力活」。

如果是剛出道那會,拿到設計文檔,也許我早就不管三七二十一地敲程式碼了。但是,經歷過多年在業務開發線上摸爬滾打,加上對學習面向對象和領域驅動設計的一些領悟,直覺告訴我沒那麼簡單,我應該了解清楚來龍去脈再動手……
經過調研,我終於明白了「訂單化」是什麼。顧名思義,就是把 得到App 內所有的虛擬商品在交付時用標準的訂單號關聯起來?你也許會好奇,一個電商平台居然沒有訂單?我相信「存在即合理」,當時這麼做肯定有當時的原因和背景,說白了一切都是為了快速上線,快速驗證得到App的商業模式,活下去比設計實現一個完美的系統優先順序更高。
沒有訂單的購買機制運行了一年多後,商城平台組實現了訂單系統,經過財務核算部門的「努力」推動,若干後端業務方把虛擬商品的購買對接了訂單系統的三個介面(創建、支付、簽收),這就是最初的訂單化的「萌芽」。
如下圖,以精品課系統舉例,要實現精品課的售賣,該系統要和若干個外部系統直接打交道,如果把別的幾個業務系統的調動關係也畫上,腦補一下這個圖會成為什麼樣子。不管怎樣,財務核算部門的第一步要求是實現了,那就是用訂單號串起來了所有的購買資訊,實現了原始樸素的「訂單化」要求。

如此複雜的調用關係,和「高內聚低耦合」背道而馳,很快就暴露了問題:業務方要求在訂單簽收的時候增加一個簽收時間欄位,並且要求傳遞寫入已購數據表的實際時間。這個很小的需求,據參與的同事說,投入了20多人/日,將近一個月才上線,因為要同步改數個業務系統呢!
團隊嘗到了痛苦,決定改變,於是下決心做一個「訂單化」系統,同時把財務要求的數據校驗規則加上。
02
訂單化系統不能完全解決業務的問題
分析業務規則並讀了一些程式碼後,整理出了訂單化系統的一些分析和設計文檔,經過了團隊內部確認理解正確,找業務方再溝通一下就可以開工了。如下圖,是其中在第三方支付(微信和支付寶)這個場景下的時序圖:

開發工作眼看著就要開始了,我帶著掌握的內容,滿懷信心的去和合作部門(關注訂單化系統的一些「老闆」們)交流,卻感覺大家關注的點甚至方向都常常不一致,越交流內心越分裂。
我作為訂單化系統的負責人是乙方,最關心的是:基於現有確定的需求,如何儘快上線訂單化系統。
而他們甲方關心的是:一定要正確的記賬(面向現在),能夠高效準確的算賬(面向未來),把過去的賬給解釋清楚(面向過去),似乎對「訂單化」系統並不是那麼「感興趣」。
我的目標在財務生態圈裡只是個過程!怎麼達到真正的目標?我該怎麼辦?
那個時間段感受到了雙重的壓力,一面來自於業務方,因為交給我的開發任務居然不能完全解決業務的問題,一面來自於開發團隊內部,領導們不理解為什麼訂單化系統遲遲不能取得顯著進展……
帶著問題,我參與了財務審計對賬工作。開始時,可以用「身陷重圍,十面埋伏」來形容,因為幾乎每天都會被「拷問」,為什麼這麼多問題數據?誰是對應的產品經理呢?得到端誰對權益數據準確性負責呢?讓你們老大招個懂財務的產品經理吧!誰都能聽出來,是對我能否勝任工作的擔憂和不信任……
終於順利出關,完成了公司的要求,自己直接和業務方的夥伴們,面對面的工作近一個月,讓我收穫頗豐:
- 從財務角度,理解了和體會了正確記錄數據的重要性
- 推進已知問題的止損,理解了得到「訂單化」在全局的地位
- 「提煉」了一些「統一語言」
- 自己下決心:不能為了「訂單化」而實現「訂單化」
- 收穫了些許財務思維,和財務相關的數據變動和規則結論,要「記在小本本上」
- 收穫了財務生態圈的信任。信任很關鍵,一個團隊或者跨團隊協作時,信任本身就是生產力。
03
「訂單化」系統演變為了「訂單交付」 系統
領域驅動設計思想指導的開發過程,是一個全程強調「領域模型」的開發過程,首先開發團隊要和領域專家去針對業務需求進行充分的討論溝通,才能確定真正的問題域和業務期望。

主動與業務的溝通
下面的圖,是一次找財務方向的產品經理溝通討論時給我畫的,產品經理說第一次有技術主動和她聊財務相關的業務,一高興就給我講了很多。

為了讓自己的理解和產品經理想要表達的不產生太大的偏差,當天結合這個草圖,趕緊畫了一個自己理解的圖,第二天又去給產品經理講了一遍。反述的過程,自己明白訂單化在全局的位置,雖然貌似不起眼但是卻擔負著得到所有虛擬商品的交付。

經過和業務方的多次交流後,我們逐漸提煉和理解了一些「統一語言」,舉例如下:
- 訂單完整的生命周期:下單,支付,已支付待交付,交付(發貨),簽收
- 確收:收入和交付數據核對無誤,可以確認為財務收入
- 權益:用戶購買虛擬商品後,獲得可以學習對應課程的權利
- 補償:該給權益的時候沒給,要補上權益
回到領域驅動設計,扣一下字眼。
首先,一個領域,解決一個核心問題。任何一個系統都會屬於某個特定的領域;確定了系統所屬的領域,相當於確定了系統的核心目標;確定了系統的核心業務,那麼要解決的關鍵問題、問題的範圍邊界就基本確定了。驅動,我理解的是回答優先順序和孰輕孰重。
前面的「訂單化」系統之所以不能解決業務的問題,就是因為陷入了誤區之一「還沒有確定自己要幹什麼,就陷入技術細節」。

訂單化系統演變為「訂單交付系統」
經過繼續深入調研後,把「訂單化」要完成的內容,劃分成了支付和交付兩部分,如下圖。

重新確定的領域問題是:訂單簽約和履約,正確的交付權益。從全局角度看,就是交易與訂單。交易是行為,訂單是契約,交付是履約。
從得到後端的角度看,核心領域問題是「訂單交付」,所以一個「訂單交付」系統就呼之欲出了。幾乎在同時,公司也確定了要做一個交易中心的中台服務,去和若干支付系統對接,我把他們起名為「交易生態圈」。
下面這個圖,用來說明訂單交付系統和其它系統的關係,在整個得到app中用戶發生購買行為後,一起確保用戶的購買權益及時交付,一起履約訂單這個合約。

04
「訂單交付系統」的設計建模
從前面的內容中我們可以看到,「訂單化」系統的設計,依然沒有使得各個業務系統(諸如精品課、訂閱專欄等)從購買交付的商品售賣場景擺脫出來,導致各個業務系統各自為戰的重複實現了自己不擅長的商品購買交付邏輯,由於缺乏領域知識敏感度,產生的交付數據達不到財務核算的精確要求。

這個其實在領域驅動設計思想中也有理論依據,原有的建模方法陷入了「以用戶為中心」的誤區。DDD 的思想認為,建模不能以用戶為中心作為出發點。在人機交互系統面前,各個系統的領域模型將變得沒有差別,職責會不明,因為無論什麼都可以歸結為「用戶的行為」,以用戶為中心來思考領域模型的思維只是停留在需求的表面,而沒有挖掘出真正的需求的本質。

藉助 DDD 的建模思想指導,進行了重新建模,新模型面對的核心領域模型是「商品」,核心限界上下文是「訂單交付」。

實現後的訂單交付系統,使得從下單到交付,業務系統無需關注,感覺不到訂單的存在。

05
用限界上下文保護領域
確定了領域後,就要保護領域不能隨意被「侵犯」,而保護的依據,就是「限界上下文」。
如下圖,Eric Evans 用細胞來形容限界上下文,因為「細胞之所以能夠存在,是因為細胞膜限定了什麼在細胞內,什麼在細胞外,並且確定了什麼物質可以通過細胞膜。」這裡,細胞代表上下文,而細胞膜代表了包裹上下文的邊界。

對業務上下文和限界的理解不足,很容易切換到以用戶為中心去建立領域模型的心流模式。
例如,人去乘坐飛機,要強調出"機場登機流程管理"這個上下文的重要性,不管到機場之前是什麼角色,人到了登機這個場景就是乘客,是屬於「登機流程」這個上下文的,要遵守這個場景上下文的業務規則和規範,接受「登機流程」的調度指揮,而不是由著自己「肆意妄為」。
由機場「登機流程上下文」業務規則調度,和乘客去主動觸發登記所需要的動作,完全可以表現為兩種設計,偽程式碼如下。
前者
登機流程上下文.排隊(乘客)
登機流程上下文.安檢(乘客)
登機流程上下文.擺渡(乘客,航班)
登機流程上下文.登機(乘客,航班)
後者
乘客.排隊(機場)
乘客.我要安檢(機場)
乘客.我要坐擺渡車(擺渡車)
乘客.我要上飛機(航班)
前者是有序的安全的,不會給機場製造意外,後者機場是不可控的。
在「訂單交付系統」推進的過程中,由於大家立場不同,所以遇到一些來破壞領域的事情也就不足為奇,例如我推進了如下的一些動作來保衛領域,其中有些動作已經完全超越了一名「開發人員」的職責範圍。
一名技術人員敢於對業務內容做決策,離不開對領域知識的把握。

06
總結
DDD 思想指導的開發過程,首先是開發團隊要和領域專家去針對業務需求進行充分的討論溝通,這一點很重要,業務線的開發人員有個不好的習慣:被動接受需求,回頭再來抱怨業務人員或者產品經理沒有表述清楚,人非聖賢孰能無過,合作的就是要互相補位。
在一個 DDD 的一個討論群里,有一位夥伴問,領域驅動設計的價值到底在什麼地方?筆者在公司內做了一次關於領域驅動設計的分享後,同樣有小夥伴問我,學習 DDD 到底能給工作帶來什麼?
有的夥伴說,DDD 難是因為它沒有固定的招式!這不是一個好回答的問題,因為如果好回答,也許DDD早就像敏捷思想、OO(面向對象)思想、MVC、微服務那樣火遍大江南北甚至五湖四海了……
但是如果你些許認真的學習過領域驅動設計,便會發現,到處都有 DDD 的思想。
在這裡要感謝領域驅動設計的佈道者張逸老師,從他的《領域驅動設計實踐》課中受益匪淺,上文這個不好回答的問題就是來自課程群。