DDD 之跨層調用的思考

一、背景

最近通過 COLA 構建籃球運營管理平臺演示源碼的時候對跨層調用做了一些深度思考,在跨層調用中有些調用並不是嚴格按規範或者相對固定的分層模式去走的,這就出現了一些疑問,比如不按規範來我怎麼控制代碼質量,我怎麼知道最佳實踐是什麼?另外一方面的問題是目前實踐 DDD 的代碼和案例確實不是很多,深度集成各種中間件和調用案例的工程也不是很多,大多情況下都是理論 + 摸着石頭過河。本文結合最近的代碼實踐和跟 COLA 作者溝通的一些點來闡述在跨層調用下的場景。

二、對規範的思考

跨層調用在架構風格或者架構思想上目前看不是一個好少年。由於跨層調用很容易帶來工程的不穩定性,比如 dto 的轉換,跨層調用的耦合到後期無法維護。但是在實踐中跨層調用就像幽靈一樣,隨時出現。我們通過之前的架構風格等博文可以知道,在整潔架構,洋蔥架構,嚴格的分層架構中只有外層依賴內層,並不會出現跨層調用。在調用鏈路上每層都有自己的職責。很多同學比較執着於規範,這無可厚非,但是理想化的工程模型在現實中很難全部實現,因此嚴格的跨層調用規範並不適用於所有場景。下面我們通過一些 case 來闡述一些打破跨層調用規範的場景。

三、打破規範的場景

3.1 阿里巴巴 Java 開發規範

WX20210521-220234@2x.png

如上圖是阿里巴巴 Java 代碼開發規範 - 嵩山版,第六章第一節的應用分層的分層架構示意圖。很明顯上圖是一種鬆散分層的模型,在這個鬆散分層中並沒有嚴格強調上層只依賴下層,而是上層與相對下層的鬆散組合。當然這張圖也沒有從 DDD 的方面考慮。下面我們看一下 DDD 的應用分層架構,如下圖是鬆散的分層架構模式

當我們把跨層調用的依賴箭頭去掉,就像一層一層壘積木一樣的,跨層之間沒有關係。根據 DDD 羣的討論,這兩種架構風格都害人不淺,尤其是鬆散的分層架構。

3.2 CQRS 調用

在 CQRS 架構示意圖中,Query 層很明顯是比較靈活的,通常來說單純的應用 CQRS 或者在 DDD 中應用 CQRS 都將面臨查詢是否經過業務層或者領域層。比較複雜的查詢場景有分頁查詢,導出。多表聚合查詢,多數據源聚合查詢,單數據源搜索引擎。相對 Command 而言這些查詢更重,一旦實現也不容易調整,但是有一點好處就是相對穩定,迭代上面如果沒有太多業務邏輯的話比較好維護。另外一點就是這些查詢可以更接近更上層,如應用層或者適配層。爲了兼容性能指標,查詢通常不受 DO,BO 的轉換約束。所以在鬆散的分層架構中融合 CQRS 的 query 是相對容易的。另外再多說一句,當這種跨層調用出現的時候,我們的查詢結果模型應該怎麼定或者怎麼管理?這裏建議底層依然走 DO 模型,但是可以繞過 BO,在應用層用聚合 DTO 來承接。當然另一種方式就是 DAO.mapper 的返回走的是 DTO 模型。不過這樣的話在整個層裏面就顯得有點突出了。

3.3 消息回調

    在消息回調的場景中,這個跨層調用就會顯得很突出。比如在 COLA 中基礎設施層已經集成了 MQ 相關的中間件,那麼收發消息肯定都是經過基礎設施層的服務的。領域服務工程發送消息很明顯是遵守層間調用依賴的,但是消費消息就顯得有點另類了,因爲消息肯定是從基礎設施層出來的。那麼誰去消費這個消息,如果有多業務線的話這個消息是不是先要到應用層處理。另外的問題是應用層不會包含 MQ 中間件的依賴,這樣的話消息的監聽基本就在基礎設施層了。

    在 cola 實踐中 CQRS 模式在應用層集成之後應用層可以依賴基礎設施層服務。但是消費消息的話只能在基礎設施層發起,因此一種可能的解決方案就是在基礎設施層引用應用層,這就形成了一種相互依賴的關係。但是這應該不是最好的解決方法。

    下面我們探索另外一種可能的方案。由於中間存在領域層,而應用層也肯定會依賴領域包。在之前博客帖子領域的代碼實戰中我簡單模擬了下整個領域分包的實踐,在基礎設施層的 mq 領域調用了 app 層的調用,說白了還是在同一個模塊裏,所以衝擊並不大。我現在負責的交易核心中臺項目也沒有單獨將領域層分出來。所以一種可能的方案就是將 app 層 + 領域層 + 基礎設施層融合在一起,通過約定的分包策略進行代碼組織開發。當然,領域層模型可以單獨分模塊出來這樣的話也會好一點。

    那假如我非要在分包策略的基礎上分模塊,根據 cola 的分包模型去搞呢。那消息消費的問題該怎麼解決?這兩天我也進行了一定的思考,結合公司業務場景和交易核心項目工程的代碼案例,消息消費目前細分起來可以分爲應用層消息和領域層消息。下面說明一下這兩個的區別。

領域層消息:領域層消息消費的場景是跟自己的核心模型有關的消息。舉個例子,在交易中心的核心領域模型中有個支付單對接支付中心的業務,那麼支付中心返回的支付結果消息就算是交易中心的領域層消息。

