最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

一篇帶你入門DDD實戰(zhàn)

2023-05-06 20:44 作者:程序員鶴涵  | 我要投稿

DDD理論

微服務和DDD的淵源

軟件架構模式的演進


我們先來分析一下軟件架構模式演進的三個階段。

第一階段是單機架構:采用面向過程的設計方法,系統(tǒng)包括客戶端UI層和數(shù)據(jù)庫兩層,采用C/S架構模式,整個系統(tǒng)圍繞數(shù)據(jù)庫驅動設計和開發(fā),并且總是從設計數(shù)據(jù)庫和字段開始。

第二階段是集中式架構:采用面向對象的設計方法,系統(tǒng)包括業(yè)務接入層、業(yè)務邏輯層和數(shù)據(jù)庫層,采用經典的三層架構,也有部分應用采用傳統(tǒng)的SOA架構。這種架構容易使系統(tǒng)變得臃腫,可擴展性和彈性伸縮性差。

第三階段是分布式微服務架構:隨著微服務架構理念的提出,集中式架構正向分布式微服務架構演進。微服務架構可以很好地實現(xiàn)應用之間的解耦,解決單體應用擴展性和彈性伸縮能力不足的問題。

在單機和集中式架構時代,系統(tǒng)分析、設計和開發(fā)往往是獨立、分階段割裂進行的。

很容易導致需求、設計與代碼實現(xiàn)的不一致,往往到了軟件上線后,我們才發(fā)現(xiàn)很多功能并不是自己想要的,或者做出來的功能跟自己提出的需求偏差太大

微服務設計和拆分的困境

那進入微服務架構時代以后,微服務確實也解決了原來采用集中式架構的單體應用的很多問題,比如擴展性、彈性伸縮能力、小規(guī)模團隊的敏捷開發(fā)等等。

微服務的粒度應該多大呀?微服務到底應該如何拆分和設計呢?微服務的邊界應該在哪里?

2004年埃里克·埃文斯(Eric Evans)發(fā)表了《領域驅動設計》(Domain-Driven Design –Tackling Complexity in the Heart of Software)這本書,從此領域驅動設計(Domain Driven Design,簡稱DDD)誕生。DDD核心思想是通過領域驅動設計方法定義領域模型,從而確定業(yè)務和應用邊界,保證業(yè)務模型與代碼模型的一致性。

但DDD提出后在軟件開發(fā)領域一直都是“雷聲大,雨點小”!直到Martin Fowler提出微服務架構,DDD才真正迎來了自己的時代。

利用DDD設計方法來建立領域模型,劃分領域邊界,再根據(jù)這些領域邊界從業(yè)務視角來劃分微服務邊界。而按照DDD方法設計出的微服務的業(yè)務和應用邊界都非常合理,可以很好地實現(xiàn)微服務內部和外部的“高內聚、低耦合”。

現(xiàn)在,很多大型互聯(lián)網(wǎng)企業(yè)已經將DDD設計方法作為微服務的主流設計方法了。DDD也開始真正火爆起來。

為什么DDD適合微服務

DDD是一種處理高度復雜領域的設計思想,它試圖分離技術實現(xiàn)的復雜性,并圍繞業(yè)務概念構建領域模型來控制業(yè)務的復雜性,以解決軟件難以理解,難以演進的問題。DDD不是架構,而是一種架構設計方法論,它通過邊界劃分將復雜業(yè)務領域簡單化,幫我們設計出清晰的領域和應用邊界,可以很容易地實現(xiàn)架構演進。

DDD包括戰(zhàn)略設計和戰(zhàn)術設計兩部分。

戰(zhàn)略設計:主要從業(yè)務視角出發(fā),建立業(yè)務領域模型,劃分領域邊界,建立通用語言的限界上下文,限界上下文可以作為微服務設計的參考邊界。

戰(zhàn)術設計:則從技術視角出發(fā),側重于領域模型的技術實現(xiàn),完成軟件開發(fā)和落地,包括:聚合根、實體、值對象、領域服務、應用服務和資源庫等代碼邏輯的設計和實現(xiàn)。

我們不妨來看看DDD是如何進行戰(zhàn)略設計的。

DDD戰(zhàn)略設計會建立領域模型,領域模型可以用于指導微服務的設計和拆分。

事件風暴是建立領域模型的主要方法,它是一個從發(fā)散到收斂的過程。

它通常采用用例分析、場景分析和用戶旅程分析,盡可能全面不遺漏地分解業(yè)務領域,并梳理領域對象之間的關系,這是一個發(fā)散的過程。事件風暴過程會產生很多的實體、命令、事件等領域對象,我們將這些領域對象從不同的維度進行聚類,形成如聚合、限界上下文等邊界,建立領域模型,這就是一個收斂的過程。


我們可以用三步來劃定領域模型和微服務的邊界。

戰(zhàn)略設計主要包括三步:

  1. 通過事件風暴梳理業(yè)務過程中的要素,確定領域實體等領域對象。

  2. 根據(jù)領域實體之間的業(yè)務關聯(lián)性,將業(yè)務緊密相關的實體組合形成聚合。

  3. 根據(jù)業(yè)務和語義邊界等因素,將一個或多個聚合劃定在一個限界上下文內,形成領域模型。

戰(zhàn)術設計的主要工作是將領域模型中的領域對象與代碼模型中的代碼對象建立映射關系,并調整業(yè)務架構和領域模型以響應業(yè)務變化。最終,將系統(tǒng)架構與業(yè)務架構綁定,建立新的映射關系。

DDD與微服務的關系

DDD主要關注:從業(yè)務領域視角劃分領域邊界,構建通用語言進行高效溝通,通過業(yè)務抽象,建立領域模型,維持業(yè)務和代碼的邏輯一致性。

微服務主要關注:運行時的進程間通信、容錯和故障隔離,實現(xiàn)去中心化數(shù)據(jù)管理和去中心化服務治理,關注微服務的獨立開發(fā)、測試、構建和部署。

總體來說:

  1. DDD是一套完整而系統(tǒng)的設計方法,它能帶給你從戰(zhàn)略設計到戰(zhàn)術設計的標準設計過程,使得你的設計思路能夠更加清晰,設計過程更加規(guī)范。
  2. DDD善于處理與領域相關的擁有高復雜度業(yè)務的產品開發(fā),通過它可以建立一個核心而穩(wěn)定的領域模型,有利于領域知識的傳遞與傳承。
  3. DDD強調團隊與領域專家的合作,能夠幫助你的團隊建立一個溝通良好的氛圍,構建一致的架構體系。
  4. DDD的設計思想、原則與模式有助于提高你的架構設計能力。
  5. 無論是在新項目中設計微服務,還是將系統(tǒng)從單體架構演進到微服務,都可以遵循DDD的架構原則。
  6. DDD不僅適用于微服務,也適用于傳統(tǒng)的單體應用

關鍵概念

領域和子域Domain

我們先看一下漢語詞典中對領域的解釋:“領域是從事一種專門活動或事業(yè)的范圍、部類或部門?!卑俣劝倏茖︻I域的解釋:“領域具體指一種特定的范圍或區(qū)域?!?/p>

領域就是用來確定范圍的,范圍即邊界,這也是DDD在設計中不斷強調邊界的原因。

既然領域是用來限定業(yè)務邊界和范圍的,那么就會有大小之分,領域越大,業(yè)務范圍就越大,反之則相反。

領域可以進一步劃分為子領域。我們把劃分出來的多個子領域稱為子域,每個子域對應一個更小的問題域或更小的業(yè)務范圍


我們來看一下上面這張圖。這個例子是在講如何給桃樹建立一個完整的生物學知識體系。初中生物課其實早就告訴我們研究方法了。它的研究過程是這樣的。

第一步:確定研究對象,即研究領域,這里是一棵桃樹。

