不同團隊落地DDD所採取的應用架構風格可能不同,並沒有統一的、標準的DDD工程架構。即使無法制定通用的、標準的工程應用架構,但為團隊制定一個遵循領域驅動設計思想的參考架構依然有價值。 ...
背景
為什麼要制定參考工程架構
不同團隊落地DDD所採取的應用架構風格可能不同,並沒有統一的、標準的DDD工程架構。有些團隊可能遵循經典的DDD四層架構,或改進的DDD四層架構,有些團隊可能綜合考慮分層架構、整潔架構、六邊形架構等多種架構風格,有些在實踐中可能引入CQRS解決讀模型與寫模型的差異化等等。即使無法制定通用的、標準的工程應用架構,但為團隊制定一個遵循領域驅動設計思想的參考架構依然有價值。基於以下原因:
- 為團隊實踐DDD的戰術設計提供可以快速開始的工程參考
- 參考工程大量的命名和結構決策,顯式的體現DDD的相關理念,有利於團隊對DDD的戰術實現達成一致認知
- 同時,參考架構有助於沉澱團隊對領域驅動設計的一些思考和最佳實踐
參考架構的考量因素
雖然無法制定完全通用的DDD參考架構,但制定某個特定上下文下的參考架構卻具有可行性和實踐價值。針對於上下文的選擇要儘量貼合實際的工程實踐場景並考慮多維度的因素。
本文所述參考工程架構遵循以下原則:
- 遵循領域驅動設計的本質思想
- 充分考慮業務系統建設特點
- 依賴最小化,保持輕量
希望工程參考架構能涵蓋以下範圍
- 分離業務域與技術域
參考架構要遵循技術和業務隔離的特性,可以參考多種架構風格。業務與技術關註點的分離並不是DDD獨有的特點,在六邊形、整潔架構、洋蔥架構中都遵循了這一重要原則。
- 多限界上下文場景
大多數團隊基於DDD進行微服務拆分的時候,特別是系統建設初期,對單個微服務應用內的限界上下文的粒度需要權衡。由於團隊組織架構因素及微服務成本問題,單個應用容納的限界上下文一般是多個(理想情況下是1:1)。這些限界上下文隨著後續的逐步迭代有可能會遷移至獨立應用。因此,參考架構將多上下文的應用場景作為重要考量因素。
- 明確的組件、職責邊界及依賴關係
- 支持領域報表場景:報表場景在業務系統較為常見,DDD並沒有體現該場景的處理方式。作為工程參考架構,還是希望能夠從實際業務出發,體現對寫模型和報表模型的顯示支持
- 外部依賴最小化:需要排除不必要的依賴,保持工程架構的輕量化
參考架構剖析
應用的多上下文結構
基於以上原則,參考工程考慮單個應用內多上下文的場景,以期在模塊化和服務粒度及成本間進行權衡折衷。應用架構對多上下文的支持的邏輯示意圖如下所示,在解決方案域對限界上下文進行識別和劃分之後,基於其業務內聚性和關聯性,把多個上下文實現單個工程應用中。單個應用內的多個限界上下文間可能存在交互,交互的形式可以是基於事件驅動,也可以是基於進程內調用。採用事件驅動的方式上下文間的耦合性對低一些,但一般需要引入事件匯流排的支持,額外組件的引入必然會導致複雜性的上升。進程內調用則耦合性會高一些,但從實現角度複雜度會低一些。具體選擇哪種方式開發人員可以基於實際情況進行權衡。
需要再次說明的是,這種應用架構決策是一種多因素的權衡,並沒有與子域與限界上下文1:1的理想化實踐保持一致。
從上圖的邏輯示意圖我們再深入一層,從分層的維度去剖析一下詳細的應用架構的展現形式,如下圖所示:
分層關註點
客戶端
客戶端與應用處於不同的進程,是應用能力的消費端,在實際項目中可能是APP端、PC端、小程式端、公眾號端或三方的業務調用端等等。
接入層
接入層是外部系統與應用內部業務能力的中間層,接入層是應用層對外的門面,是當前應用對外暴露業務能力的入口。該層的組成可能是對外提供的HTTP介面聲明、分散式定時任務調度、消息監聽器、RPC服務等等。其重要職責包括對外部系統的請求進行基礎的參數校驗、入參適配和服務路由(轉發至系一層的應用服務)以及響應數據的適配。
業務層:
該層是應用的業務邏輯所在層,整個架構風格採用模塊化單體風格,在該層不同的限界上下文體現為不同的模塊。在每個限界上下文內採用分層架構,獨立劃分為應用層、領域層和網關層。
應用層:
協調領域對象、領域服務或外部依賴服務完成業務用例,該層只做能力協調,不處理任何領域邏輯。
領域層:
領域層是整個分層的核心,與技術實現無關,主要負責領域模型、領域事件、領域服務定義,以及業務相關外部服務的介面抽象以及倉庫的介面抽象等。
領域層與應用服務的本質區別是:應用層不包含領域邏輯,領域邏輯全部下沉到領域層實現。
網關層:
網關層定位是應用的出口網關,是應用與外部基礎設施交互的防腐層,處理所有技術相關實現。
該組件的命名有多種方式,比如有些團隊將其命名為 “rpc”,也有些團隊將其命名為 “infrastructure”,不同的命名體現了團隊對其背後所表達的隱喻的決策選擇。在本文的參考架構中選擇了 網關-Gateway這一命名,決策原因是:限界上下文自身是高內聚的,其與外部的交互需要統一齣口,Gateway所表達的網關的含義恰當的表現了這種統一齣口的理念。如果Facade層是應用的北向網關,是外部系統請求進入內部的入口。則此時的Gateway則表達的是限界上下文的南向網關,是內部應用連接外部的出口。
組件及依賴
從巨集觀的分層我們再深入一層看下每層的組件劃分。如下圖所示:
Start組件:
整個應用的啟動入口、載入應用配置信息等等。
Common組件
提供在不同的限界上下文間復用的領域模型元素的抽象,比如對Command、Query、Event、Entity、ValueObjec通用抽象等。當然,領域模型的通用抽象不是必須在Common組件內以提供復用,也可以作為一個獨立的限界上下文,並以共用內核方式與其它上下文進行共用,或者也可以實現為獨立的jar包組件。
API 組件
RPC類型服務的介面聲明組件,以公司內部使用的JSF為例,該組件是應用對外部系統暴露的JSF API的組件。該組件可以是獨立的工程,當然,有些團隊會將其作為一個Module放入應用工程中。
統一門面組件:Facade
外部客戶端觸達應用系統的入口,也是內部應用服務的統一門面,類似於六邊形架構風格下的適配器。參考架構中基於不同場景劃分為 provider(RPC服務)、task(定時任務)、listener(MQ監聽)、rest(http介面)等幾個子包。外部請求進入系統後,由Facade組件完成入參基本校驗、入參轉換、服務路由以及出參轉換等操作。另外,還可以承擔處理登錄態、鑒權以及日誌等相關能力。
應用服務組件:Application Service
應用服務代表著用例以及系統行為,其通過委托到領域層和基礎設施層(參考架構中的Gateway組件)完成用例的應用邏輯邏輯處理,可以理解為應用服務是領域層的客戶端。該組件典型的職責:
從存儲層載入領域對象、委托領域對象執行領域邏輯、保存領域對象
- 重要事件通知到外部
- 出入參轉化適配
- 事務處理外部
- 非領域邏輯的服務調用
External API
應用服務的邏輯不僅僅需要協調領域層,有時還需要依賴於外部的三方服務。External API 組件負責對這些外部服務進行介面聲明定義,不做具體實現。
應用服務組件不直接依賴這些外部服務實現,而是依賴其介面抽象。同時,此處的模型定義是基於該限界上下文的語義,是一種對外部模型的適配。
該組件不依賴其它組件,且僅被應用服務組件和Gateway組件依賴。網關組件依賴External API 組件的介面聲明並提供底層技術實現,應用服務組件依賴其介面,並通過IOC方式將具體實現註入完成服務調用。
註意,該組件所依賴的服務不涉及領域邏輯,只是用於支撐應用服務的編排。如果是涉及了領域邏輯,則對外部服務依賴的介面定義需要下沉到Domain層。
Query
Query組件解決領域相關的報表查詢場景,在限界上下文內作為與應用服務對等的組件存在,兩個組件分別負責業務的查詢和命令邏輯。
雖然引入了Query組件對報表場景提供支持,但沒有完全的引入CQRS這一模式。在很多資料中CQRS與DDD同時提及的概率比較高,因為,在DDD下我們解決了複雜的面向領域的寫側模型,但在報表場景下,這種富領域模型有可能並不是最佳選擇。如果讀側和寫側都基於統一的領域模型,一般會導致領域模型的折衷設計。為了滿足查詢側訴求,領域模型不得不引入額外的、領域無關的屬性,由此造成領域模型的污染。
Domain
Domain組件是領域邏輯核心,承擔整個系統領域邏輯的實現,其定義了領域模型、領域服務、領域事件以及倉儲層的抽象。該組件不依賴其它組件(除了通用的領域模型抽象組件Common之外。
上圖所體現的參考架構使用了DDD的戰術設計的經典建模元素,比如聚合、實體、值對象、倉儲、工廠以及領域事件等。在實際落地過程中,這些設計元素的抽象具有一定的挑戰,設計過程中需要經過不斷分析、權衡和重構以完成建模,這正是核心設計所在。
Gateway
網關層承擔整個技術相關性的實現,是內部應用的出口網關。
技術相關性是網關組件區別於其它組件的根本特性。在該組件內要處理技術實現的所有細節,比如與外部服務、中間件、DB的交互等。同時,其與Domain組件以及External API組件的介面抽象協作,共同承擔了系統與外部依賴間(包括外部服務以及應用依賴的中間件、DB等基礎設施)的防腐層職能,負責內部模型到外部模型轉化、外部模型到內部模型轉化以及具體交互。基於網關組件的特性,也非常適合在該層做統一的外部服務數據緩存及降級熔斷處理。
網關層依賴於Domain、Application Service、External API和Query組件,負責上述四個組件定義的介面實現。在Gateway組件通過子包對實現進行隔離:
- query:查詢服務組件的實現
- external:External API 組件中依賴外部服務的介面實現
- repository:倉儲介面的實現
最後
應用架構模式的選擇是系統架構設計的重要維度之一,結構不僅僅是簡單的包結構和命名,其傳達的是一種頂層抽象,背後包含了大量的實踐和知識。制定符合團隊情況的工程參考架構,併在團隊成員間達成共識非常重要。領域驅動設計並沒有統一的、通用的架構,試圖定義標準架構是不切實際的。本文描述的工程架構只是一個參考,實踐過程中應該基於團隊特定情況而有所差異,但原則上都應該遵循業務域與技術域分離的核心理念。
作者:京東科技 倪新明
內容來源:京東雲開發者社區