領域驅動設計的基礎知識總結

1. 什麼是領域(Domain)

咱們所作的軟件系統的目的都是來解決一系列問題,例如作一個電商系統來在線銷售本身企業的產品;作一個灰度發佈平臺來提高服務的質量和穩定性。任何一個系統都會屬於某個特定的領域,例如:html

  • 論壇是一個領域:要作一個論壇,那這個論壇的核心業務是肯定的:好比用戶發帖、回帖等核心基本功能;
  • 電商系統是一個領域:只要是電商領域的系統,那核心業務就是:商品瀏覽、購物車、下單、減庫存、付款交易等核心環節;

同一個領域的系統都具備相同的核心業務,由於他們要解決的問題的本質是相似的。所以能夠推斷:一個領域本質上能夠理解爲一個 問題域 。只要肯定了系統所屬的領域,那麼這個系統的核心業務,即要解決的關鍵問題就基本肯定了。一般咱們說,要成爲一個領域的專家,必需要在這個領域深刻研究不少年才行,只有這樣纔會遇到很是多的該領域的問題,積累了豐富的經驗。算法

2.界限上下文(Bounded Context)

一般來講,一個領域有且只有一個核心問題,咱們稱之爲該領域的『核心子域』。在覈心子域、通用子域、支撐子域梳理的同時,會定義出子域中的『限界上下文』及其關係,用它來 闡述子域之間的關係 。界限上下文能夠簡單理解成一個子系統或組件模塊。數據庫

例如:下圖是對酒店管理的子域和界限上下文的梳理:設計模式

3. 領域模型(Domain Model)

領域驅動設計(Domain-Driven Design)分爲兩個階段:安全

  1. 以一種領域專家、設計人員、開發人員都能理解的通用語言做爲相互交流的工具,在交流的過程當中發現領域概念,而後將這些概念設計成一個領域模型;
  2. 由領域模型驅動軟件設計,用代碼來實現該領域模型;

因而可知,領域驅動設計的核心是創建正確的領域模型。領域模型具備如下特色:架構

  1. 對具備某個邊界的領域的一個抽象,反映了領域內用戶 業務需求的本質 。它屬於『解決問題空間』。領域模型是有邊界的,只反應了咱們在領域內所關注的部分,包括 實體概念(如:貨物,書本,應聘記錄,地址等),以及 過程概念(如:資金轉帳等);
  2. 提升軟件的 可維護性,業務可理解性以及可重用性。領域模型確保了咱們的軟件的業務邏輯都在一個模型中,幫助開發人員相對平滑地將領域知識轉化爲軟件構造;
  3. 貫穿軟件 分析、設計、開發 的整個過程。領域專家、設計人員、開發人員面向同一個模型進行交流,彼此共享知識與信息,因此能夠防止需求走樣,讓軟件開發人員作出來的軟件真正知足需求;要創建正確的領域模型並不簡單,須要領域專家、設計、開發人員積極溝通共同努力,而後才能使你們對領域的認識不斷深刻,從而不斷細化和完善領域模型;
  4. 爲了讓領域模型看的見,使用的經常使用表達領域模型的方式:圖、代碼或文字;
  5. 重要性:領域模型是整個軟件的核心,是軟件中最有價值和最具競爭力的部分;設計足夠精良且符合業務需求的領域模型可以更快速的響應需求變化;

4. 領域通用語言

由軟件專家和領域專家合做開發一個領域的模型是有必要的。開發過程當中, 開發人員以類、算法、設計模式、架構等進行思考與交流。但領域專家對此一無所知,他們對技術上的術語沒有太多概念,只瞭解特有的領域專業技能,例如:在空中交通監控樣例中,領域專家知道飛機、路線、海拔、經度、緯度,他們有本身的術語來討論這些事情。軟件專家和領域專家交流過程當中,須要作翻譯才能讓對方理解這些概念。函數

領域驅動設計的一個核心原則是使用一種基於模型的語言。使用模型做爲語言的核心骨架,要求團隊在進行全部的交流是都使用一致的語言,在代碼中也是這樣,這種語言被稱爲『通用語言』。工具

5.建模思考的問題:用戶需求

