[轉]淺析DDD(領域驅動設計)

?

最近在做一些微服務相關的設計,內容包括服務的劃分,Restful API的設計等。其中比較棘手的就是Service的職責劃分:如何抽象具有統一業務范疇的Model,使其模塊化,又如何高度提煉并組合多模塊,使得業務可獨立服務化。為了找尋答案,看了不少書籍和博客,在DDD中找到了一些思路,個人覺得受益匪淺,或許也可以受用于大家,特分享于此。

?

什么是DDD

?

軟件開發不是一蹴而就的事情,我們不可能在不了解產品(或行業領域)的前提下進行軟件開發,在開發前,通常需要進行大量的業務知識梳理,而后到達軟件設計的層面,最后才是開發。而在業務知識梳理的過程中,我們必然會形成某個領域知識,根據領域知識來一步步驅動軟件設計,就是領域驅動設計的基本概念。

?

聽起來這和傳統意義的軟件開發沒啥區別,只是換了點新鮮的名詞而已,其實不然。

?

軟件開發 VS DDD

?

一般軟件設計或者說軟件開發分兩種:瀑布式敏捷式

?

前者一般是項目經理經過大量的業務分析后,會基于現有需求整理出一個基本模型,再將結果傳遞給開發人員,這就是開發人員的需求文檔,他們只需要照此開發便是。這種模式下,是很難頻繁的從用戶那里得到反饋,因此在前期分析時就已經默認了這個業務模型是正確的,那么結果可想而之,數月甚至數年后交付的時候,必然和客戶的預期差距較大。

?

后者在此基礎上進行了改進,它也需要大量的分析,范圍會設計到更精細的業務模塊,它是小步迭代,周期性交付,那么獲取客戶的反饋也就比較頻繁和及時。可敏捷也不能夠將業務中的方方面面都考慮到,并且敏捷是擁抱變化的,大量的需求或者業務模型變更必將帶來不小的維護成本,同時,對人(Developer)的要求也必然會更高。

?

DDD則不同:它像是更小粒度的迭代設計,它的最小單元是領域模型(Domain Model),所謂領域模型就是能夠精確反映領域中某一知識元素的載體,這種知識的獲取需要通過與領域專家(Domain Expert)進行頻繁的溝通才能將專業知識轉化為領域模型。領域模型無關技術,具有高度的業務抽象性,它能夠精確的描述領域中的知識體系;同時它也是獨立的,我們還需要學會如何讓它具有表達性,讓模型彼此之間建立關系,形成完整的領域架構。通常我們可以用象形圖或一種通用的語言(Ubiquitous Language)去描述它們之間的關系。在此之上,我們就可以進行領域中的代碼設計(Domain Code Design)。如果將軟件設計比做是造一座房子,那么領域代碼設計就好比是貼壁紙。前者已經將房子的藍圖框架規劃好,而后者只是一個小部分的設計:如果墻紙貼錯了,我們可以重來,可如果房子結構設計錯了,那可就悲劇了。

?

建立領域知識(Build Domain Model)

?

說了這么多領域模型的概念,到底什么是領域模型呢?以飛機航行為例子:

?

現要為航空公司開發一款能夠為飛機提供導航,保證無路線沖突監控軟件。那我們應該從哪里開始下手呢?根據DDD的思路,我們第一步是建立領域知識:作為平時管理和維護機場飛行秩序的工作人員來說,他們自然就是這個領域的專家,我們第一個目標就是與他們溝通,也許我們并不能從中獲取所有想要的知識,但至少可以篩選出主要的內容和元素。你可能會聽到諸如起飛,著陸,飛行沖突,延誤等領域名詞,讓們從一個簡單的例子開始(就算是錯誤的也沒關系):

?

  • 起點->飛機->終點

?

這個模型很直接,但有點過于簡單,因為我們無法看出飛機在空中做了什么,也無法得知飛機怎么從起點到的終點,剛才我們似乎提到無路線沖突,那么如此似乎會好些:

?

  • 飛機->路線->起點/終點

?

既然點構成線,那何不:

?

  • 飛機->路線->points(含起點,終點)

?

這個過程,是我們不斷建立領域知識的過程,其中的重點就是尋找領域專家頻繁溝通,從中提煉必要領域元素。

?