應用層消息:應用層消息消費的場景是跟自己的核心模型沒有多大關係的消息。舉個例子,在交易中心的核心領域模型並沒有某某業務模型或者並沒有這個場景,但是需要監聽其他系統發出的消息來調整自己核心模型的數據和狀態。那麼這裏的應用場景可能就有很多,比如中臺類項目可能就會經常遇到需要對接各種其他業務線的平臺系統,每個平臺系統都會有自己的業務模型來對接中臺的核心領域模型。那麼在中臺類項目的角度來看這些業務線平臺類系統的特定場景就屬於中臺的應用層,這些應用層業務會跟核心領域模型做數據模型的對接。

    大概瞭解這兩類消息的情況下,消息回調在跨層調用和多模塊分包情況下就很好處理了。針對領域層消息消費可以通過領域服務接口來定義消費邏輯,如(gataway)。針對應用層消息消費目前有兩種方案:

  1. 在領域層內單獨通過如 ablility 包作爲消息消費的入口。優點:避免跨層調用,缺點:容易污染領域模型,將特定業務場景混合核心領域模型導致代碼混亂。2. 通過 spring 的應用事件發佈消費機制來實現

優點:消息消費邏輯和觸發邏輯分開,避免跨層調用。缺點:消費邏輯代碼不方便統一管理。產生了二次消息消費和訂閱,代碼不容易理解。

那麼針對第二種方案的可行性我們分析一下:假如採用 rocketmq 作爲消息中間件的話,那麼消息消費的監聽代碼(XXListener)應該是在基礎設施層,然後通過 spring application event 的發佈訂閱機制將消息發佈出去。在應用層的 spring 事件監聽器會監聽到消息,通過應用層再次觸發業務邏輯代碼。由於整個項目肯定已經跟 spring 框架耦合了,所以可以藉助這種機制將消息再次轉發出去。消息體的話可以通過 map 返回也可以在 XXListener 轉成領域層的消息模型到應用層,應用層不用再次進行轉換。總體上來說,消息回調的跨層調用實際上受到很多因素影響,畢竟針對消費的消息去做區分也會帶來理解上的門檻。目前看消息回調的跨層調用還是儘量避免爲好。

3.4 跟隨者(遵奉者)模式

之前在學 DDD 的 40 + 模式的時候有個模式令人印象深刻,雖然不是很出名,但是在服務間調用的時候有一定用處。下面來回顧一下跟隨者模式的定義: 通過嚴格遵從上游團隊的模型,可以消除在 BOUNDED CONTEXT 之間進行轉換的複雜性。儘管這會限制下游設計人員的風格,而且可能不會得到理想的應用程序模型,但選擇 CONFORMITY 模式可以極大地簡化集成。此外,這樣還可以與供應商團隊共享 UBIQUITOUS LANGUAGE。供應商處於統治地位,因此最好使溝通變容易。他們從利他主義的角度出發,會與你分享信息。 eric 的書上大多描述了跟隨者模式在團隊間和上下游合作的一些問題,以及大概的解決辦法。當然應對到代碼層面可能不太好理解。舉個例子,業務訂單系統跟交易系統和支付系統的單據轉換以及上下游的調用就會出現這種協作問題。通常業務訂單系統會按交易系統的模型去做數據轉換,和適配。交易系統會按支付系統的模型做轉換和適配,中間可能會加入防腐層來防治核心領域模型和業務被污染。上面說的是比較常見的情況,下面我們說一下其他兩種場景

  1. 上游的數據我都要,上游的數據模型我直接複製處理 2. 上游的數據我根據下游需要而要,但是我不存,而是調用下游直接給下游。

那麼上面這幾種情況都算是跟隨者模式的例子,在代碼上我可以不需要將上游的模型拿到我自己的領域層去識別。而只需要在 DTO 層面上做適配就可以了,同理我需要調用下游的時候也不需要經過領域層,而是直接通過防腐層或者適配層調用。在 cola 工程模塊中就是應用層 (app) 或者適配層 (adapter) 可以直接調用基礎設施層 (infrast) 的外部服務接口。當然這種跨層調用也是很好處理的,具體實踐起來也沒有多大難度。

3.5 跨層調用的返回值處理

上面說了一些跨層調用的場景,這裏特別說明一下跨層調用的返回值如何處理。一般情況下上游調用下游接口是下游包裝自己業務領域的 data,code,msg。但是有時候上游需要知道下下的返回信息,這樣的話就需要特殊處理一下了。

四、總結

在上述第三節中爲什麼說 DDD 的鬆散分層架構害人不淺呢,我想有以下幾點原因:

  1. 人云亦云,複製粘貼 2. 潛意識的鬆散分層架構很靈活且更容易實踐 3. 由於跨層調用存在,實踐者可以沒有條件和約束的應用這種策略

最終經過跨層調用實踐的代碼很多都難以維護,且在多模塊的複雜系統中跨層調用重構起來更困難。在架構實踐中,合適的纔是最好的。但是並不是隨意的應用跨層調用,也不是嚴格的遵守上下層依賴調用,而是有選擇的,謹慎的根據需求和技術場景等考慮是否進行跨層調用,通常來說 90% 的場景都不需要跨層調用。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/l8-xqgSI6KVRwy1N3w5I3w