『用戶需求』不能等同於『用戶』,捕捉『用戶心中的模型』也不能等同於『以用戶爲核心設計領域模型』。設計領域模型時不能以用戶爲出發點去思考問題,不能老想着用戶會對系統作什麼;而應該從一個客觀的角度,根據用戶需求挖掘出領域內的相關事物,思考這些事物的本質關聯及其變化規律做爲出發點去思考問題。性能

領域模型是 排除了人以外的客觀世界模型 ,包含了人所扮演的參與者角色。可是通常狀況下不要讓參與者角色在領域模型中佔據主要位置,不然各個系統的領域模型將變得沒有差異,由於軟件系統就是一我的機交互的系統,都是以人爲主的活動記錄或跟蹤。例如:學習

  • 論壇中若是以人爲主導,那麼領域模型就是:人發帖,人回帖,人結貼,等等;
  • 貨物託運系統中若是以人爲主導,就變成了:託運人託運貨物,收貨人收貨物,付款人付款,等等;

以一個貨物運輸系統爲例子簡單說明一下。在用戶需求相對明朗以後,這樣描述領域模型:

  • 一個Cargo(貨物)涉及多個Customer(客戶,如託運人、收貨人、付款人),每一個Customer承擔不一樣的角色;
  • Cargo的運送目標已指定,即Cargo有一個運送目標;
  • 由一系列知足Specification(規格)的Carrier Movement(運輸動做)來完成運輸目標;

以上描述沒有從用戶的角度去描述領域模型,而是以領域內的相關事物爲出發點,考慮這些事物的本質關聯及其變化規律的:

  • 以貨物爲中心,把客戶當作是貨物在某個場景中可能會涉及到的關聯角色,如貨物會涉及到託運人、收貨人、付款人;
  • 貨物有一個肯定的目標,貨物會通過一系列的運輸動做到達目的地。

以用戶爲中心來思考領域模型的思惟只是停留在需求的表面,而沒有挖掘出真正的需求的本質。領域建模時須要努力挖掘用戶需求的本質,這樣才能真正實現用戶需求。

6. 經典分層架構

 

用戶界面/展現層:1)請求應用層獲取用戶所需的展現數據;2)發送命令給應用層執行用戶的命令

應用層:薄薄的一層,定義軟件要完成的任務。對外爲展現層提供各類應用功能,對內調用領域層(領域對象或領域服務)完成各類業務邏輯。應用層不包含業務邏輯

領域層:表達業務概念、業務狀態信息及業務規則,是業務軟件的核心

基礎設施層:爲其餘層提供通用的技術能力,提供了層間通訊;爲領域層提供持久化機制。

7. 使用的模式

7.1. 總覽圖

7.2. 關聯的設計

關聯在領域建模的過程當中很是重要,關聯的設計能夠遵循以下的一些原則:

  • 關聯 儘可能少。對象之間複雜的關聯容易造成對象的關係網,對於理解和維護單個對象很不利,同時也很難劃分對象與對象之間的邊界;另外,減小關聯有助於簡化對象之間的遍歷;
  • 關聯儘可能保持 單向 的關聯;
  • 在創建關聯時,須要挖掘是否存在關聯的 限制條件 。若是存在,那麼最好把限制條件加到關聯上,每每這樣的限制條件能將關聯化繁爲簡,即將多對多簡化爲1對多,或將1對多簡化爲1對1;

7.3. 實體(Entity)

實體就是領域中須要 惟一標識 的領域概念。由於咱們有時須要區分是哪一個實體:有兩個實體,若是惟一標識不同,那麼即使實體的其餘全部屬性都同樣,也認爲他們是兩個不一樣的實體。

不該該給實體定義太多的屬性或行爲,而應該尋找關聯,將屬性或行爲轉移到其餘關聯的實體或值對象上。好比:Customer 實體,有一些地址信息,因爲地址信息是一個完整的有業務含義的概念,因此咱們能夠定義一個 Address 對象,而後把 Customer 的地址相關的信息轉移到 Address 對象上。若是沒有 Address 對象,而把這些地址信息直接放在 Customer 對象上,而後對於一些其餘的相似Address的信息也都直接放在Customer 上,會致使 Customer 對象很混亂,結構不清晰,最終致使它難以維護和理解。

7.4. 值對象(Value Object)