第二步:對研究對象進行細分,將桃樹細分為器官,器官又分為營養(yǎng)器官和生殖器官兩種。其中營養(yǎng)器官包括根、莖和葉,生殖器官包括花、果實和種子。桃樹的知識體系是我們已經確定要研究的問題域,對應DDD的領域。根、莖、葉、花、果實和種子等器官則是細分后的問題子域。這個過程就是DDD將領域細分為多個子域的過程。

第三步:對器官進行細分,將器官細分為組織。比如,葉子器官可細分為保護組織、營養(yǎng)組織和輸導組織等。這個過程就是DDD將子域進一步細分為多個子域的過程。

第四步:對組織進行細分,將組織細分為細胞,細胞成為我們研究的最小單元。細胞之間的細胞壁確定了單元的邊界,也確定了研究的最小邊界

第五步:細胞核、線粒體、細胞膜等物質共同構成細胞,這些物質一起協(xié)作讓細胞具有這類細胞特定的生物功能。在這里你可以把細胞理解為DDD的聚合,細胞內的這些物質就可以理解為聚合里面的聚合根、實體以及值對象等,在聚合內這些實體一起協(xié)作完成特定的業(yè)務功能。這個過程類似DDD設計時,確定微服務內功能要素和邊界的過程。

核心域,通用域和支撐域

在領域不斷劃分的過程中,領域會細分為不同的子域,子域可以根據(jù)自身重要性和功能屬性劃分為三類子域,它們分別是:核心域、通用域和支撐域。

決定產品和公司核心競爭力的子域是核心域,它是業(yè)務成功的主要因素和公司的核心競爭力。

沒有太多個性化的訴求,同時被多個子域使用的通用功能子域是通用域。

還有一種功能子域是必需的,但既不包含決定產品和公司核心競爭力的功能,也不包含通用功能的子域,它就是支撐域。

這三類子域相較之下,核心域是最重要的。

通用域和支撐域如果對應到企業(yè)系統(tǒng),舉例來說的話,通用域則是你需要用到的通用系統(tǒng),比如認證、權限、短信等等,這類應用很容易買到,沒有企業(yè)特點限制,不需要做太多的定制化。

而支撐域則具有企業(yè)特性,但不具有通用性,例如數(shù)據(jù)代碼類的數(shù)據(jù)字典,站內信等系統(tǒng)。

那為什么要劃分核心域、通用域和支撐域,主要目的是什么呢?

要事為先

公司在IT系統(tǒng)建設過程中,由于預算和資源有限,對不同類型的子域應有不同的關注度和資源投入策略,記住好鋼要用在刀刃上。

很多公司的業(yè)務,表面看上去相似,但商業(yè)模式和戰(zhàn)略方向是存在很大差異的,因此公司的關注點會不一樣,在劃分核心域、通用域和支撐域時,其結果也會出現(xiàn)非常大的差異。

比如同樣都是電商平臺的淘寶、天貓、京東和蘇寧易購,他們的商業(yè)模式是不同的。淘寶是C2C網(wǎng)站,個人賣家對個人買家,而天貓、京東和蘇寧易購則是B2C網(wǎng)站,是公司賣家對個人買家。即便是蘇寧易購與京東都是B2C的模式,他們的商業(yè)模式也是不一樣的,蘇寧易購是典型的傳統(tǒng)線下賣場轉型成為電商,京東則是直營加部分平臺模式。

商業(yè)模式的不同會導致核心域劃分結果的不同。有的公司核心域可能在客戶服務,有的可能在產品質量,有的可能在物流。在公司領域細分、建立領域模型和系統(tǒng)建設時,我們就要結合公司戰(zhàn)略重點和商業(yè)模式,找到核心域了,且重點關注核心域。

通用語言

在事件風暴過程中,通過團隊交流達成共識的,能夠簡單、清晰、準確描述業(yè)務涵義和規(guī)則的語言就是通用語言。也就是說,通用語言是團隊統(tǒng)一的語言,不管你在團隊中承擔什么角色,在同一個領域的軟件生命周期里都使用統(tǒng)一的語言進行交流。

通用語言的價值也就很明了了,它可以解決交流障礙這個問題,使領域專家和開發(fā)人員能夠協(xié)同合作,從而確保業(yè)務需求的正確表達。

但是,對這個概念的理解,到這里還不夠。

通用語言包含術語和用例場景,并且能夠直接反映在代碼中。通用語言中的名詞可以給領域對象命名,如商品、訂單等,對應實體對象;而動詞則表示一個動作或事件,如商品已下單、訂單已付款等,對應領域事件或者命令。

通用語言貫穿DDD的整個設計過程。作為項目團隊溝通和協(xié)商形成的統(tǒng)一語言,基于它,你就能夠開發(fā)出可讀性更好的代碼,將業(yè)務需求準確轉化為代碼設計。

這張圖描述了從事件風暴建立通用語言到領域對象設計和代碼落地的完整過程。


簡單來說,通用語言確定了項目團隊內部交流的統(tǒng)一語言,而這個語言所在的語義環(huán)境則是由限界上下文來限定的,以確保語義的唯一性。

限界上下文BoundedContext

我們可以將限界上下文拆解為兩個詞:限界和上下文。限界就是領域的邊界,而上下文則是語義環(huán)境。通過領域的限界上下文,我們就可以在統(tǒng)一的領域邊界內用統(tǒng)一的語言進行交流。

綜合一下,我認為限界上下文的定義就是:用來封裝通用語言和領域對象,提供上下文環(huán)境,保證在領域之內的一些術語、業(yè)務相關對象等(通用語言)有一個確切的含義,沒有二義性。這個邊界定義了模型的適用范圍,使團隊所有成員能夠明確地知道什么應該在模型中實現(xiàn),什么不應該在模型中實現(xiàn)。

領域專家、架構師和開發(fā)人員的主要工作就是通過事件風暴來劃分限界上下文。限界上下文確定了微服務的設計和拆分方向,是微服務設計和拆分的主要依據(jù)。如果不考慮技術異構、團隊溝通等其它外部因素,一個限界上下文理論上就可以設計為一個微服務。

實體Entity

我們先來看一下實體是什么東西?

在DDD中有這樣一類對象,它們擁有唯一標識符,且標識符在歷經各種狀態(tài)變更后仍能保持一致。對這些對象而言,重要的不是其屬性,而是其延續(xù)性和標識,對象的延續(xù)性和標識會跨越甚至超出軟件的生命周期。我們把這樣的對象稱為實體。

1. 實體的業(yè)務形態(tài)

在DDD不同的設計過程中,實體的形態(tài)是不同的。在戰(zhàn)略設計時,實體是領域模型的一個重要對象。領域模型中的實體是多個屬性、操作或行為的載體。在事件風暴中,我們可以根據(jù)命令、操作或者事件,找出產生這些行為的業(yè)務實體對象,進而按照一定的業(yè)務規(guī)則將依存度高和業(yè)務關聯(lián)緊密的多個實體對象和值對象進行聚類,形成聚合。你可以這么理解,實體和值對象是組成領域模型的基礎單元。

2. 實體的代碼形態(tài)

在代碼模型中,實體的表現(xiàn)形式是實體類,這個類包含了實體的屬性和方法,通過這些方法實現(xiàn)實體自身的業(yè)務邏輯。在DDD里,這些實體類通常采用充血模型,與這個實體相關的所有業(yè)務邏輯都在實體類的方法中實現(xiàn),跨多個實體的領域邏輯則在領域服務中實現(xiàn)

3. 實體的運行形態(tài)