盡管看起來還是很簡單,但我們已經開始一步步的在建立領域對象和領域模型了。

?

通用語言(Ubiquitous Language)

?

上面的例子的確看起來簡單,但過程并非容易:我們(開發人員)和領域專家在溝通的過程中是存在天然屏障的:我們滿腦子都是類,方法,設計模式,算法,繼承,封裝,多態,如何面向對象等等;這些領域專家是不懂的,他們只知道飛機故障,經緯度,航班路線等專業術語。

?

所以,在建立領域知識的時候,我們(開發人員和領域專家)必須要交換知識,知識的范圍范圍涉及領域模型的各個元素,如果一方對模型的描述令對方感到困惑,那么應該立刻換一種描述方式,直到雙方都能夠接受并且理解為止。在這一過程中,就需要建立一種通用語言,作為開發人員和領域專家的溝通橋梁。

?

可如何形成這種通用語言呢?其實答案并不唯一,確切的說也沒有什么標準答案。

?

a)UML
利用UML可以清晰的表現類,并且展示它們之間的關系。但是一旦聚合關系復雜,UML葉子節點將會變的十分龐大,可能就沒有那么直觀易懂了。最重要的是,它無法精確的描述類的行為。為了彌補這種缺陷,可以為具體的行為部分補充必要說明(可以是標簽或者文檔),但這往往又很耗時,而且更新維護起來十分不便。
b)偽代碼
極限編程是推薦這么做的,這個辦法對程序猿來說固然好,可立刻就要將現有模型映射到代碼層面,這對人的要求也是不低,并不容易實現。

?

模型驅動設計(Domain Driven Design)

?

模型關系圖(Model-Driven Design)

?

領域驅動設計中的模型關系圖如下:

?

?

層結構(Layered Architecture)

?

?

  • User Interface

?

負責向用戶展現信息,并且會解析用戶行為,即常說的展現層。

?

  • Application Layer

?

應用層沒有任何的業務邏輯代碼,它很簡單,它主要為程序提供任務處理。

?

  • Domain Layer

?

這一層包含有關領域的信息,是業務的核心,領域模型的狀態都直接或間接(持久化至數據庫)存儲在這一層。

?

  • Infrastructure Layer

?

為其他層提供底層依賴操作。

?

層結構的劃分是很有必要的,只有清晰的結構,那么最終的領域設計才宜用,比如用戶要預定航班,向Application Layer的service發起請求,而后Domain Layler從Infrastructure Layer獲取領域對象,校驗通過后會更新用戶狀態,最后再次通過Infratructure Layer持久化到數據庫中。

?

實體(Entity) & 值對象(Value Object)

?

實體與面向對象中的概念類似,在這里再次提出是因為它是領域模型的基本元素。在領域模型中,實體應該具有唯一的標識符,從設計的一開始就應該考慮實體,決定是否建立一個實體也是十分重要的。

?

值對象和我們說的編程中數值類型的變量是不同的,它僅僅是沒有唯一標識符的實體,比如有兩個收獲地址的信息完全一樣,那它就是值對象,并不是實體。值對象在領域模型中是可以被共享的,他們應該是“不可變的”(只讀的),當有其他地方需要用到值對象時,可以將它的副本作為參數傳遞。

?

服務(Services)

?

當我們在分析某一領域時,一直在嘗試如何將信息轉化為領域模型,但并非所有的點我們都能用Model來涵蓋。對象應當有屬性,狀態和行為,但有時領域中有一些行為是無法映射到具體的對象中的,我們也不能強行將其放入在某一個模型對象中,而將其單獨作為一個方法又沒有地方,此時就需要服務.

?

服務是無狀態的,對象是有狀態的。所謂狀態,就是對象的基本屬性:高矮胖瘦,年輕漂亮。服務本身也是對象,但它卻沒有屬性(只有行為),因此說是無狀態的。

?

PS:這與我們常說的服務器的狀態是兩個概念,無狀態的服務器是指,對服務器來說每次接收到的HTTP請求都像是客戶端第一次發送的一樣;而有狀態的服務器就會存儲客戶端的狀態,常見的就是Cookie&Session

?

服務存在的目的就是為領域提供簡單的方法。為了提供大量便捷的方法,自然要關聯許多領域模型,所以說,行為(Action)天生就應該存在于服務中。

?

服務具有以下特點:

?

a)服務中體現的行為一定是不屬于任何實體和值對象的,但它屬于領域模型的范圍內
b)服務的行為一定設計其他多個對象
c)服務的操作是無狀態的

?

PS:不要隨意放置服務,如果該行為是屬于應用層的,那就應該放在那;如果它為領域模型服務,那它就應該存儲在領域層中,要避免業務的服務直接操作數據庫,最好通過DAO。

?

模塊(Moudles)

?

對于一個復雜的應用來說,領域模型將會變的越來越大,以至于很難去描述和理解,更別提模型之間的關系了。模塊的出現,就是為了組織統一的模型概念來達到減少復雜性的目的的。而另一個原因則是模塊可以提高代碼質量和可維護性,比如我們常說的高內聚,低耦合就是要提倡將相關的類內聚在一起實現模塊化。

?

模塊應當有對外的統一接口供其他模塊調用,比如有三個對象在模塊a中,那么模塊b不應該直接操作這三個對象,而是操作暴露的接口。模塊的命名也很有講究,最好能夠深層次反映領域模型。

?

聚合(Aggregates)

?

聚合被看作是多個模型單元間的組合,它定義了模型的關系和邊界。每個聚合都有一個根,根是一個實體,并且是唯一可被外訪問的。正是如此,聚合可以保證多個模型單元的不變性,因為其他模型都參考聚合的根。所以要想改變其他對象,只能通過聚合的根去操作。根如果沒有了,那么聚合中的其他對象也將不存在。
一個簡單的例子如下:

?

?

customer是該聚合的根,其他的都是內部對象,如果外部需要用戶地址,拷貝一份傳遞出去即可。顯而易見,用戶如果不存在,其他信息均無意義。

?

工廠(Factories)

?

在大型系統中,實體和聚合通常是很復雜的,這就導致了很難去通過構造器來創建對象。工廠就決解了這個問題,它把創建對象的細節封裝起來,巧妙的實現了依賴反轉。當然對聚合也適用(當建立了聚合根時,其他對象可以自動創建)。工廠最早被大家熟知可能還是在設計模式中,的確,在這里提到的工廠也是這個概念。

?

但是不要盲目的去應用工廠,以下場景不需要工廠:
a)構造器很簡單
b)構造對象時不依賴于其他對象的創建
c)用策略模式就可以解決

?

倉庫(Repository)

?

倉庫封裝了獲取對象的邏輯,領域對象無須和底層數據庫交互,它只需要從倉庫中獲取對象即可。倉庫可以存儲對象的引用,當一個對象被創建后,它可能會被存儲到倉庫中,那么下次就可以從倉庫取。如果用戶請求的數據沒在倉庫中,則會從數據庫里取,這就減少了底層交互的次數。當然,倉庫獲取對象也是有策略的,如下:

?

?

PS:倉庫看起來有些像Infrastructure Layer的東西,但其實不然,倉庫更像是本地緩存,需要時才會訪問數據庫

?

結束語

?

CQRS本身也是一種架構模式,但更多的是它被應用在DDD中。因為DDD中有工廠倉庫來管理領域模型,前者主要用于創建,而后者則用于存儲。這就表明在DDD中是默認將讀寫分離的,DDD似乎就天生和CQRS有著無縫的鏈接。

?

CQRS往往要求數據庫進行讀寫分離,具體來說,所有的更新操作均無返回值(void),而讀操作才返回對應的值。在實現CQRS時,又和事件源(Event Source)相結合,以下是一個簡單的交互過程:

?

客戶端發起一個請求,服務端將其映射為一個命令,該命令會從倉庫中讀取一個相關的聚合,對該聚合進行操作,將會生成一個事件源,將該事件發送出去,接收方收到消息后(并不是立刻)將會更新領域對象,完成一次更新操作。

?

在此基礎上,還有稱之為六邊形的架構風格,它將DDD的領域模型包裹在內,外圍含有多種適配器來適配各種通信方式,總體來說,我覺得無論是DDD,CQRS還是六邊形,都是一種架構的設計思路,沒有絕對的優勢,同時也有各自的復雜度,并不容易理解,但有時在軟件設計時,不妨多學習一下其中的小細節和思路,必然能夠有所收獲。

?