並非每個事物都必須有一個惟一標識。就以上面的地址對象 Address 爲例,若是兩個 Customer 的地址信息是同樣的,咱們就會認爲這兩個 Customer 的地址是同一個。用程序的方式來表達就是:若是兩個對象全部屬性的值都相同,咱們會認爲它們是同一個對象,那麼就能夠把這種對象設計爲值對象。

值對象的特徵:

  • 值對象 沒有惟一標識 ,這是它和實體的最大不一樣。值對象在判斷是不是同一個對象時是經過它們的全部屬性是否相同,若是相同則認爲是同一個值對象。在區分是不是同一個實體時,只看實體的惟一標識是否相同,而無論實體的屬性是否相同。
  • 值對象是 不可變 的,即全部屬性都是隻讀的,因此能夠被安全的共享。

應該給值對象設計的儘可能簡單,不要讓它引用不少其餘的對象。值對象只是一個值,相似(int a = 3)中的『3』,只不過是用對象來表示。值對象雖然是隻讀的,是一個完整的不可分割的總體,可是能夠被整個替換掉:相似(a = 4)把a的值由『3』替換爲爲『4』,當修改 Customer 的 Address 對象引用時,不是經過 Customer.Address.Street 這樣的方式來修改屬性,能夠這樣作:Customer.Address = new Address(…)

7.5. 領域服務(Domain Service)

領域中的一些概念不太適合建模爲對象(實體對象或值對象),由於它們本質上就是一些操做、動做,而不是事物。這些操做每每須要 協調多個領域對象。若是強行將這些操做職責分配給任何一個對象,則被分配的對象就是承擔一些不應承擔的職責,從而會致使對象的職責不明確很混亂。DDD認爲領域服務模式是一個很天然的範式用來對應這種跨多個對象的操做。通常的領域對象都是有狀態和行爲的,而領域服務沒有狀態只有行爲。

領域服務還有一個很重要的功能就是能夠避免領域邏輯泄露到應用層。由於若是沒有領域服務,那麼應用層會直接調用領域對象完成本該是屬於領域服務該作的操做,須要瞭解每一個領域對象的業務功能,以及它可能會與哪些其餘領域對象交互等一系列領域知識。這樣一來,領域層可能會把一部分領域知識泄露到應用層。對於應用層來講,經過調用領域服務提供的簡單易懂且意義明確的接口確定也要比直接操縱領域對象容易的多。

說到領域服務,還須要提一下軟件中通常有三種服務:應用層服務、領域服務、基礎服務。從如下的例子中能夠清晰的看出每種服務的職責:

應用層服務

  1. 獲取輸入(如一個XML請求)
  2. 發送消息給領域層服務,要求其實現轉賬的業務邏輯
  3. 領域層服務處理成功,則調用基礎層服務發送Email通知

領域層服務

  1. 獲取源賬號和目標賬號,分別通知源賬號和目標賬號進行扣除金額和增長金額的操做
  2. 提供返回結果給應用層

基礎層服務

  1. 按照應用層的請求,發送Email通知

7.6. 聚合及聚合根(Aggregate,Aggregate Root)

聚合定義了一組具備 內聚關係 的相關對象的集合,以及對象之間清晰的所屬關係和邊界,避免了錯綜複雜的難以維護的對象關係網的造成。咱們把聚合看做是一個修改數據的單元。

聚合有如下特色:

  1. 每一個聚合有一個根和一個邊界:根是聚合內的某個實體;邊界定義了一個聚合內部有哪些實體或值對象;
  2. 聚合根是外部能夠保持對聚合引用的惟一元素,負責與外部其餘對象打交道並維護本身內部的業務規則。聚合內部的對象之間能夠相互引用,可是聚合外部若是要訪問聚合內部的對象時,必須經過聚合根開始導航,絕對不能繞過聚合根直接訪問聚合內的對象;
  3. 聚合內除根之外的其餘實體的惟一標識都是本地標識,也就是隻要在聚合內部保持惟一便可,由於它們老是從屬於這個聚合的;
  4. 聚合內部的對象能夠保持對其餘聚合根的引用;
  5. 刪除一個聚合根時必須同時刪除該聚合內的全部相關對象,由於他們都同屬於一個聚合,是一個完整的概念;
  6. 基於聚合的以上概念,咱們能夠推論出從數據庫查詢時的單元也是以聚合爲一個單元,不能直接查詢聚合內部的某個非根的對象;