實體以DO(領域對象)的形式存在,每個實體對象都有唯一的ID。我們可以對一個實體對象進行多次修改,修改后的數(shù)據(jù)和原來的數(shù)據(jù)可能會大不相同。但是,由于它們擁有相同的ID,它們依然是同一個實體。比如商品是商品上下文的一個實體,通過唯一的商品ID來標識,不管這個商品的數(shù)據(jù)如何變化,商品的ID一直保持不變,它始終是同一個商品。

4. 實體的數(shù)據(jù)庫形態(tài)

與傳統(tǒng)數(shù)據(jù)模型設計優(yōu)先不同,DDD是先構建領域模型,針對實際業(yè)務場景構建實體對象和行為,再將實體對象映射到數(shù)據(jù)持久化對象。

在領域模型映射到數(shù)據(jù)模型時,一個實體可能對應0個、1個或者多個數(shù)據(jù)庫持久化對象。大多數(shù)情況下實體與持久化對象是一對一。在某些場景中,有些實體只是暫駐靜態(tài)內存的一個運行態(tài)實體,它不需要持久化。比如,基于多個價格配置數(shù)據(jù)計算后生成的折扣實體。

而在有些復雜場景下,實體與持久化對象則可能是一對多或者多對一的關系。比如,用戶user與角色role兩個持久化對象可生成權限實體,一個實體對應兩個持久化對象,這是一對多的場景。再比如,有些場景為了避免數(shù)據(jù)庫的聯(lián)表查詢,提升系統(tǒng)性能,會將客戶信息customer和賬戶信息account兩類數(shù)據(jù)保存到同一張數(shù)據(jù)庫表中,客戶和賬戶兩個實體可根據(jù)需要從一個持久化對象中生成,這就是多對一的場景。

值對象ValueObject

《實現(xiàn)領域驅動設計》一書中對值對象的定義:通過對象屬性值來識別的對象,它將多個相關屬性組合為一個概念整體。在DDD中用來描述領域的特定方面,并且是一個沒有標識符的對象,叫作值對象。

值對象描述了領域中的一件東西,這個東西是不可變的,它將不同的相關屬性組合成了一個概念整體。當度量和描述改變時,可以用另外一個值對象予以替換。它可以和其它值對象進行相等性比較,且不會對協(xié)作對象造成副作用。這部分在后面講“值對象的運行形態(tài)”時還會有例子。

簡單來說就是一堆不可變的屬性的集合,為了避免屬性的零碎


人員實體原本包括:姓名、年齡、性別以及人員所在的省、市、縣和街道等屬性。這樣顯示地址相關的屬性就很零碎了對不對?現(xiàn)在,我們可以將“省、市、縣和街道等屬性”拿出來構成一個“地址屬性集合”,這個集合就是值對象了。

1. 值對象的業(yè)務形態(tài)

值對象是DDD領域模型中的一個基礎對象,它跟實體一樣都來源于事件風暴所構建的領域模型,都包含了若干個屬性,它與實體一起構成聚合。

我們不妨對照實體,來看值對象的業(yè)務形態(tài),這樣更好理解。本質上,實體是看得到、摸得著的實實在在的業(yè)務對象,實體具有業(yè)務屬性、業(yè)務行為和業(yè)務邏輯。而值對象只是若干個屬性的集合,只有數(shù)據(jù)初始化操作和有限的不涉及修改數(shù)據(jù)的行為,基本不包含業(yè)務邏輯。值對象的屬性集雖然在物理上獨立出來了,但在邏輯上它仍然是實體屬性的一部分,用于描述實體的特征。

在值對象中也有部分共享的標準類型的值對象,它們有自己的限界上下文,有自己的持久化對象,可以建立共享的數(shù)據(jù)類微服務,比如數(shù)據(jù)字典。

2. 值對象的代碼形態(tài)

值對象在代碼中有這樣兩種形態(tài)。如果值對象是單一屬性,則直接定義為實體類的屬性;如果值對象是屬性集合,則把它設計為Class類,Class將具有整體概念的多個屬性歸集到屬性集合,這樣的值對象沒有ID,會被實體整體引用。

我們看一下下面這段代碼,person這個實體有若干個單一屬性的值對象,比如Id、name等屬性;同時它也包含多個屬性的值對象,比如地址address。


3. 值對象的運行形態(tài)

實體實例化后的DO對象的業(yè)務屬性和業(yè)務行為非常豐富,但值對象實例化的對象則相對簡單和乏味。除了值對象數(shù)據(jù)初始化和整體替換的行為外,其它業(yè)務行為就很少了。

值對象嵌入到實體的話,有這樣兩種不同的數(shù)據(jù)格式,也可以說是兩種方式,分別是屬性嵌入的方式和序列化大對象的方式。

引用單一屬性的值對象或只有一條記錄的多屬性值對象的實體,可以采用屬性嵌入的方式嵌入。引用一條或多條記錄的多屬性值對象的實體,可以采用序列化大對象的方式嵌入。比如,人員實體可以有多個通訊地址,多個地址序列化后可以嵌入人員的地址屬性。值對象創(chuàng)建后就不允許修改了,只能用另外一個值對象來整體替換。

案例1:以屬性嵌入的方式形成的人員實體對象,地址值對象直接以屬性值嵌入人員實體中。


案例2:以序列化大對象的方式形成的人員實體對象,地址值對象被序列化成大對象Json串后,嵌入人員實體中。


4. 值對象的數(shù)據(jù)庫形態(tài)

DDD引入值對象是希望實現(xiàn)從“數(shù)據(jù)建模為中心”向“領域建模為中心”轉變,減少數(shù)據(jù)庫表的數(shù)量和表與表之間復雜的依賴關系,盡可能地簡化數(shù)據(jù)庫設計,提升數(shù)據(jù)庫性能。

如何理解用值對象來簡化數(shù)據(jù)庫設計呢?

傳統(tǒng)的數(shù)據(jù)建模大多是根據(jù)數(shù)據(jù)庫范式設計的,每一個數(shù)據(jù)庫表對應一個實體,每一個實體的屬性值用單獨的一列來存儲,一個實體主表會對應N個實體從表。而值對象在數(shù)據(jù)庫持久化方面簡化了設計,它的數(shù)據(jù)庫設計大多采用非數(shù)據(jù)庫范式,值對象的屬性值和實體對象的屬性值保存在同一個數(shù)據(jù)庫實體表中。

舉個例子,還是基于上述人員和地址那個場景,實體和數(shù)據(jù)模型設計通常有兩種解決方案:

第一是把地址值對象的所有屬性都放到人員實體表中,創(chuàng)建人員實體,創(chuàng)建人員數(shù)據(jù)表;

第二是創(chuàng)建人員和地址兩個實體,同時創(chuàng)建人員和地址兩張表。

第一個方案會破壞地址的業(yè)務涵義和概念完整性,第二個方案增加了不必要的實體和表,需要處理多個實體和表的關系,從而增加了數(shù)據(jù)庫設計的復雜性。

那到底應該怎樣設計,才能讓業(yè)務含義清楚,同時又不讓數(shù)據(jù)庫變得復雜呢?

在領域建模時,我們可以將部分對象設計為值對象,保留對象的業(yè)務涵義,同時又減少了實體的數(shù)量;在數(shù)據(jù)建模時,我們可以將值對象嵌入實體,減少實體表的數(shù)量,簡化數(shù)據(jù)庫設計。

要想發(fā)揮對象的威力,就需要優(yōu)先做領域建模,弱化數(shù)據(jù)庫的作用,只把數(shù)據(jù)庫作為一個保存數(shù)據(jù)的倉庫即可。即使違反數(shù)據(jù)庫設計原則,也不用大驚小怪,只要業(yè)務能夠順利運行,就沒什么關系。

聚合根AggregateRoot

聚合根的主要目的是為了避免由于復雜數(shù)據(jù)模型缺少統(tǒng)一的業(yè)務規(guī)則控制,而導致聚合、實體之間數(shù)據(jù)不一致性的問題。