至于能否應用?如何應用?,筆者只能說不能生搬硬套,需要有一定的實踐經驗才能去嘗試,一般情況下,結合項目特點,能適當的靈活采用其中的設計思路即可。

?

參考資料:

?

Domain Driven Design Quickly

?

領域驅動設計(DDD)實現之路

?

DDD領域驅動設計基本理論知識總結

?

淺談命令查詢職責分離(CQRS)模式

?

你應該知道的四種優秀架構


---------------------
作者:喜歡特別冷的冬天下著雪
來源:CSDN
原文:https://blog.csdn.net/kkkkkxiaofei/article/details/62237121
版權聲明:本文為作者原創文章,轉載請附上博文鏈接!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/285676.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/285676.shtml
英文地址,請注明出處:http://en.pswp.cn/news/285676.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【PhotoScan精品教程】任務三:生成密集點云、生成網格、生成DEM、生成DOM

《無人機航空攝影測量精品教程》合集目錄(Pix4d、CC、EPS、PhotoScan、Inpho) 【PhotoScan精品教程】任務一:新建工程、導入照片、設置坐標系、導入控制點(POS)、自由空三 【PhotoScan精品教程】任務二:刺像控點,平差,質量報告精度檢查,像控點POS權重調配 【PhotoScan…

windows環境實現批量加密文件,并創建加密文件同名(不帶后綴)的文件夾,然后把加密文件和圖片和文本放入這個文件夾。

1、 需求 我想把資源文件先加密成壓縮文件,然后同時創建每個加密壓縮文件同名的文件夾,同時需要把這個加密文件拷貝到這個同名的文件夾,然后還需要把一個圖片和一個文本文檔同時放進這個文件夾,然后在不加密壓縮這個文件夾&#…

.NET7之MiniAPI(特別篇) :Preview5優化了JWT驗證(上)

在.NET7的Preview5中,優化了asp.net core中的JWT驗證,不用像以前繁瑣了,更重要的是帶來了一組生成Token的工具,可以讓開發人員或測試人員不需登錄獲取Token,而達到測試的目的。創建項目現在來看一下怎么使用&#xff0…

iOS - UTI

一、UTI概念 1、什么是UTI Uniform Type Identifier,是字符串,格式標識符。 根據UTI,可得到相應的其他類型的格式標識符。比如public.jpeg對應于: A four-character file type code (an OSType) of JPEGA filename extension of .…

【Inpho精品教程】任務一:Inpho預處理準備(Pix4d生成未畸變圖像、Pix4d生成相機參數文件)

《無人機航空攝影測量精品教程》合集目錄(Pix4d、CC、EPS、PhotoScan、Inpho) Inpho軟對于初學者來說,可能不像pix4d、PhotoScan等那么得心應手,處理過程也稍微有些復雜。Inpho處理的是POS分離的照片,在新建工程之前,需要獲取相機參數、未畸變圖像等,而這些前期的工作一…

學習MongoDB(三) Add an Arbiter to Replica Set 集群中加入仲裁節點

Add an Arbiter to Replica Set 在集群中加入仲裁節點,當集群中主節點掛掉后負責選出新的主節點,仲裁節點也是一個mongo實力,但是它不存儲數據。 1、仲裁節點消耗很小的資源,而且不需要專用的服務器。 2、不能把仲裁節點安裝到集…

[轉]new Thread的弊端及Java四種線程池的使用