如何識別聚合:

能夠從業務的角度分析哪些對象它們的關係是內聚的,可當作一個總體來考慮的,而後這些對象能夠放在一個聚合內。關係內聚是指這些對象之間必須保持一個固定規則,固定規則是指在數據變化時必須保持不變的一致性規則。當修改一個聚合時,必須在 事務級別 確保整個聚合內的全部對象知足這個固定規則。聚合儘可能不要太大,不然可能帶來必定的性能問題。一般在大部分領域模型中,有70%的聚合一般只有一個實體,即聚合根,該實體內部沒有包含其餘實體,只包含一些值對象;另外30%的聚合中,基本上也只包含兩到三個實體。

如何識別聚合根:

若是一個聚合只有一個實體,那麼這個實體就是聚合根;若是有多個實體,那麼咱們能夠思考聚合內哪一個對象有獨立存在的意義而且能夠和外部直接進行交互。

7.7. 工廠(Factory)

DDD中的工廠也是一種體現 封裝思想 的模式。DDD中引入工廠模式的緣由是:有時建立一個領域對象是一件比較複雜的事情,不只僅是簡單的new操做。工廠是用來封裝建立一個複雜對象尤爲是聚合時所需的知識,將建立對象的細節(如何實例化對象,而後作哪些初始化操做)隱藏起來。

客戶傳遞給工廠一些簡單的參數,若是參數符合業務規則,則工廠能夠在內部建立出一個相應的領域對象返回給客戶;可是若是參數無效,應該拋出異常,以確保不會建立出一個錯誤的對象。固然也並不老是須要經過工廠來建立對象,事實上大部分狀況下領域對象的建立都不會太複雜,只須要簡單的使用構造函數就能夠了。隱藏建立對象的好處:能夠不讓領域層的業務邏輯泄露到應用層,同時也減輕了應用層的負擔,它只須要簡單的調用領域工廠建立出指望的對象便可。

7.8. 倉儲(Repository)

倉儲被設計出來的緣由:領域模型中的對象自從建立後不會一直留在內存活動,當它不活動時會被持久化到DB中,當須要的時候會重建該對象。因此,重建對象是一個和DB打交道的過程,須要提供一種機制,提供相似集合的接口來幫助咱們 管理對象

倉儲裏存放的對象必定是聚合,由於以前提到的領域模型是以聚合的概念來劃分邊界的。咱們 只對聚合設計倉儲 ,把整個聚合當作一個總體,要麼一塊兒取出來,要麼一塊兒被刪除,不會單獨對某個聚合內的子對象進行單獨查詢和更新。倉儲還有一個重要的特徵就是分爲倉儲定義部分和倉儲實現部分,在領域模型中定義倉儲的接口,而在基礎設施層實現具體的倉儲。

8.設計領域模型時通常步驟

  1. 根據需求創建初步的領域模型,識別明顯的領域概念和之間的關聯(1:1, 1:n的關係),用文字精確沒有歧義的描述出每一個領域概念的含義;
  2. 分析主要的軟件功能,識別主要的應用層的類,這樣有助於及早發現哪些是應用層的職責,哪些是領域層的職責;
  3. 進一步分析領域模型,識別出實體、值對象、領域服務;
  4. 分析關聯,經過對業務的深刻分析和軟件設計原則及性能方面的權衡,明確關聯的方向,去掉一些不須要的關聯;
  5. 找出聚合邊界及聚合根,在分析過程當中會出現難以清洗判斷的選擇問題,這就依賴平時分析經驗的積累了;
  6. 爲聚合根配置倉儲,通常狀況下爲一個聚合分配一個倉儲,此時設計好倉儲的接口便可;
  7. 遍歷全部場景,肯定設計的領域模型能有效解決業務需求;
  8. 考慮如何建立實體和值對象,是經過工廠仍是構造函數;
  9. 重構模型,尋找模型中有疑問或蹩腳的地方,好比思考:聚合的設計是否正確,模型的性能等等;

領域建模是一個不斷重構,持續完善的過程,你們會在討論中將變化的部分反映到模型中,從而模型不斷細化並朝正確的方向走。

9. 參考

本文是閱讀學習 湯雪華的博客 後所作的一些整理,但願能對你們有所幫助~

相關文章
相關標籤/搜索