DDD在大衆點評交易系統演進中的應用

本文整理自美團技術沙龍第73期《基於領域驅動設計(DDD)的架構演進和實踐》,主要介紹了DDD的核心概念、常見的設計思路,並結合DDD介紹大衆點評交易系統的演進過程,最後做了一些總結和思考。希望這些內容能夠對大家有所幫助或啓發。

1 大衆點評交易業務介紹

本文主要涉及境外出行、商場團購和內容商業化等三類交易業務場景。在大衆點評App裏,在境外城市站有美食、購物、商場、景點、門票、當地玩樂等頻道入口,可以購買境外出行交易產品,在境內的逛街/商場頻道可以找到商場團購優惠以及商場團購代金券。

此外,商家如果有推廣需求可以在商家端App(開店寶App)“點星”入口購買達人的創作服務,最終達人交付的筆記,在點評App信息流裏進行展示。具體來說,境外出行產品覆蓋景點門票、餐廳訂座和休閒娛樂;商場團購產品包含普通團單和秒殺團單,適用於商場的優惠活動;內容商業化產品則允許商家購買達人的圖文或視頻筆記,以此來推廣自己的服務或產品。

2 領域驅動設計概述

2.1 什麼是領域驅動設計

領域驅動設計是一種軟件設計方法,它主要用於處理複雜業務需求。我們可以將其分解爲“領域”、“驅動”和“設計”三個部分來理解。“領域”指的是特定的業務範圍或問題域,如電商、醫療、保險等。確定領域後,我們就能明確核心的業務問題。例如,在電商中,核心問題可能涉及商品、庫存、倉儲和物流;在保險領域,則可能關注投保、承保和理賠等方面。

“設計”在DDD中通常指的是領域模型的設計,DDD強調領域模型是系統的核心,它反映了業務概念和業務規則。“驅動”有兩層含義:一是業務問題域驅動領域建模的過程;二是領域模型驅動技術實現或代碼開發的過程。確保領域模型的準確性是關鍵,因爲它可以保證代碼實現能夠真實反映並解決業務的核心問題。

領域驅動設計是一種處理高度複雜領域的設計思想,它通過分離技術實現的複雜性,圍繞業務概念構建領域模型來控制業務的複雜性,以解決軟件難以理解、難以演化等問題。領域驅動設計是一種設計思想,首先體現了分離的思想,它分離了業務複雜性和技術複雜性,其次體現了分治的思想,它通過領域模型、限界上下文或子域進行分治。

2.2 領域驅動設計核心概念

領域驅動設計涉及到的核心概念非常多,我們重點強調一下“統一語言”和“限界上下文”。“統一語言”貫穿領域驅動設計從戰略設計到戰術設計到最後的代碼實現全過程,對於需求分析、知識提煉和最後代碼的實現,都是非常重要的。

“限界上下文”是連接問題空間和解決方案空間的橋樑,一方面我們在問題空間分析問題時,它是語言的邊界和模型的邊界;另一方面,在解決方案空間我們通過限界上下文來確定應用的邊界和技術的邊界,從而幫助我們確定整個系統及各個限界上下文的解決方案。

2.3 領域驅動設計的過程

首先,領域驅動設計需要業務、產品、研發以及QA共同來參與,應基於對問題域以及業務願景的理解,並進行充分討論而達成統一認知,在這過程中提煉領域知識,並建立統一語言。同時在領域知識基礎上進一步提煉,分解問題域爲核心子域、支撐子域和通用子域,再通過模型驅動設計思想,設計領域模型,通過領域模型連接業務和系統,並且在模型驅動設計過程中,會有新的認知迭代。通過這些認知迭代進一步豐富統一語言,因此領域知識是一個不斷迭代、螺旋式推進的過程。

3 大衆點評交易系統演進

點評交易系統的發展歷程從業務視角和技術視角看,分別有三個階段。從業務視角看:

  • 第一階段是單業務線單業務形態階段,這個階段我們只支持了境外出行交易業務場景,包含了預訂的業務形態;
  • 第二個階段是單業務線多業務形態階段,業務形態變得更加豐富;
  • 第三個階段即多業務線多業務形態階段。