如果把聚合比作組織,那聚合根就是這個組織的負責人。聚合根也稱為根實體,它不僅是實體,還是聚合的管理者。

首先它作為實體本身,擁有實體的屬性和業(yè)務行為,實現(xiàn)自身的業(yè)務邏輯。

其次它作為聚合的管理者,在聚合內部負責協(xié)調實體和值對象按照固定的業(yè)務規(guī)則協(xié)同完成共同的業(yè)務邏輯。

最后在聚合之間,它還是聚合對外的接口人,以聚合根ID關聯(lián)的方式接受外部任務和請求,在上下文內實現(xiàn)聚合之間的業(yè)務協(xié)同。也就是說,聚合之間通過聚合根ID關聯(lián)引用,如果需要訪問其它聚合的實體,就要先訪問聚合根,再導航到聚合內部實體,外部對象不能直接訪問聚合內實體。

聚合Aggregate

在DDD中,實體和值對象是很基礎的領域對象。實體一般對應業(yè)務對象,它具有業(yè)務屬性和業(yè)務行為;而值對象主要是屬性集合,對實體的狀態(tài)和特征進行描述。但實體和值對象都只是個體化的對象,它們的行為表現(xiàn)出來的是個體的能力。

舉個例子。社會是由一個個的個體組成的,象征著我們每一個人。隨著社會的發(fā)展,慢慢出現(xiàn)了社團、機構、部門等組織,我們開始從個人變成了組織的一員,大家可以協(xié)同一致的工作,朝著一個最大的目標前進,發(fā)揮出更大的力量。

領域模型內的實體和值對象就好比個體,而能讓實體和值對象協(xié)同工作的組織就是聚合,它用來確保這些領域對象在實現(xiàn)共同的業(yè)務邏輯時,能保證數(shù)據(jù)的一致性。

聚合就是由業(yè)務和邏輯緊密關聯(lián)的實體和值對象組合而成的,聚合是數(shù)據(jù)修改和持久化的基本單元,每一個聚合對應一個倉儲,實現(xiàn)數(shù)據(jù)的持久化。

聚合有一個聚合根和上下文邊界,這個邊界根據(jù)業(yè)務單一職責和高內聚原則,定義了聚合內部應該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設計出來的微服務很自然就是“高內聚、低耦合”的。

聚合在DDD分層架構里屬于領域層,領域層包含了多個聚合,共同實現(xiàn)核心業(yè)務邏輯。聚合內實體以充血模型實現(xiàn)個體業(yè)務能力,以及業(yè)務邏輯的高內聚??缍鄠€實體的業(yè)務邏輯通過領域服務來實現(xiàn),跨多個聚合的業(yè)務邏輯通過應用服務來實現(xiàn)。比如有的業(yè)務場景需要同一個聚合的A和B兩個實體來共同完成,我們就可以將這段業(yè)務邏輯用領域服務來實現(xiàn);而有的業(yè)務邏輯需要聚合C和聚合D中的兩個服務共同完成,這時你就可以用應用服務來組合這兩個服務。

怎樣設計聚合?

DDD領域建模通常采用事件風暴,它通常采用用例分析、場景分析和用戶旅程分析等方法,通過頭腦風暴列出所有可能的業(yè)務行為和事件,然后找出產生這些行為的領域對象,并梳理領域對象之間的關系,找出聚合根,找出與聚合根業(yè)務緊密關聯(lián)的實體和值對象,再將聚合根、實體和值對象組合,構建聚合。

下面我們以保險的投保業(yè)務場景為例,看一下聚合的構建過程主要都包括哪些步驟。


第 1 步:采用事件風暴,根據(jù)業(yè)務行為,梳理出在投保過程中發(fā)生這些行為的所有的實體和值對象,比如投保單、標的、客戶、被保人等等。

第 2 步:從眾多實體中選出適合作為對象管理者的根實體,也就是聚合根。判斷一個實體是否是聚合根,你可以結合以下場景分析:是否有獨立的生命周期?是否有全局唯一ID?是否可以創(chuàng)建或修改其它對象?是否有專門的模塊來管這個實體。圖中的聚合根分別是投保單和客戶實體。

第 3 步:根據(jù)業(yè)務單一職責和高內聚原則,找出與聚合根關聯(lián)的所有緊密依賴的實體和值對象。構建出 1 個包含聚合根(唯一)、多個實體和值對象的對象集合,這個集合就是聚合。在圖中我們構建了客戶和投保這兩個聚合。

第 4 步:在聚合內根據(jù)聚合根、實體和值對象的依賴關系,畫出對象的引用和依賴模型。這里我需要說明一下:投保人和被保人的數(shù)據(jù),是通過關聯(lián)客戶ID從客戶聚合中獲取的,在投保聚合里它們是投保單的值對象,這些值對象的數(shù)據(jù)是客戶的冗余數(shù)據(jù),即使未來客戶聚合的數(shù)據(jù)發(fā)生了變更,也不會影響投保單的值對象數(shù)據(jù)。從圖中我們還可以看出實體之間的引用關系,比如在投保聚合里投保單聚合根引用了報價單實體,報價單實體則引用了報價規(guī)則子實體。

第 5 步:多個聚合根據(jù)業(yè)務語義和上下文一起劃分到同一個限界上下文內。

這就是一個聚合誕生的完整過程了。

聚合的一些設計原則

《實現(xiàn)領域驅動設計》一書中對聚合設計原則的描述

1. 在一致性邊界內建模真正的不變條件。聚合用來封裝真正的不變性,而不是簡單地將對象組合在一起。聚合內有一套不變的業(yè)務規(guī)則,各實體和值對象按照統(tǒng)一的業(yè)務規(guī)則運行,實現(xiàn)對象數(shù)據(jù)的一致性,邊界之外的任何東西都與該聚合無關,這就是聚合能實現(xiàn)業(yè)務高內聚的原因。

2. 設計小聚合。如果聚合設計得過大,聚合會因為包含過多的實體,導致實體之間的管理過于復雜,高頻操作時會出現(xiàn)并發(fā)沖突或者數(shù)據(jù)庫鎖,最終導致系統(tǒng)可用性變差。而小聚合設計則可以降低由于業(yè)務過大導致聚合重構的可能性,讓領域模型更能適應業(yè)務的變化。

3. 通過唯一標識引用其它聚合。聚合之間是通過關聯(lián)外部聚合根ID的方式引用,而不是直接對象引用的方式。外部聚合的對象放在聚合邊界內管理,容易導致聚合的邊界不清晰,也會增加聚合之間的耦合度。

4. 在邊界之外使用最終一致性。聚合內數(shù)據(jù)強一致性,而聚合之間數(shù)據(jù)最終一致性。在一次事務中,最多只能更改一個聚合的狀態(tài)。如果一次業(yè)務操作涉及多個聚合狀態(tài)的更改,應采用領域事件的方式異步修改相關的聚合,實現(xiàn)聚合之間的解耦。

5. 通過應用層實現(xiàn)跨聚合的服務調用。為實現(xiàn)微服務內聚合之間的解耦,以及未來以聚合為單位的微服務組合和拆分,應避免跨聚合的領域服務調用和跨聚合的數(shù)據(jù)庫表關聯(lián)。

原則需要消化吸收后靈活運用到自己的系統(tǒng)中才能產生威力

領域事件

領域事件是領域模型中非常重要的一部分,用來表示領域中發(fā)生的事件。一個領域事件將導致進一步的業(yè)務操作,在實現(xiàn)業(yè)務解耦的同時,還有助于形成完整的業(yè)務閉環(huán)。

舉例來說的話

領域事件可以是業(yè)務流程的一個步驟,比如投保業(yè)務繳費完成后,觸發(fā)投保單轉保單的動作;