介紹new Thread的弊端及Java四種線程池的使用,對Android同樣適用。本文是基礎篇,后面會分享下線程池一些高級功能。 1、new Thread的弊端 執行一個異步任務你還只是如下new Thread嗎? new Thread(new Runnable() {Overridepublic void run() …

【Inpho精品教程】任務二:Inpho創建工程(創建項目、新建相機參數、導入照片、導入POS、生成航條、保存項目)

《無人機航空攝影測量精品教程》合集目錄(Pix4d、CC、EPS、PhotoScan、Inpho) 在任務一中,我們做了Inpho創建工程前的數據預處理工作,獲取了未畸變的圖像、相機參數、Inpho支持的POS等數據,本文在此基礎之上,創建Inpho工程,主要步驟有:創建項目、新建相機參數、導入照片…

return view詳解

1、return View(); 返回值 類型:System.Web.Mvc.ViewResult將視圖呈現給響應的 View() 結果。 注釋 View() 類的此方法重載將返回一個具有空 ViewName 屬性的 ViewResult 對象。 如果你正在編寫控制器操作的單元測試,則需為那些不采用字符串視圖名稱的單…

C語言試題九十一之寫一個程序,用公式π/4=1-1/3+1/5-1/7+...,求π的近似值,直到最后一項的絕對值小于10^-8為止。

1、題目 寫一個程序,用公式π/4=1-1/3+1/5-1/7+...,求π的近似值,直到最后一項的絕對值小于10^-8為止。 2 、溫馨提示 C語言試題匯總里可用于計算機二級C語言筆試、機試、研究生復試中C程序設計科目、幫助C語言學者打好程序基礎、C語言基礎,鍛煉您的邏輯思維和解決問…

C# 切換中英文輸入法

本文經原作者授權以原創方式二次分享,歡迎轉載、分享。原文作者:唐宋元明清原文地址:https://www.cnblogs.com/kybs0/p/10298697.htmlC# 切換中英文輸入法在界面輸入時,有時需要限定輸入法。在不自定義正則表達式或者其它輸入處理…

[轉].NET 開源項目 Polly 介紹

今天介紹一個 .NET 開源庫:Polly,它是支持 .NET Core 的,目前在 GitHub 的 Star 數量已經接近 5 千,它是一個強大且實用的 .NET 庫。 Polly 介紹 官方對 Polly 的介紹是這樣的: Polly is a .NET resilience and tran…

「 劉一哥GIS」CSDN專業技術博文專欄目錄索引

劉一哥GIS 個人簡介:劉一哥,多年研究地圖學、地理信息系統、遙感、攝影測量和GPS等應用,精通ArcGIS等軟件的應用,精通多門編程語言,擅長GIS二次開發和數據庫系統開發,具有豐富的行業經驗,致力于…

memcached 和 redis 的區別與選擇

沒有必要過多的關注性能。由于Redis只使用單核,而Memcached可以使用多核,所以在比較上,平均每一個核上Redis在存儲小數據時比Memcached性能更高。而在100k以上的數據中,Memcached性能要高于Redis,雖然Redis最近也在存儲…

C語言試題九十之實現輸入一行字符,分別統計出其中英文字母、空格、數字和其他字符的個數。

??個人主頁:個人主頁 ??系列專欄:C語言試題200例目錄 ??推薦一款刷算法、筆試、面經、拿大公司offer神器 ?? 點擊跳轉進入網站 ?作者簡介:大家好,我是碼莎拉蒂,CSDN博客專家(全站排名Top 50),阿里云博客專家、51CTO博客專家、華為云享專家 1、題目 輸入一行字…

數據庫備份需要注意的

2019獨角獸企業重金招聘Python工程師標準>>> 1、PHPMYADMIN無法導出大數據表的 如果你的數據庫中有上百個數據表,并且有很多數據表記錄都超過了1G,還有很多INNODB數據表,這個時候用PHPMYADMIN導出,你就會發現恢復后可能…

在 .NET 6 中使用 dotnet format 格式化代碼

我不得不承認,在 code review 的時候,我花費了很多時間來研究 C# 的代碼格式問題,這是沒有太大意義的工作,我應該專注于其他事情,而不是觀察同事是否忘記格式化代碼,或者是使用了其他不同規則的代碼編輯器。…

【天工Godwork精品教程】任務一:創建工程(導入相片、編輯相機參數、導入POS)

無人機航測精品專欄鏈接:《無人機航空攝影測量精品教程》 【天工Godwork精品教程】任務一:創建工程(導入相片、編輯相機參數、導入POS) 【天工Godwork精品教程】任務二:導入控制點、POS權重設置、自由空三 【天工Godwork精品教程】任務三:刺像控點、空三平差、精度優化調…

[轉]數據庫事務ACID特性

ACID特性 數據庫管理系統中事務(transaction)的四個特性(分析時根據首字母縮寫依次解釋):原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性&#xff…

dotnet-exec 0.4.0 released

dotnet-exec 0.4.0 releasedIntrodotnet-exec 是一個 C# 程序的命令行小工具,可以用來運行一些簡單的 C# 程序而無需創建項目文件,而且可以自定義項目的入口方法,支持但不限于 Main 方法Install/Updatedotnet-exec 是一個 dotnet tool&#x…