而從技術視角看,主要是經歷了包括簡單架構、微服務架構和平臺化架構等三個階段的演進。

3.1 簡單架構階段

這個階段是我們業務和系統起步的階段,當時我們只支持了預訂形態的一兩個品類的交易,整體上相對比較簡單,同時我們當時團隊的規模也很小,爲了快速支持業務上從0到1這個過程中不斷的探索和試錯,我們在技術系統建設的主要思路是按照業務環節對業務功能模塊做了一些簡單的劃分,從而做到能夠快速的迭代和交付。

在這個階段,我們的系統架構也相對簡單,根據業務進行了基礎的拆分。具體來說,接入層分爲商家B端、商品C端和訂單C端,而服務層則劃分爲商家、商品和訂單三個部分。整體上採用了傳統的MVC分層架構。這種架構在項目初期確實展現出了其優勢,即“簡單”和“快”。

然而,隨着業務需求的不斷增加和變得複雜,系統開始暴露出一些問題,主要可以歸納爲兩個方面:

首先,我們採用的是數據驅動設計,通常是先建立數據庫表,這導致模型無法直觀地反映業務實際情況。其次,由於採用了傳統分層架構,我們將數據庫表映射爲持久化對象(PO),然後在服務層通過CRUD操作進行過程式編程。這在多個場景下出現了功能相似但又有所不同的需求時,經常導致重複編寫相似的代碼,最終造成了邏輯上的分散,系統整體的內聚性不足。

以訂單退款邏輯爲例,我們面臨着包括訂單確認前/後退款、履約前/後退款等多種場景,以及需要考慮由用戶(買家)、商家(賣家)、客服和系統等不同角色發起的退款。雖然這些不同場景和角色發起的退款業務邏輯在很大程度上是相似的,但它們之間也存在一些差異。

在傳統的MVC架構模式下,由於缺乏對業務領域的深入理解和沉澱,服務間的調用往往缺乏清晰的結構,導致邏輯交織在一起。此外,研發團隊在系統迭代過程中可能沒有足夠重視高內聚和低耦合的設計原則。因此,系統內部往往會出現多處重複且相似的訂單退款代碼邏輯,這不僅降低了系統的可讀性,也給系統的可維護性帶來了挑戰。  

3.2 微服務化階段

隨着業務品類的增加和業務模式的多樣化,我們的業務和系統複雜度迅速上升,團隊規模也相應擴大。這種複雜性主要由三個因素造成:

首先,業務規模的擴張帶來了系統規模和代碼量的增加;其次,業務需求的累積導致了系統內部的重複代碼、複雜的依賴關係,以及爲了滿足高可用性和高性能需求而引入的各種技術組件和並行、異步解決方案;最後,業務需求的頻繁變動也增加了系統的複雜性。

爲了應對這些挑戰,我們的主要思路是:通過分治的方法來管理軟件規模,利用系統分層和關注點分離的原則來優化系統結構,以及通過隔離變化來應對頻繁的需求迭代。這些策略都是領域驅動設計(DDD)的核心理念,基於此,我們實施了微服務架構的拆分,以更好地管理和控制系統複雜性。

在領域驅動設計的落地方法上,我們參照行業實踐內容並且結合自身的理解,我們將DDD的實施過程劃分爲以下四個階段:

  1. 理解問題域:這個階段的核心是深入分析業務價值、需求以及構建業務概念模型。產出統一語言和子域劃分,確保團隊在業務理解上達成共識。
  2. 識別限界上下文:在這一階段,我們通過組織、業務和應用的邊界來確定限界上下文,並且明確不同上下文間的關係和交互。
  3. 領域建模:包括領域分析、設計建模,以及模型的持續迭代。這個階段的目標是構建能夠反映業務核心概念和規則的模型。
  4. 模型實現:實現階段主要依賴於應用分層架構、微服務架構和應用集成,確保領域模型能夠在系統中得到有效實施。

理解問題域