也可能是定時批處理過程中發(fā)生的事件,比如批處理生成季繳保費通知單,觸發(fā)發(fā)送繳費郵件通知操作;

或者一個事件發(fā)生后觸發(fā)的后續(xù)動作,比如密碼連續(xù)輸錯三次,觸發(fā)鎖定賬戶的動作。

那如何識別領域事件呢?

很簡單,和剛才講的定義是強關聯(lián)的。在做用戶旅程或者場景分析時,我們要捕捉業(yè)務、需求人員或領域專家口中的關鍵詞:“如果發(fā)生……,則……”“當做完……的時候,請通知……”“發(fā)生……時,則……”等。在這些場景中,如果發(fā)生某種事件后,會觸發(fā)進一步的操作,那么這個事件很可能就是領域事件

領域事件總體架構

領域事件的執(zhí)行需要一系列的組件和技術來支撐。

領域事件處理包括:事件構建和發(fā)布、事件數(shù)據(jù)持久化、事件總線、消息中間件、事件接收和處理等。下面我們逐一講一下。


1. 事件構建和發(fā)布

事件基本屬性至少包括:事件唯一標識、發(fā)生時間、事件類型和事件源,其中事件唯一標識應該是全局唯一的,以便事件能夠無歧義地在多個限界上下文中傳遞。事件基本屬性主要記錄事件自身以及事件發(fā)生背景的數(shù)據(jù)。

另外事件中還有一項更重要,那就是業(yè)務屬性,用于記錄事件發(fā)生那一刻的業(yè)務數(shù)據(jù),這些數(shù)據(jù)會隨事件傳輸?shù)接嗛喎剑蚤_展下一步的業(yè)務操作。

事件基本屬性和業(yè)務屬性一起構成事件實體,事件實體依賴聚合根。領域事件發(fā)生后,事件中的業(yè)務數(shù)據(jù)不再修改,因此業(yè)務數(shù)據(jù)可以以序列化值對象的形式保存,這種存儲格式在消息中間件中也比較容易解析和獲取。

為了保證事件結構的統(tǒng)一,我們還會創(chuàng)建事件基類 DomainEvent(參考下圖),子類可以擴充屬性和方法。由于事件沒有太多的業(yè)務行為,實現(xiàn)方法一般比較簡單。


事件發(fā)布之前需要先構建事件實體并持久化。事件發(fā)布的方式有很多種,你可以通過應用服務或者領域服務發(fā)布到事件總線或者消息中間件,也可以從事件表中利用定時程序或數(shù)據(jù)庫日志捕獲技術獲取增量事件數(shù)據(jù),發(fā)布到消息中間件。

2. 事件數(shù)據(jù)持久化

事件數(shù)據(jù)持久化可用于系統(tǒng)之間的數(shù)據(jù)對賬,或者實現(xiàn)發(fā)布方和訂閱方事件數(shù)據(jù)的審計。當遇到消息中間件、訂閱方系統(tǒng)宕機或者網(wǎng)絡中斷,在問題解決后仍可繼續(xù)后續(xù)業(yè)務流轉,保證數(shù)據(jù)的一致性。

事件數(shù)據(jù)持久化有兩種方案,在實施過程中你可以根據(jù)自己的業(yè)務場景進行選擇。

  • 持久化到本地業(yè)務數(shù)據(jù)庫的事件表中,利用本地事務保證業(yè)務和事件數(shù)據(jù)的一致性。
  • 持久化到共享的事件數(shù)據(jù)庫中。這里需要注意的是:業(yè)務數(shù)據(jù)庫和事件數(shù)據(jù)庫不在一個數(shù)據(jù)庫中,它們的數(shù)據(jù)持久化操作會跨數(shù)據(jù)庫,因此需要分布式事務機制來保證業(yè)務和事件數(shù)據(jù)的強一致性,結果就是會對系統(tǒng)性能造成一定的影響。
3. 事件總線(EventBus)

事件總線是實現(xiàn)微服務內聚合之間領域事件的重要組件,它提供事件分發(fā)和接收等服務。事件總線是進程內模型,它會在微服務內聚合之間遍歷訂閱者列表,采取同步或異步的模式傳遞數(shù)據(jù)。事件分發(fā)流程大致如下:

  • 如果是微服務內的訂閱者(其它聚合),則直接分發(fā)到指定訂閱者;
  • 如果是微服務外的訂閱者,將事件數(shù)據(jù)保存到事件庫(表)并異步發(fā)送到消息中間件;
  • 如果同時存在微服務內和外訂閱者,則先分發(fā)到內部訂閱者,將事件消息保存到事件庫(表),再異步發(fā)送到消息中間件。
4. 消息中間件

跨微服務的領域事件大多會用到消息中間件,實現(xiàn)跨微服務的事件發(fā)布和訂閱。消息中間件的產品非常成熟,市場上可選的技術也非常多,比如Kafka,RabbitMQ等。

5. 事件接收和處理

微服務訂閱方在應用層采用監(jiān)聽機制,接收消息隊列中的事件數(shù)據(jù),完成事件數(shù)據(jù)的持久化后,就可以開始進一步的業(yè)務處理。領域事件處理可在領域服務中實現(xiàn)。

領域事件運行機制案例

承保業(yè)務流程的繳費通知單事件,來給你解釋一下領域事件的運行機制。這個領域事件發(fā)生在投保和收款微服務之間。發(fā)生的領域事件是:繳費通知單已生成。下一步的業(yè)務操作是:繳費。


事件起點:出單員生成投保單,核保通過后,發(fā)起生成繳費通知單的操作。

1.投保微服務應用服務,調用聚合中的領域服務createPaymentNotice和createPaymentNoticeEvent,分別創(chuàng)建繳費通知單、繳費通知單事件。其中繳費通知單事件類PaymentNoticeEvent繼承基類DomainEvent。

2.利用倉儲服務持久化繳費通知單相關的業(yè)務和事件數(shù)據(jù)。為了避免分布式事務,這些業(yè)務和事件數(shù)據(jù)都持久化到本地投保微服務數(shù)據(jù)庫中。

3.通過數(shù)據(jù)庫日志捕獲技術或者定時程序,從數(shù)據(jù)庫事件表中獲取事件增量數(shù)據(jù),發(fā)布到消息中間件。這里說明:事件發(fā)布也可以通過應用服務或者領域服務完成發(fā)布。

4.收款微服務在應用層從消息中間件訂閱繳費通知單事件消息主題,監(jiān)聽并獲取事件數(shù)據(jù)后,應用服務調用領域層的領域服務將事件數(shù)據(jù)持久化到本地數(shù)據(jù)庫中。

5.收款微服務調用領域層的領域服務PayPremium,完成繳費。

6.事件結束。

事件風暴

事件風暴需要準備些什么

1. 事件風暴的參與者

除了領域專家(對業(yè)務極其了解的人),事件風暴的其他參與者可以是DDD專家、架構師、產品經理、項目經理、開發(fā)人員和測試人員等項目團隊成員。

領域建模是統(tǒng)一團隊語言的過程,因此項目團隊應盡早地參與到領域建模中,這樣才能高效建立起團隊的通用語言。到了微服務建設時,領域模型也更容易和系統(tǒng)架構保持一致。

2. 事件風暴要準備的材料

在這個過程中,我們要用不同顏色的貼紙區(qū)分領域行為。如下圖,

  • 用藍色表示命令
  • 用綠色表示實體
  • 橙色表示領域事件
  • 黃色表示補充信息(補充信息主要用來說明注意事項,比如外部依賴等。)


3. 事件風暴的場地

事件風暴的發(fā)明者曾經建議要準備八米長的墻,這樣設計就不會受到空間的限制了。當然,這個不是必要條件,看各自的現(xiàn)實條件吧,不要讓思維受限就好。

或者是一個多人在線協(xié)作的白板。

