【實踐篇】領域驅動設計:DDD工程參考架構

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/05/22/17419966.html
-Advertisement-
Play Games

不同團隊落地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:倉儲介面的實現

最後

應用架構模式的選擇是系統架構設計的重要維度之一,結構不僅僅是簡單的包結構和命名,其傳達的是一種頂層抽象,背後包含了大量的實踐和知識。制定符合團隊情況的工程參考架構,併在團隊成員間達成共識非常重要。領域驅動設計並沒有統一的、通用的架構,試圖定義標準架構是不切實際的。本文描述的工程架構只是一個參考,實踐過程中應該基於團隊特定情況而有所差異,但原則上都應該遵循業務域與技術域分離的核心理念

作者:京東科技 倪新明

內容來源:京東雲開發者社區


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2022年的程式員節, #大齡程式員去哪兒了#成為了社交媒體上最火的話題之一,程式員的職場成長問題在社會上引起了廣泛關註。 有2位在技術領域摸爬滾打很多年的開發者,35歲後的他們,有70後,有80後,依然在編程開發,依然有離職創業的勇氣,努力實現自己的人生價值。走進他們的故事,你會發現,這個世上沒有 ...
  • # React筆記-Hooks(九) ## Hooks ### 概念 >React Hooks 的意思是 組件儘量寫成純函數 如果需要外部功能和副作用 就用鉤子把外部代碼"鉤"進來 ### 函數組件和類組件區別 >- 函數組件沒有狀態(state) 類組件有 >- 函數組件沒有生命周期 類組件有(掛 ...
  • OpenAI於前幾天發佈了IOS版ChatGPT智能App應用。預示著ChatGPT正式踏入了移動設備領域。 現在可以去AppStore下載這款免費、帶有語音設別功能的ChatGPT應用了。 基於vite4.x+vue3+pinia2模仿chatgpt手機端聊天模板Vue3-MobileGPT。 運 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 在實際的開發工作過程中,積累了一些常見又超級好用的 Javascript 技巧和代碼片段,包括整理的其他大神的 JS 使用技巧,今天篩選了 9 個,以供大家參考。 1、動態載入 JS 文件 在一些特殊的場景下,特別是一些庫和框架的開 ...
  • HTTP 1.1相比HTTP 1.0具有以下優點: 1. 持久連接 :HTTP 1.1引入了持久連接機制,允許多個請求和響應復用同一個TCP連接。這樣可以減少建立和關閉連接的開銷,提高性能和效率。2. 流水線處理 :HTTP 1.1支持流水線處理,即可以同時發送多個請求,不需要等待前一個請求的響應。 ...
  • 參考:[Building a WebGL Carousel with React Three Fiber and GSAP](https://tympanus.net/codrops/2023/04/27/building-a-webgl-carousel-with-react-three-fibe ...
  • ## 一、模式動機 迭代器模式(Iterator Pattern)是一種使用頻率非常高的行為型設計模式,**迭代器**用於**對一個聚合對象進行遍歷**。通過**引入迭代器**可以**將數據的遍歷功能從聚合對象中分離出來**,**聚合對象只負責存儲數據**,而**遍曆數據由迭代器來完成**,簡化了聚 ...
  • 之前總聊微服務,今天換一個話題---低代碼。低代碼這個詞也是最近這幾年很火的概念,尤其是遇到大環境下行,很多大廠和互聯網那個公司也在慢慢在低代碼方向發力,當然,對於傳統項目交付型的軟體公司,低代碼也具有相當大的吸引力。 ...
一周排行
    -Advertisement-
    Play Games
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...