業務價值分析有助於評估系統的複雜性,並且可以指導我們識別最爲關鍵的業務領域。業務需求分析是一個關鍵的知識提煉過程,其中涉及多種方法和工具,例如事件風暴、四色建模以及用例分析等,我們採用的是相對輕量的用例分析法。

在進行用例分析之前,我們首先需要對業務流程進行細緻的分析。這一步驟通過拆解業務流程和環節,幫助我們發現和識別具體的業務用例。這裏簡化了交易業務流程的分析,因爲大多數人對電商類業務流程較爲熟悉。對於不熟悉的業務領域,我們將需要進行更深入的業務流程和場景分析。

我們將交易業務流程分爲四個主要部分:客戶合作流程、商家上單流程、在線交易流程和資金結算流程。有了這些流程的分解,我們就可以進入到具體的用例分析階段。

在用例分析階段,我們以商家上單流程和在線交易流程爲例來說明。在商家上單流程中,涉及到的主要角色包括商家和運營。商家負責創建新商品、商品上架、商品下架以及更新商品庫存等操作。而運營人員則參與商品審覈,包括審覈通過、審覈駁回、查看審覈列表等關鍵用例。至於在線交易流程,其參與方主要是買家、商家以及客服。買家的行爲包括購買商品、支付、申請退款和查看訂單等,商家則處理訂單確認、發送憑證、覈銷憑證和訂單檢索等關鍵用例。客服則參與售後服務,涉及訂單退款、訂單賠付等核心用例。

在完成業務流程和用例分析之後,我們可以根據相關性對問題進行初步分類,並劃分爲不同的子域,建立統一語言。爲了更好地進行知識提煉,爲識別限界上下文和建立領域模型提供必要的信息,我們需要深入分析每個用例,並制定用例規約來提取關鍵概念。

在實際操作中,我們沒有嚴格制定用例規約,而是使用產品需求文檔中的描述。在技術方案設計階段,我們也會使用類似於時序圖和接口描述的方法來詳細闡述用例。無論採用哪種描述方式,關鍵在於堅持使用統一語言,這對於從描述中提煉出核心概念至關重要。這樣做不僅有助於團隊成員之間的溝通,也便於後續的設計和開發工作。 

在業務流程分析、用例分析以及用例規約的制定和編寫之後,我們對交易業務的領域知識已經有了充分的瞭解,並構建了相應的概念模型。在這個模型中,銷售簽約商家,商家負責商品的創建,用戶選擇商品進行下單,下單購買過程中可能會使用優惠,在訂單完成之後需要財務介入對商家進行結算。

對這些關鍵的概念進行歸類之後,我們識別出了商家、商品、訂單、優惠和結算等幾個子域。這裏或許會產生一個疑問:對於我們已經熟悉的領域,是否真的需要經過這樣複雜的分析和提煉過程來劃分子域?實際上,對於有經驗的架構師而言,確實可以迅速地完成子域的識別和劃分,這也展示了領域驅動設計過程中的一種藝術性。然而,這些系統性的分析步驟確保了即使是不熟悉領域的團隊成員,也能夠準確地理解業務並作出恰當的架構決策。

在問題域分析階段主要的輸出包括兩大部分:一是統一語言,二是子域劃分。

  1. 在統一語言上,通過用例分析我們提煉了商家、買家、商品等統一語言,通過用例規約的整理對統一語言進行了豐富,包括售賣規則、售賣單元、訂單項等等,我們可以使用這些統一語言進行交流並且用於後面的模型設計和代碼實現。
  2. 在子域劃分上,我們最終識別出了如圖所示的這樣幾個子域,結合我們在價值分析階段得到的爲用戶提供一站式服務體驗,以及爲商家提供一體化售賣平臺的這樣的核心價值,我們將商品域和訂單域作爲核心域進行重點建設。

此外,對於子域的劃分方法,可以分別按照業務和組織兩個視角來看,從業務視角上可以按照業務環節或業務方向進行劃分,我們使用的其實就是按照業務環節來劃分的,將商家合作到商品上單再到交易和結算的整個業務流程進行階段劃分,按照劃分出來的每個環節確定子領域。