4. 事件風暴分析的關注點

在領域建模的過程中,我們需要重點關注這類業(yè)務的語言和行為。比如某些業(yè)務動作或行為(事件)是否會觸發(fā)下一個業(yè)務動作,這個動作(事件)的輸入和輸出是什么?是誰(實體)發(fā)出的什么動作(命令),觸發(fā)了這個動作(事件)…我們可以從這些暗藏的詞匯中,分析出領域模型中的事件、命令和實體等領域對象。

如何用事件風暴構建領域模型

領域建模的過程主要包括產品愿景、業(yè)務場景分析、領域建模和微服務拆分與設計這幾個重要階段。下面以用戶中臺為例,介紹一下如何用事件風暴構建領域模型。

1. 產品愿景

產品愿景的主要目的是對產品頂層價值的設計,使產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

產品愿景的參與角色:領域專家、業(yè)務需求方、產品經理、項目經理和開發(fā)經理。

在建模之前,項目團隊要思考這樣兩點:

  • 用戶中臺到底能夠做什么?
  • 它的業(yè)務范圍、目標用戶、核心價值和愿景,與其它同類產品的差異和優(yōu)勢在哪里?

這個過程也是明確用戶中臺建設方向和統(tǒng)一團隊思想的過程。參與者要對每一個點(下圖最左側列的內容)發(fā)表意見,用水筆寫在貼紙上,貼在黃色貼紙的位置。這個過程會讓參與者充分發(fā)表意見,最后會將發(fā)散的意見統(tǒng)一為通用語言。如果你的團隊的產品愿景和目標已經很清晰了,那這個步驟你可以忽略。

2. 業(yè)務場景分析

場景分析是從用戶視角出發(fā)的,根據(jù)業(yè)務流程或用戶旅程,采用用例和場景分析,探索領域中的典型場景,找出領域事件、實體和命令等領域對象,支撐領域建模。事件風暴參與者要盡可能地遍歷所有業(yè)務細節(jié),充分發(fā)表意見,不要遺漏業(yè)務要點。

場景分析的參與角色:領域專家、產品經理、需求分析人員、架構師、項目經理、開發(fā)經理和測試經理。

用戶中臺有這樣三個典型的業(yè)務場景:

  • 第一個是系統(tǒng)和崗位設置,設置系統(tǒng)中崗位的菜單權限;
  • 第二個是用戶權限配置,為用戶建立賬戶和密碼,設置用戶崗位;
  • 第三個是用戶登錄系統(tǒng)和權限校驗,生成用戶登錄和操作日志。

我們可以按照業(yè)務流程,一步一步搜尋用戶業(yè)務流程中的關鍵領域事件,比如崗位已創(chuàng)建,用戶已創(chuàng)建等事件。再找出什么行為會引起這些領域事件,這些行為可能是一個或若干個命令組合在一起產生的,比如創(chuàng)建用戶時,第一個命令是從公司HR系統(tǒng)中獲取用戶信息,第二個命令是根據(jù)HR的員工信息在用戶中臺創(chuàng)建用戶,創(chuàng)建完用戶后就會產生用戶已創(chuàng)建的領域事件。當然這個領域事件可能會觸發(fā)下一步的操作,比如發(fā)布到郵件系統(tǒng)通知用戶已創(chuàng)建,但也可能到此就結束了,你需要根據(jù)具體情況來分析是否還有下一步的操作。


3. 領域建模

領域建模時,我們會根據(jù)場景分析過程中產生的領域對象,比如命令、事件等之間關系,找出產生命令的實體,分析實體之間的依賴關系組成聚合,為聚合劃定限界上下文,建立領域模型以及模型之間的依賴。領域模型利用限界上下文向上可以指導微服務設計,通過聚合向下可以指導聚合根、實體和值對象的設計。

領域建模的參與角色:領域專家、產品經理、需求分析人員、架構師、項目經理、開發(fā)經理和測試經理。

具體可以分為這樣三步。

第一步:從命令和事件中提取產生這些行為的實體。用綠色貼紙表示實體。通過分析用戶中臺的命令和事件等行為數(shù)據(jù),提取了產生這些行為的用戶、賬戶、認證票據(jù)、系統(tǒng)、菜單、崗位和用戶日志七個實體。


第二步:根據(jù)聚合根的管理性質從七個實體中找出聚合根,比如,用戶管理用戶相關實體以及值對象,系統(tǒng)可以管理與系統(tǒng)相關的菜單等實體等,可以找出用戶和系統(tǒng)等聚合根。然后根據(jù)業(yè)務依賴和業(yè)務內聚原則,將聚合根以及它關聯(lián)的實體和值對象組合為聚合,比如系統(tǒng)和菜單實體可以組合為“系統(tǒng)功能”聚合。按照上述方法,用戶中臺就有了系統(tǒng)功能、崗位、用戶信息、用戶日志、賬戶和認證票據(jù)六個聚合。

第三步:劃定限界上下文,根據(jù)上下文語義將聚合歸類。根據(jù)用戶域的上下文語境,用戶基本信息和用戶日志信息這兩個聚合共同構成用戶信息域,分別管理用戶基本信息、用戶登錄和操作日志。認證票據(jù)和賬戶這兩個聚合共同構成認證域,分別實現(xiàn)不同方式的登錄和認證。系統(tǒng)功能和崗位這兩個聚合共同構成權限域,分別實現(xiàn)系統(tǒng)和菜單管理以及系統(tǒng)的崗位配置。根據(jù)業(yè)務邊界,我們可以將用戶中臺劃分為三個限界上下文:用戶信息、認證和權限。


到這里我們就完成了用戶中臺領域模型的構建了。那由于領域建模的過程中產生的領域對象實在太多了,我們可以借助表格來記錄。


4. 微服務拆分與設計

我們在基礎篇講過,原則上一個領域模型就可以設計為一個微服務,但由于領域建模時只考慮了業(yè)務因素,沒有考慮微服務落地時的技術、團隊以及運行環(huán)境等非業(yè)務因素,因此在微服務拆分與設計時,我們不能簡單地將領域模型作為拆分微服務的唯一標準,它只能作為微服務拆分的一個重要依據(jù)。

一般來說一個限界上下文拆分成一個微服務

微服務的設計還需要考慮服務的粒度、分層、邊界劃分、依賴關系和集成關系。除了考慮業(yè)務職責單一外,我們還需要考慮將敏態(tài)與穩(wěn)態(tài)業(yè)務的分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟件包大小以及技術異構等非業(yè)務因素。

微服務設計建議參與的角色:領域專家、產品經理、需求分析人員、架構師、項目經理、開發(fā)經理和測試經理。

用戶中臺微服務設計如果不考慮非業(yè)務因素,我們完全可以按照領域模型與微服務一對一的關系來設計,將用戶中臺設計為:用戶、認證和權限三個微服務。但如果用戶日志數(shù)據(jù)量巨大,大到需要采用大數(shù)據(jù)技術來實現(xiàn),這時用戶信息聚合與用戶日志聚合就會有技術異構。雖然在領域建模時,我們將他們放在一個了領域模型內,但如果考慮技術異構,這兩個聚合就不適合放到同一個微服務里了。我們可以以聚合作為拆分單位,將用戶基本信息管理和用戶日志管理拆分為兩個技術異構的微服務,分別用不同的技術來實現(xiàn)它們。

分層架構

DDD分層架構

DDD的分層架構在不斷發(fā)展。最早是傳統(tǒng)的四層架構;后來四層架構有了進一步的優(yōu)化,實現(xiàn)了各層對基礎層的解耦;再后來領域層和應用層之間增加了上下文環(huán)境(Context)層,五層架構(DCI)就此形成了。


