領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-driven design,DDD)是一種為復(fù)雜需求開發(fā)軟件的方法,它將軟件的實(shí)現(xiàn)與不斷發(fā)展的核心業(yè)務(wù)概念模型緊密地結(jié)合在一起。
領(lǐng)域是一個(gè)知識(shí)的范疇。它指的是我們的軟件所要模擬的業(yè)務(wù)知識(shí)。領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的中心是領(lǐng)域模型,它對(duì)一個(gè)領(lǐng)域的流程和規(guī)則有著深刻的理解。洋蔥架構(gòu)實(shí)現(xiàn)了這一概念,并極大地改善了代碼的品質(zhì),降低了復(fù)雜性,并且支持不斷發(fā)展的企業(yè)系統(tǒng)。
為什么要用洋蔥架構(gòu)?
領(lǐng)域?qū)嶓w是核心和中心部分。洋蔥架構(gòu)是建立在一個(gè)領(lǐng)域模型上的,其中各層是通過接口連接的。其背后的思想是,在領(lǐng)域?qū)嶓w和業(yè)務(wù)規(guī)則構(gòu)成架構(gòu)的核心部分時(shí),盡可能將外部依賴性保持在外。
它提供了靈活、可持續(xù)和可移植的架構(gòu)。
各層之間沒有緊密的耦合,并且有關(guān)注點(diǎn)的分離。
由于所有的代碼都依賴于更深的層或者中心,所以提供了更好的可維護(hù)性。
提高了整體代碼的可測(cè)試性,因?yàn)閱卧獪y(cè)試可以為單獨(dú)的層創(chuàng)建,而不會(huì)影響到其他的模塊。
框架/技術(shù)可以很容易地改變而不影響核心領(lǐng)域。例如,RabbitMQ 可以被 ActiveMQ 取代,SQL 可以被 MongoDB 取代。
原則
洋蔥架構(gòu)是由多個(gè)同心層構(gòu)成,它們相互連接,并朝向代表領(lǐng)域的核心。它是基于控制反轉(zhuǎn)(Inversion of Control,IoC)的原則。該架構(gòu)并不關(guān)注底層技術(shù)或框架,而是關(guān)注實(shí)際的領(lǐng)域模型。它是基于以下原則:
依賴性
圓圈代表不同的責(zé)任層。一般來(lái)說,我們潛入得越深,就越接近于領(lǐng)域和業(yè)務(wù)規(guī)則。外圈代表機(jī)制,內(nèi)圈代表核心領(lǐng)域邏輯。外層依賴于內(nèi)層,而內(nèi)層則對(duì)外圈一無(wú)所知。通常情況下,屬于外圈的類、方法、變量和源代碼依賴于內(nèi)圈,但是反過來(lái)也一樣。
數(shù)據(jù)格式/結(jié)構(gòu)可能因?qū)佣悺M鈱拥臄?shù)據(jù)格式不應(yīng)該被內(nèi)層使用。例如,API 中使用的數(shù)據(jù)格式可以與 DB 中用于持久化的數(shù)據(jù)格式不同。數(shù)據(jù)流可以使用數(shù)據(jù)傳輸對(duì)象。每當(dāng)數(shù)據(jù)跨層/跨界時(shí),它應(yīng)該以方便該層的形式出現(xiàn)。例如,API 可以有 DTO,DB 層可以有 Entity Objects,這取決于存儲(chǔ)在數(shù)據(jù)庫(kù)中的對(duì)象與領(lǐng)域模型的不同。
數(shù)據(jù)封裝
每個(gè)層/圈封裝或隱藏內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),并向外層公開接口。所有的層也需要提供便于內(nèi)層消費(fèi)的信息。其目的是最小化層與層之間的耦合,最大化跨層垂直切面內(nèi)的耦合 。我們?cè)谳^深的層定義抽象接口,并在最外層提供其具體實(shí)現(xiàn)。這樣可以確保我們專注于領(lǐng)域模型,而不必過多地?fù)?dān)心實(shí)現(xiàn)細(xì)節(jié)。我們還可以使用依賴性注入框架,比如 Spring,在運(yùn)行時(shí)將接口與實(shí)現(xiàn)連接起來(lái)。例如,領(lǐng)域中使用的存儲(chǔ)庫(kù)和應(yīng)用服務(wù)中使用的外部服務(wù)在基礎(chǔ)設(shè)施層實(shí)現(xiàn)。
洋蔥架構(gòu)中的數(shù)據(jù)封裝
關(guān)注點(diǎn)的分離
應(yīng)用被分為若干層,每一層都有一組職責(zé),并解決不同的關(guān)注點(diǎn)。每一層都作為應(yīng)用中的模塊/包/命名空間。
耦合性
低耦合性,可以使一個(gè)模塊與另一個(gè)模塊交互,而不需要關(guān)注另一個(gè)模塊的內(nèi)部。所有的內(nèi)部層都不需要關(guān)注外部層的內(nèi)部實(shí)現(xiàn)。
洋蔥架構(gòu)層
讓我們通過一個(gè)創(chuàng)建訂單的用例來(lái)了解架構(gòu)的不同層和它們的職責(zé)。當(dāng)收到一個(gè)創(chuàng)建訂單的請(qǐng)求時(shí),我們會(huì)對(duì)這個(gè)訂單進(jìn)行驗(yàn)證,將這個(gè)訂單保存在數(shù)據(jù)庫(kù)中,更新所有訂單項(xiàng)目的庫(kù)存,借記訂單金額,最后向客戶發(fā)送訂單完成的通知。
說明各層之間的依賴關(guān)系的包圖
領(lǐng)域模型/實(shí)體
領(lǐng)域?qū)嶓w是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的基本構(gòu)件,它們被用來(lái)在代碼中為通用語(yǔ)言的概念建模。實(shí)體是在問題域中具有唯一身份的領(lǐng)域概念。領(lǐng)域?qū)嶓w封裝了屬性和實(shí)體行為。它應(yīng)該是獨(dú)立于數(shù)據(jù)庫(kù)或網(wǎng)絡(luò) API 等特定技術(shù)的。例如,在訂單領(lǐng)域,訂單是一個(gè)實(shí)體,并具有像 OrderId、Address、UserInfo、OrderItems、PricingInfo 這樣的屬性以及像 AddOrderItems、GetPricingInfo、ValidateOrder 這樣的行為。
訂單實(shí)體類
領(lǐng)域服務(wù)
領(lǐng)域服務(wù)負(fù)責(zé)保持領(lǐng)域邏輯和業(yè)務(wù)規(guī)則。所有的業(yè)務(wù)邏輯應(yīng)該作為領(lǐng)域服務(wù)的一部分來(lái)實(shí)現(xiàn)。領(lǐng)域服務(wù)由應(yīng)用服務(wù)協(xié)調(diào),以服務(wù)于業(yè)務(wù)用例。它們不是典型的 CRUD 服務(wù),通常是獨(dú)立的服務(wù)。領(lǐng)域服務(wù)負(fù)責(zé)復(fù)雜的業(yè)務(wù)規(guī)則,如在處理訂單時(shí)計(jì)算價(jià)格和稅收信息,保存和更新訂單的訂單庫(kù)接口,更新購(gòu)買物品信息的庫(kù)存接口等。
它包含了對(duì)其目標(biāo)非常關(guān)鍵的算法,并且將用例作為應(yīng)用的核心來(lái)實(shí)現(xiàn)。
應(yīng)用服務(wù)
應(yīng)用服務(wù)也被稱為“用例”,是只負(fù)責(zé)協(xié)調(diào)請(qǐng)求步驟的服務(wù),不應(yīng)該有任何業(yè)務(wù)邏輯。應(yīng)用服務(wù)與其他服務(wù)交互,以滿足客戶的請(qǐng)求。讓我們考慮一下用例,用一個(gè)物品清單創(chuàng)建一個(gè)訂單。我們首先需要計(jì)算價(jià)格,包括稅收計(jì)算/折扣等,保存訂單項(xiàng)目并向客戶發(fā)送訂單確認(rèn)通知。定價(jià)計(jì)算應(yīng)該是領(lǐng)域服務(wù)的一部分,但涉及定價(jià)計(jì)算、檢查可用性、保存訂單和通知用戶的協(xié)調(diào)工作應(yīng)該是應(yīng)用服務(wù)的一部分。應(yīng)用服務(wù)只能由基礎(chǔ)設(shè)施服務(wù)調(diào)用。
基礎(chǔ)設(shè)施服務(wù)
基礎(chǔ)設(shè)施服務(wù)也被稱為基礎(chǔ)設(shè)施適配器,是洋蔥架構(gòu)的最外層。這些服務(wù)負(fù)責(zé)與外部世界交互,不解決任何領(lǐng)域的問題。這些服務(wù)只是與外部資源通信,沒有任何邏輯。例如:外部通知服務(wù)、GRPC 服務(wù)器端點(diǎn)、Kafka 事件流適配器、數(shù)據(jù)庫(kù)適配器。
可觀察性服務(wù)
可觀察性服務(wù)負(fù)責(zé)監(jiān)控應(yīng)用。這些服務(wù)有助于執(zhí)行以下任務(wù):
數(shù)據(jù)收集(指標(biāo)、日志、痕跡):主要使用庫(kù)/側(cè)線來(lái)收集代碼執(zhí)行期間的各種數(shù)據(jù)。
數(shù)據(jù)存儲(chǔ):使用能夠集中存儲(chǔ)所收集的數(shù)據(jù)的工具(分類、索引等)。
可視化:使用允許你對(duì)收集的數(shù)據(jù)進(jìn)行可視化的工具。
一些例子包括 Splunk、ELK、Grafana、Graphite、Datadog。
測(cè)試策略
洋蔥架構(gòu)的不同層有不同的職責(zé),相應(yīng)地也有不同的測(cè)試策略。測(cè)試金字塔是一個(gè)很好的框架,它規(guī)定了不同類型的測(cè)試。屬于領(lǐng)域模型、領(lǐng)域服務(wù)和應(yīng)用服務(wù)的業(yè)務(wù)規(guī)則應(yīng)通過單元測(cè)試進(jìn)行測(cè)試。當(dāng)我們移動(dòng)到外層時(shí),在基礎(chǔ)設(shè)施服務(wù)中進(jìn)行集成測(cè)試更有意義。對(duì)于我們的應(yīng)用,端到端測(cè)試和 BDD 是最合適的測(cè)試策略。
針對(duì)不同層的測(cè)試策略
微服務(wù)
當(dāng)孤立地看待每個(gè)微服務(wù)時(shí),洋蔥架構(gòu)也適用于微服務(wù)。每個(gè)微服務(wù)都有自己的模型、自己的用例,并定義了自己的外部接口,用于檢索或修改數(shù)據(jù)。這些接口可以用一個(gè)適配器來(lái)實(shí)現(xiàn),該適配器通過公開 HTTP Rest、GRPC、Thrift Endpoints 等連接到另一個(gè)微服務(wù)。它很適合微服務(wù),在微服務(wù)中,數(shù)據(jù)訪問層不僅包括數(shù)據(jù)庫(kù),還包括例如一個(gè) http 客戶端,以從另一個(gè)微服務(wù),甚至從外部系統(tǒng)獲取數(shù)據(jù)。
應(yīng)用結(jié)構(gòu)和層數(shù)
應(yīng)用結(jié)構(gòu)和層,包括層如何映射到模塊以及它們之間的依賴關(guān)系。它還描述了對(duì)不同層使用什么樣的測(cè)試策略
模塊化與打包
有兩種方法來(lái)組織應(yīng)用的源代碼:
要么,我們可以將所有的包放在一個(gè)模塊/項(xiàng)目中,要么將應(yīng)用分為不同的模塊/項(xiàng)目,每個(gè)模塊/項(xiàng)目負(fù)責(zé)洋蔥架構(gòu)中的一個(gè)層。
這在很大程度上取決于應(yīng)用的復(fù)雜性和項(xiàng)目的規(guī)模,將源代碼分為多個(gè)模塊。在微服務(wù)架構(gòu)中,模塊化可能有意義,也可能沒有意義,這取決于復(fù)雜性和用例。
框架、客戶端和驅(qū)動(dòng)
基礎(chǔ)設(shè)施層由網(wǎng)絡(luò)或服務(wù)器的框架、數(shù)據(jù)庫(kù)的客戶端、隊(duì)列或外部服務(wù)組成。它負(fù)責(zé)配置和縫合所有的外部服務(wù)和框架。洋蔥架構(gòu)提供了解耦功能,因此在任何時(shí)候交換技術(shù)都會(huì)變得更容易。
我們需要每個(gè)層嗎?
將我們的應(yīng)用分層組織有助于實(shí)現(xiàn)關(guān)注點(diǎn)的分離。但我們需要所有的層嗎?也許需要,也許不需要。這取決于用例和應(yīng)用的復(fù)雜性。根據(jù)應(yīng)用的需要,也可以創(chuàng)建更多的抽象層。例如,對(duì)于沒有很多業(yè)務(wù)邏輯的小型應(yīng)用,擁有領(lǐng)域服務(wù)可能沒有意義。無(wú)論哪一層,依賴關(guān)系都應(yīng)該是從外層到內(nèi)層。
總結(jié)
洋蔥架構(gòu)在開始時(shí)可能似乎有些困難,但是在業(yè)界已經(jīng)得到了普遍的認(rèn)可。這是一種讓軟件易于演進(jìn)的強(qiáng)有力架構(gòu)。通過把應(yīng)用劃分為幾層,可以使系統(tǒng)更加易于測(cè)試、維護(hù)和移植。它有助于在舊框架過時(shí)時(shí)輕松采用新框架/技術(shù)。與其他架構(gòu)風(fēng)格類似,如六邊形、分層、簡(jiǎn)潔的架構(gòu)等,它為常見問題提供了一個(gè)解決方案。
審核編輯:劉清
-
適配器
+關(guān)注
關(guān)注
8文章
1956瀏覽量
68045 -
SQL
+關(guān)注
關(guān)注
1文章
764瀏覽量
44157 -
RBAC
+關(guān)注
關(guān)注
0文章
44瀏覽量
9973 -
mongodb
+關(guān)注
關(guān)注
0文章
22瀏覽量
370
原文標(biāo)題:詳解DDD“洋蔥架構(gòu)”
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論