當目標系統爲客戶提供多個業務方向的產品時,可以根據業務方向進行子領域劃分,比如銀行系統可以從儲蓄、理財、外匯等幾個方向來進行拆分;當目標系統用於企業的管理時,可以從組織視角按照業務職能部門進行劃分。

識別限界上下文

在對問題域進行了充分的分析之後,我們進入了限界上下文識別的階段。前面提到了限界上下文的重要性,它是連接問題空間與解決方案空間的重要橋樑。一方面我們在問題空間分析問題時,它是語言的邊界和模型的邊界,也就是業務的邊界,另外在解決方案空間我們通過限界上下文來確定應用的邊界。

所以我們在限界上下文識別的時候,也主要是從業務邊界和應用邊界兩方面來進行。首先我們基於語義相關性和功能相關性對我們在問題域分析階段所羅列的業務活動進行歸類,優先考慮功能相關性,得到初步的限界上下文劃分,在我們交易系統的分析過程中,這個結果與子域劃分結果基本上是一致的。

那麼限界上下文具體要到什麼粒度呢,這裏跟我們的業務複雜度、技術複雜度以及團隊規模有一定的關係,結合我們的實際情況,我們對商品和訂單這兩個核心域的限界上下文做進一步的識別和劃分。

仔細思考後我們發現,儘管商品和訂單是貫穿整個業務流程的核心概念,但在業務流程的不同階段涉及不同的參與方和關注點,對應到系統能力上的訴求也不盡相同。

以商品爲例,其涉及到商品的創建、審覈發佈以及用戶端的展示銷售等環節。在商品創建階段,商家關注錄單效率和商品製作過程的管理;在審覈階段,運營關注審覈需求和審覈效率;而在展示銷售階段,用戶關注商品信息、價格庫存以及如何做出購買決策。訂單的情形也類似,在購買、履約和售後各個階段,關注點也有所不同。因此,我們對商品和訂單的限界上下文進行了細分,以確保系統設計能夠更精準地滿足各階段的業務需求。

限界上下文的識別過程雖然本質上仍然是對問題域拆分和求解的過程,但同時限界上下文也是應用的邊界和技術的邊界,所以我們也需要考慮一些質量需求和技術因素,不過需要注意的是我們仍然要遵循先業務後技術的原則,並且在考慮技術因素時,仍然要保證領域模型的完整性和一致性。

我們從質量屬性、服務集成和功能複用三個方面對限界上下文做進一步的劃分,以商品計算爲例,商品計算量大、任務多、規則複雜,爲了避免影響正常的商品展示和售賣,所以從展銷上下文進行了拆解。此外,我們的商品和訂單都涉及到要與很多第三方的系統進行對接,這裏面將第三方服務的集成劃分爲單獨的直連上下文,從而隔離三方系統差異對內部商品和訂單相關係統帶來的變化。在功能複用上,我們考慮對多個限界上下文都涉及的功能進行提煉,作爲單獨的一個上下文,比如商家權限上下文。

限界上下文封裝了按照縱向切分的業務能力,那多個限界上下文如何協作來完成一個完整的業務場景呢,這就涉及到限界上下文的映射,按照通信集成模式和團隊協作模式來劃分,有多種映射關係,這裏面我們用到最多的是通過防腐層、開放主機服務和發佈語言三者聯動來隔離上下游的變化、維護整個領域模型的穩定性。

領域建模

在領域建模階段,我們整體上分爲領域分析建模和領域設計建模。首先,主要是對用例以及用例規約和用戶故事進行詳細的分析,從中通過名詞法和動詞法尋找領域概念來構建我們的領域分析模型。在此基礎上,我們基於DDD戰術設計的元模型,識別出這些概念中的實體和值對象,並且根據業務規則的不變性設計聚合。

以訂單爲例,這裏是我們簡化之後的模型,包括訂單、支付單、履約單、憑證以及退款單這樣幾個聚合,在存在狀態變化時,聚合之間通過領域事件進行協作。

模型實現