我們看一下上面這張圖,在最早的傳統(tǒng)四層架構中,基礎層是被其它層依賴的,它位于最核心的位置,那按照分層架構的思想,它應該就是核心,但實際上領域層才是軟件的核心,所以這種依賴是有問題的。后來我們采用了依賴倒置(Dependency inversion principle,DIP)的設計,優(yōu)化了傳統(tǒng)的四層架構,實現(xiàn)了各層對基礎層的解耦。

我們今天講的DDD分層架構就是優(yōu)化后的四層架構。在下面這張圖中,從上到下依次是:用戶接口層、應用層、領域層和基礎層。那DDD各層的主要職責是什么呢?下面我來逐一介紹一下。


1.用戶接口層

用戶接口層負責向用戶顯示信息和解釋用戶指令。這里的用戶可能是:用戶、程序、自動化測試和批處理腳本等等。

2.應用層

應用層是很薄的一層,理論上不應該有業(yè)務規(guī)則或邏輯,主要面向用例和流程相關的操作。但應用層又位于領域層之上,因為領域層包含多個聚合,所以它可以協(xié)調多個聚合的服務和領域對象完成服務編排和組合,協(xié)作完成業(yè)務操作。

此外,應用層也是微服務之間交互的通道,它可以調用其它微服務的應用服務,完成微服務之間的服務組合和編排。

這里我要提醒你一下:在設計和開發(fā)時,不要將本該放在領域層的業(yè)務邏輯放到應用層中實現(xiàn)。因為龐大的應用層會使領域模型失焦,時間一長你的微服務就會演化為傳統(tǒng)的三層架構,業(yè)務邏輯會變得混亂。

另外,應用服務是在應用層的,它負責服務的組合、編排和轉發(fā),負責處理業(yè)務用例的執(zhí)行順序以及結果的拼裝,以粗粒度的服務通過API網(wǎng)關向前端發(fā)布。還有,應用服務還可以進行安全認證、權限校驗、事務控制、發(fā)送或訂閱領域事件等。

3.領域層

領域層的作用是實現(xiàn)企業(yè)核心業(yè)務邏輯,通過各種校驗手段保證業(yè)務的正確性。領域層主要體現(xiàn)領域模型的業(yè)務能力,它用來表達業(yè)務概念、業(yè)務狀態(tài)和業(yè)務規(guī)則。

領域層包含聚合根、實體、值對象、領域服務等領域模型中的領域對象。

這里我要特別解釋一下其中幾個領域對象的關系,以便你在設計領域層的時候能更加清楚。

首先,領域模型的業(yè)務邏輯主要是由實體和領域服務來實現(xiàn)的,其中實體會采用充血模型來實現(xiàn)所有與之相關的業(yè)務功能。

其次,你要知道,實體和領域服務在實現(xiàn)業(yè)務邏輯上不是同級的,當領域中的某些功能,單一實體(或者值對象)不能實現(xiàn)時,領域服務就會出馬,它可以組合聚合內的多個實體(或者值對象),實現(xiàn)復雜的業(yè)務邏輯。

4.基礎層

基礎層是貫穿所有層的,它的作用就是為其它各層提供通用的技術和基礎服務,包括第三方工具、驅動、消息中間件、網(wǎng)關、文件、緩存以及數(shù)據(jù)庫等。比較常見的功能還是提供數(shù)據(jù)庫持久化。

基礎層包含基礎服務,它采用依賴倒置設計,封裝基礎資源服務,實現(xiàn)應用層、領域層與基礎層的解耦,降低外部資源變化對應用的影響。

比如說,在傳統(tǒng)架構設計中,由于上層應用對數(shù)據(jù)庫的強耦合,很多公司在架構演進中最擔憂的可能就是換數(shù)據(jù)庫了,因為一旦更換數(shù)據(jù)庫,就可能需要重寫大部分的代碼,這對應用來說是致命的。那采用依賴倒置的設計以后,應用層就可以通過解耦來保持獨立的核心業(yè)務邏輯。當數(shù)據(jù)庫變更時,我們只需要更換數(shù)據(jù)庫基礎服務就可以了,這樣就將資源變更對應用的影響降到了最低。

DDD分層架構最重要的原則

在《實現(xiàn)領域驅動設計》一書中,DDD分層架構有一個重要的原則:每層只能與位于其下方的層發(fā)生耦合。

而架構根據(jù)耦合的緊密程度又可以分為兩種:嚴格分層架構和松散分層架構。優(yōu)化后的DDD分層架構模型就屬于嚴格分層架構,任何層只能對位于其直接下方的層產生依賴。而傳統(tǒng)的DDD分層架構則屬于松散分層架構,它允許某層與其任意下方的層發(fā)生依賴。

建議采用嚴格分層架構。

在嚴格分層架構中,領域服務只能被應用服務調用,而應用服務只能被用戶接口層調用,服務是逐層對外封裝或組合的,依賴關系清晰。而在松散分層架構中,領域服務可以同時被應用層或用戶接口層調用,服務的依賴關系比較復雜且難管理,甚至容易使核心業(yè)務邏輯外泄。

試想下,如果領域層中的某個服務發(fā)生了重大變更,那該如何通知所有調用方同步調整和升級呢?但在嚴格分層架構中,你只需要逐層通知上層服務就可以了。

代碼架構

整潔架構

整潔架構又名“洋蔥架構”。為什么叫它洋蔥架構?看看下面這張圖你就明白了。整潔架構的層就像洋蔥片一樣,它體現(xiàn)了分層的設計思想。

在整潔架構里,同心圓代表應用軟件的不同部分,從里到外依次是領域模型、領域服務、應用服務和最外圍的容易變化的內容,比如用戶界面和基礎設施。

整潔架構最主要的原則是依賴原則,它定義了各層的依賴關系,越往里依賴越低,代碼級別越高,越是核心能力。外圓代碼依賴只能指向內圓,內圓不需要知道外圓的任何情況。


在洋蔥架構中,各層的職能是這樣劃分的:

  • 領域模型實現(xiàn)領域內核心業(yè)務邏輯,它封裝了企業(yè)級的業(yè)務規(guī)則。領域模型的主體是實體,一個實體可以是一個帶方法的對象,也可以是一個數(shù)據(jù)結構和方法集合。
  • 領域服務實現(xiàn)涉及多個實體的復雜業(yè)務邏輯。
  • 應用服務實現(xiàn)與用戶操作相關的服務組合與編排,它包含了應用特有的業(yè)務流程規(guī)則,封裝和實現(xiàn)了系統(tǒng)所有用例。
  • 最外層主要提供適配的能力,適配能力分為主動適配和被動適配。主動適配主要實現(xiàn)外部用戶、網(wǎng)頁、批處理和自動化測試等對內層業(yè)務邏輯訪問適配。被動適配主要是實現(xiàn)核心業(yè)務邏輯對基礎資源訪問的適配,比如數(shù)據(jù)庫、緩存、文件系統(tǒng)和消息中間件等。
  • 紅圈內的領域模型、領域服務和應用服務一起組成軟件核心業(yè)務能力。
六邊形架構

六邊形架構又名“端口適配器架構”。追溯微服務架構的淵源,一般都會涉及到六邊形架構。

六邊形架構的核心理念是:應用是通過端口與外部進行交互的。我想這也是微服務架構下API網(wǎng)關盛行的主要原因吧。

也就是說,在下圖的六邊形架構中,紅圈內的核心業(yè)務邏輯(應用程序和領域模型)與外部資源(包括APP、Web應用以及數(shù)據(jù)庫資源等)完全隔離,僅通過適配器進行交互。它解決了業(yè)務邏輯與用戶界面的代碼交錯問題,很好地實現(xiàn)了前后端分離。六邊形架構各層的依賴關系與整潔架構一樣,都是由外向內依賴。