在完成限界上下文的識別以及領域模型的設計之後,接下來進入到代碼實現階段,那我們如何將具體的業務流程或業務活動映射到我們的系統進行代碼實現呢。這裏我們首先是從業務視角對業務流程和業務活動進行分層結構化拆解,其實我們之前的用例分析和用例規約就是這個拆解過程,在拆解之後,我們按照一定的映射關係將其映射到用戶接口、應用服務、領域服務、聚合和端口的實現上。

最後,我們按照限界上下文劃分微服務,服務內部按照分層架構進行實現。整體上基於關注點分離和SOLID原則,分爲接入層、應用層、領域層和基礎設施層。最終需要維護領域層的穩定性,對上由接入層和應用層來隔離變化,對下由基礎設施層通過依賴倒置的方式來隔離數據以及外部依賴的差異性和變化。

3.3 平臺化階段

隨着業務的不斷髮展,出現了商場團購、內容商業化等更多的交易業務場景,在技術上可以通過平臺化的思路將底層系統能力進行復用來提升各業務的支持效率。同時,DDD的戰略模式也在重點關注組織上如何更好的管理大型業務系統,因此我們可以結合DDD來構建平臺領域模型和業務擴展模型,從而更加高效地完成平臺化改造。

我們主要以業務最爲複雜的境外交易業務作爲基礎的主領域模型,並按照DDD領域建模過程對商場團購和商業化業務進行拆解得到的領域模型與主領域模型進行映射匹配,經過同類項識別、歸併和重組,得到平臺領域模型和各業務的擴展模型。在我們的實際落地過程中,爲了實現在多業務之間進行最大化複用的目標,我們在平臺領域模型的構建上做了進一步的提煉,將平臺領域模型拆解爲基礎領域模型,以及預訂業務模型、團購業務模型等按照業務形態劃分的領域模型。

此外,爲了提升業務BP和平臺團隊的協作效率,在平臺領域模型和業務領域模型劃分的基礎上,我們採用了基於插件化的集成開發模式。通過擴展點的定義,由各業務線在各自的插件包裏基於業務擴展模型進行業務定製化實現,再集成平臺領域模型和業務擴展模型,最後實現完整的業務流程和業務場景。

4 總結和思考

DDD是一種開放的思想體系,其核心在於通過領域模型的建立來引導整個設計過程。

  • 第一,本文認爲戰略設計的重要性可能要高於戰術設計,因爲它涵蓋了對業務流程和核心概念的理解和組織。
  • 第二,領域建模是一個動態的、迭代的過程,而非一成不變的瀑布式流程。這個過程類似於一個建模渦流,從戰略設計到戰術設計,不斷迭代。在戰術設計過程中,如果發現某些方面不合理,就需要對戰略設計做出調整。同樣,子域的劃分和限界上下文的識別也是動態的,需要根據新的發現不斷優化。
  • 第三,DDD不強迫採用特定的架構模式,它關注的是業務與技術複雜性是否得到了有效分離。無論是整潔架構、六邊形架構還是傳統的DDD分層架構,只要能夠實現這一目標,它們都是可行的選擇,即便是採用MVC分層架構,只要能夠分離業務和技術複雜性,也同樣適用。

最後,我們來簡要強調一下工程師的思維模型,這些在領域驅動設計(DDD)的實施過程中也至關重要。一方面,工程師需要培養用戶思維、業務思維和產品思維,這有助於深入理解業務和問題域。基於這樣的理解,工程師可以運用結構化思維來分解問題,並通過抽象思維來提煉模型。另一方面,結合分層、分治和工程思維,工程師可以有效地將設計轉化爲實際的代碼實現。

5 參考資料

  • [1]《解構領域驅動設計》-- 張逸
  • [2]《領域驅動設計-軟件核心複雜性應對之道》-- Eric Evans
  • [3]《領域驅動設計模式、原理與實踐》-- Scott Millett, Nick Tune
  • [4]《Business Model Generation》-- Alexander Osterwalder

| 在美團公衆號菜單欄對話框回覆【2023年貨】、【2022年貨】、【2021年貨】、【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可查看美團技術團隊歷年技術文章合集。

| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行爲,請發送郵件至[email protected]申請授權。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章