六邊形架構將系統(tǒng)分為內六邊形和外六邊形兩層,這兩層的職能劃分如下:

  • 紅圈內的六邊形實現(xiàn)應用的核心業(yè)務邏輯;
  • 外六邊形完成外部應用、驅動和基礎資源等的交互和訪問,對前端應用以API主動適配的方式提供服務,對基礎資源以依賴倒置被動適配的方式實現(xiàn)資源訪問。

六邊形架構的一個端口可能對應多個外部系統(tǒng),不同的外部系統(tǒng)也可能會使用不同的適配器,由適配器負責協(xié)議轉換。這就使得應用程序能夠以一致的方式被用戶、程序、自動化測試和批處理腳本使用。

三種模型對比

雖然DDD分層架構、整潔架構、六邊形架構的架構模型表現(xiàn)形式不一樣,但你不要被它們的表象所迷惑,這三種架構模型的設計思想正是微服務架構高內聚低耦合原則的完美體現(xiàn),而它們身上閃耀的正是以領域模型為中心的設計思想。


我們看下上面這張圖,結合圖示對這三種架構模型做一個分析。

請你重點關注圖中的紅色線框,它們是非常重要的分界線,這三種架構里面都有,它的作用就是將核心業(yè)務邏輯與外部應用、基礎資源進行隔離。

紅色框內部主要實現(xiàn)核心業(yè)務邏輯,但核心業(yè)務邏輯也是有差異的,有的業(yè)務邏輯屬于領域模型的能力,有的則屬于面向用戶的用例和流程編排能力。按照這種功能的差異,我們在這三種架構中劃分了應用層和領域層,來承擔不同的業(yè)務邏輯。

領域層實現(xiàn)面向領域模型,實現(xiàn)領域模型的核心業(yè)務邏輯,屬于原子模型,它需要保持領域模型和業(yè)務邏輯的穩(wěn)定,對外提供穩(wěn)定的細粒度的領域服務,所以它處于架構的核心位置。

應用層實現(xiàn)面向用戶操作相關的用例和流程,對外提供粗粒度的API服務。它就像一個齒輪一樣進行前臺應用和領域層的適配,接收前臺需求,隨時做出響應和調整,盡量避免將前臺需求傳導到領域層。應用層作為配速齒輪則位于前臺應用和領域層之間。

COLA4.0

GitHub:https://github.com/alibaba/COLA 作者博客:https://blog.csdn.net/significantfrank/article/details/110934799

生成器:https://start.aliyun.com/bootstrap.html



DDD實戰(zhàn)


模塊劃分如下


本次我們重點完成

  • 賬戶模塊
  • 廣告管理模塊
  • 廣告下發(fā)模塊

戰(zhàn)略設計

戰(zhàn)略設計階段: 此階段主要是依賴于事件風暴(可理解為基于事件流程的頭腦風暴),來呈現(xiàn)出產品的發(fā)展方向以及核心流程和場景,并文檔化。

1.產品要解決的問題,以及從用戶角度歸納出典型業(yè)務場景,落實文檔 -----> 產品愿景、場景分析

2.找出上一步總結出的關鍵名詞,作為各個場景的實體 -----> 領域建模:找出領域對象

3.根據(jù)上一步總結出實體,總結出之間的關系(聚合根、值對象、普通實體),劃分出聚合 -----> 領域建模:定義聚合

4.以上一步歸納出的聚合為單位,根據(jù)業(yè)務場景將聚合分組,得到限界上下文(也就是所屬的領域) ----->領域建模:定義界限上下文

在第 1 步落實文檔后,后面的 2,3,4 領域建模階段都要不斷的參照第 1 步總結出的業(yè)務流程場景來進行拆解與合并; 產品愿景、場景分析 兩個階段是從宏觀到微觀的過程,而 領域建模階段是從微觀到宏觀的過程,也就是自底向上的思想。整體就像是總分、分總的過程。

產品愿景

產品愿景是對產品頂層價值設計,對產品目標用戶、核心價值、差異化競爭點等信息達成一致,避免產品偏離方向。

  • 服務用戶

想投廣告的人。廣告主以及代理商的優(yōu)化師

  • 提供能力

給廣告主提供豐富的廣告資源,實現(xiàn)廣告的管理和投放。廣告投放效果,以及效率

提高交互效率。提供open-api,可以讓客戶通過程序化的方式管理廣告。

  • 定位

業(yè)界先進的一站式效果投放平臺。提供RTA,DPA,OCPX等專業(yè)能力

  • 優(yōu)勢

依托于手機廠商提供的數(shù)據(jù)能力,給廣告主提供更好的投放效果。

場景分析


領域建模

領域建模是通過對業(yè)務和問題域進行分析,建立領域模型。

向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體對象設計。

領域建模是一個收斂的過程,分三步:

第一步找出領域實體和值對象等領域對象;

第二步找出聚合根,根據(jù)實體、值對象與聚合根的依賴關系,建立聚合;

第三步根據(jù)業(yè)務及語義邊界等因素,定義限界上下文。

第一步:找出實體和值對象等領域對象

根據(jù)場景分析,分析并找出發(fā)起或產生這些命令或領域事件的實體和值對象,將與實體或值對象有關的命令和事件聚集到實體。


第二步:定義聚合

定義聚合前,先找出聚合根。然后找出與聚合根緊密依賴的實體和值對象。我們發(fā)現(xiàn)審批意見、審批規(guī)則和請假單緊密關聯(lián),組織關系和人員緊密關聯(lián)。


第三步:定義限界上下文

把整個定義為廣告管理限界上下文

通用語言

廣告業(yè)務:

中文 英文 廣告計劃 AdCampaign 廣告組 AdGroup 廣告定向 AdTargeting 廣告創(chuàng)意 AdCreative 廣告素材 AdAsset 操作日志 OperationLog 賬號 Account

微服務拆分

理論上一個限界上下文就可以設計為一個微服務,但還需要綜合考慮多種外部因素,比如:職責單一性、敏態(tài)與穩(wěn)態(tài)業(yè)務分離、非功能性需求(如彈性伸縮、版本發(fā)布頻率和安全等要求)、軟件包大小、團隊溝通效率和技術異構等非業(yè)務要素。

可以微服務的拆分粒度大一些,但是聚合和限界上下文一定要邊界清晰,后續(xù)隨著某些功能逐漸變大再去拆分也會比較容易

戰(zhàn)術設計

有了戰(zhàn)略設計階段的結果,反而戰(zhàn)術設計階段相對清晰一些。

1.按照 DDD 四層模型建包,咱們這里使用COLA生成的包結構

2.確定聚合中的對象關系,定義哪些是實體,哪些是值對象,具體字段都有什么。

3.通過戰(zhàn)略設計階段文檔中的命令、事件來編排充血模型的領域對象,構建應用服務與領域服務

詳細設計

技術選型跟上面的關系不大??梢允褂肅OLA4.0分層框架。

使用SpringCloud技術棧,以及根據(jù)業(yè)務建模選擇中間件。

參考

[1] https://time.geekbang.org/column/intro/100037301?tab=catalog

[2]《實現(xiàn)領域驅動設計》

一篇帶你入門DDD實戰(zhàn)的評論 (共 條)

分享到微博請遵守國家法律
股票| 辽宁省| 会同县| 洛浦县| 上高县| 宁南县| 定襄县| 石台县| 章丘市| 哈巴河县| 新津县| 马关县| 阿克| 饶河县| 宜丰县| 德安县| 东海县| 霍林郭勒市| 富川| 抚宁县| 博爱县| 井陉县| 金溪县| 溧水县| 万荣县| 平阴县| 汉中市| 冷水江市| 济宁市| 连云港市| 多伦县| 泰兴市| 沙湾县| 天祝| 深泽县| 扎赉特旗| 永春县| 洱源县| 涿鹿县| 类乌齐县| 苏州市|