領域驅動設計 讀書筆記 (1)

模型

  • 由不同部分組成

  • 用於特定目的

  • 抽象的系統

  • 認知工具

  • 模型有幾種表現方法(語言、代碼、圖解)

  • 一個系統包含若干模型

通用語言是作爲領域專家與軟件專家之間的協作而演進的。

好的面向對象設計

  • 單一職責原則:類只有一個職責
  • 開放封閉原則:類應該對修改關閉,但是對擴展開放。
  • 里氏替換原則:比如有個繼承關係Person和Student。可以使用Person的時候,也可以使用Student。但是,當反射的時候,處理Student的方法可能不需要Person。

採用模式的建議

  • 使用它,但未意識到使用它
  • 聽說它,閱讀了一些知識,開始嘗試
  • 瞭解更多,開始明確地使用
  • 開始熱衷,並傳播它
  • 突然有所頓悟
  • 學到更多,更成熟地、更含蓄地應用
  • 隨着時間流逝,看到缺點
  • 對概念提出疑問(常常因爲錯誤地應用了它)
  • 忘記了它,或者增加了知識和經驗
  • 使用它,但是未意識到使用它

從以數據庫爲中心,過渡到以領域模型爲中心(可以更加純粹地使用面向對象技術)。

  • 以領域模型爲中心的設計更清晰,更忠實於領域抽象的實現,可維護性更高。
  • 強大的領域模型是減少邏輯重複的有利工具。

解決方案輪廓:

  • 問題/特性列表
  • 逐個處理特性
    • 代碼是真實模型的最重要的表示
    • 通過測試代碼試驗模型,並獲得及時反饋

另一個維度:

  • 孤立或共享的實例
  • 領域模型實例化是有狀態的還是無狀態的
  • 領域模型的完整實例化和子集實例化

領域驅動設計

領域模型不是特殊的圖,而是圖所要傳達的思想。
不只是領域專家頭腦中的知識,而是對這類知識嚴格地組織,和有選擇的抽象。
圖可以表示或者傳達模型,文字也可以。
不是要建立符合現實的模型,而是概括地反映現實。

模型在領域驅動設計中的作用:

  • 模型和設計的核心相互影響。
  • 模型是所有團隊成員使用的通用語言的中樞
  • 模型是濃縮的知識。通過如何選擇術語、分解概念以及將概念聯繫起來,模型記錄了我們看待領域的方式。

有效建模的要素

  • 模型和實現的綁定。
  • 建立一種基於模型的語言。
  • 開發一個蘊含豐富知識的模型
  • 提煉模型
  • 頭腦風暴和實驗

知識消化不是孤立的活動,一般是在開發人員的領導下,由開發人員和領域專家組成的團隊來共同協作。
信息的原始資料來自領域專家頭腦中的知識,現有系統的用戶,以及技術團隊在相關遺留系統或者同領域的其他項目中積累的經驗。

模型聚焦於需求分析。與編程和設計緊密交互。

當我們的不再侷限於尋找實體和值對象時,我們才能充分吸取知識。因爲業務規則之間可能會存在不一致。

更明確的設計:

  • 程序員和其他相關人員都明確理解,明白它是重要的業務規則,而不是不起眼的計算

對象模型包括:屬性、關係、行爲和約束。
UML無法傳達模型的兩個重要方面,一是模型所表示的概念的意義,二是對象應該做哪些事情。

避免使用包羅萬象的對象模型圖。甚至不能使用包含所有細節的UML數據存儲庫。圖要簡單,只體現思想綱要。

任何參與建模的技術人員,不管在項目中的主要職責是什麼,都必須花時間瞭解代碼。
任何負責修改代碼的人則必須學會用代碼表達模型。
每一個開發人員都必須不同程度地參與模型討論並且你領域專家保持聯繫。
參與不同工作的人都必須有意識地通過UBIQUITOUS LANGUAGE與接觸代碼的人及時交換關於模型的想法。

模式:LAYERED ARCHITECTURE

分層:

  • 用戶界面層(controller)-向用戶顯示信息和解釋用戶指令
  • 應用層(service)-定義軟件要完成的任務,指揮表達領域概念的對象來解決問題。要儘量簡單,不包含業務規則
  • 領域層(實體bean)-表達業務概念、業務狀態信息和業務規則。
  • 基礎設施層(一般也是service)-爲上面各層提供通用的技術能力:爲應用層傳遞消息,爲領域層提供持久化工具,爲界面層繪製屏幕組件等。

每一層分別設計,內聚。
各層之間鬆散連接,層與層的依賴關係是單向的。上層可以使用或者操作下層。如果下層想與上層通信,可以使用回調或者OBSERVERS。

領域模型是一系列概念的集合。領域層的軟件構造反映了模型概念。

一個對象是用來表示某種具有連續性和標識的事物,還是用來描述某種狀態的屬性,是ENTITY和VALUE OBJECT之間的根本區別。
領域中還有一些方面適合用動作或者操作來表示,最好使用SERVICE。不要把操作的責任強加到ENTITY和VALUE OBJECT身上。SERVICE是應客戶端請求完成某事。
MODULE是模型的一部分,應該反映領域中的概念。

使關聯更容易控制:

  • 規定一個遍歷方向
  • 添加一個限定符,以便有效地減少多重關聯
  • 消除不必要的關聯

ENTITY有特殊的建模和設計思路。他們有生命週期,這期間他們的形式和內容可能發生根本改變,但必須保持一種內在的連續性。
爲了跟蹤這些對象,必須定義他們的標識。

比如顏色就是VALUE OBJECT。

  • 可以是其他對象的集合
  • 可以引用ENTITY
  • 經常作爲參數在對象之間傳遞消息
  • 可以作爲ENTITY的屬性
  • 所包含的屬性應該形成一個概念整體。比如街道、城市、郵編。
  • 不關心使用的是它的哪個實體
  • 兩個VALUE OBJECT之間的雙向關聯完全沒有意義

當我們只關心一個模型元素的屬性時,應該把它歸類爲VALUE OBJECT。應該使這個模型元素表示出其屬性的意義,併爲它提供相關功能。VALUE OBJECT應該是不可變的。不要爲它分配任何標識,而且不要把它設計成像ENTITY那麼複雜。

SERVICE的特徵:

  • 與領域概念相關的操作,不是ENTITY或者VALUE OBJECT的一個自然組成部分
  • 接口是根據領域模型的其他元素定義的
  • 操作是無狀態的(有副作用,但是不保持影響其自身行爲的狀態)

SERVICE並不只是在領域層中使用,要注意區分領域層的SERVICE和其他層的SERVICE。
領域層和應用層的SERVICE和基礎設施層的SERVICE協作。應用層SERVICE和領域層SERVICE可能很難分清。
領域層的SERVICE要判斷是否滿足臨界值。
賬戶之間的轉賬屬於領域層SERVICE,因爲它含有重要的業務層規則。

將SERVICE劃分到各層中

  • 應用層 - 資金轉賬應用服務
    • 獲取輸入
    • 發送消息給領域層服務,要求其執行
    • 監聽確認消息
    • 決定使用基礎設施層SERVICE發送通知
  • 領域層 - 資金轉賬領域服務
    • 與必要的賬戶和總帳對象交互,執行相應的借入和貸出操作
    • 提供結果的確認(允許轉賬或者拒絕等)
  • 基礎設施層 - 發送通知服務
    • 按照應用程序的指示發送郵件,和其他信息

SERVICE還有其他功能,可以控制領域層中接口的粒度,避免客戶端與ENTITY或者VALUE OBJECT耦合。
大型系統中,中等粒度的,無狀態的SERVICE更容易複用。因爲在簡單的接口背後封裝了重要的功能。
細粒度的對象可能導致分佈式的消息傳遞效率低下。

應用層負責對領域對象的行爲進行協調,因此,細粒度的領域對象可能會把領域層的知識泄漏到應用層中。這樣,應用層不得不處理複雜、細緻的交互。

MODULE是一個傳統的、較成熟的設計元素。

  • 可以在MODULE中查看細節,而不會被整個模型淹沒。
  • 觀察MODULE之間的關係,而不考慮內部細節。

MODULE從更大的角度描述了領域。
MODULE應該是低耦合、高內聚的。
MODULE不僅僅是代碼的劃分,也是概念的劃分。

MODULE和較小的元素好像應該共同演變,實際上並不是這樣。MODULE被用來組織早期對象。在這之後,對象在變化時不脫離現有模塊定義的邊界。重構MODULE要比重構類做更多的工作,更有破壞性,不會很頻繁。

領域對象的生命週期

挑戰:

  • 在整個生命週期中維護完整性
  • 防止模型陷入管理生命週期複雜性造成的困境當中

可以使用三種模式解決這些問題

  • AGGREGATE - 聚合。通過定義清晰的所屬關係和邊界,並避免混亂、錯綜複雜的對象關係網來實現模型的內聚。聚合模式對於維護生命週期各個階段的完整性具有重要作用
  • 在生命週期的開始階段,使用FACTORY創建、重建複雜對象,封裝內部結構。
  • 在生命週期的中間和末尾使用REPOSITORY來提供查找和檢索持久化對象並封裝龐大基礎設施的手段

FACTORY和REPOSITORY在AGGREGATE的基礎上操作,將特定生命週期轉換的複雜性封裝起來。

在具有複雜關聯的模型中,要保證對象更改的一致性是很難的。不僅互不關聯的對象需要遵守一些固定規則,而且緊密關聯的各組對象也要遵守一些固定規則。然而,過於謹慎的鎖定機制又會導致多個用戶之間的互相干擾,導致系統不可用。

模型中要有明確定義的邊界。

AGGREGATE就是一組相關對象的集合,把它作爲數據修改的單元。每個AGGREGATE都有一個ROOT和一個邊界。邊界定義了AGGREGATE內部有什麼。ROOT是AGGREGATE包含的一個特定ENTITY。對AGGREGATE而言,外部對象只能引用ROOT。而邊界內部的對象可以互相引用。除ROOT以外的其他ENTITY都有本地標識。比如汽車就是一個AGGREGATE。
Aggregate Root

固定規則(invariant)是指在數據變化時必須保持的一致性規則,其涉及AGGREGATE成員之間的內部關係。而任何跨越AGGREGATE的規則將不要求時刻保持最新狀態。通過事件處理、批處理或者其他更新機制,這些依賴會在一定時間內得以解決。但在每個事務完成時,AGGREGATE內部所應用的規則必須滿足。
只有ROOT纔可以直接通過數據庫查詢獲取。其他所有對象必須通過遍歷關聯來獲取。

應該將ENTITY和VALUE OBJECT分門別類地聚集到AGGREGATE中,並定義每個AGGREGATE的邊界。在每個AGGREGATE中,選擇一個ENTITY作爲ROOT。對內部成員的臨時引用可以被傳遞出去,但僅在一次操作中有效。由於ROOT控制訪問,因此不能繞過它修改內部對象。
AGGREGATE劃分出一個範圍,在這個範圍內,生命週期的每個階段都必須滿足一些固定規則。FACTORY和REPOSITORY都是在AGGREGATE之上執行操作。

當創建一個對象或整個AGGREGATE時,如果創建工作很複雜,或者暴露了過多內部結構,則使用FACTORY封裝。

應該將創建複雜對象的實例和AGGREGATE的職責轉移給單獨的對象,這個對象本身沒有承擔領域模型中的職責,但仍是領域設計的一部分。提供一個封裝所有複雜裝配的接口,這個接口不需要客戶引用要被實例化對象的具體類。在創建AGGREGATE時,要把它當作一個整體,並確保它滿足固定規則。

好的工廠要滿足兩個要求:

  • 每個創建方法都是原子的。生成的實例要處於一致的狀態。
  • 應該被抽象爲所需的類型,而不是要創建的具體類。

工廠和參數耦合。
由於VALUE OBJECT是不可變的,所以對應的工廠所生成的就是最終形式。

隨意的數據庫查詢會破壞領域對象的封裝和AGGREGATE。技術基礎設施和數據庫訪問機制的暴露會增加客戶的複雜度,並妨礙模型驅動的設計。

REPOSITORY將某種類型的所有對象表示爲一個概念集合(通常是模擬的),它的行爲類似集合,只是具有更復雜的查詢功能。在增加或刪除對應類型的對象時,REPOSITORY的後臺機制負責將對應的對象添加到數據庫中,或者從數據庫刪除。這個定義將一組緊密相關的職責集中在一起,這些職責提供了對AGGREGATE的ROOT的整個生命週期的全程訪問。

REPOSITORY的優點:

  • 提供了一個簡單的模型,可用來獲得持久化對象並管理他們的生命週期
  • 使應用程序和領域設計與持久化技術(多種數據庫策略、多個數據源)解耦
  • 體現了有關對象訪問的設計決策
  • 很容易將他們替換爲啞實現(dummy implementation),以便在測試中使用

基於SPECIFICATION(規格)的查詢是將REPOSITORY通用化的好辦法。客戶可以使用規格來描述它需要什麼,而不用關心如何獲得結果。
也應該允許添加專門的硬編碼查詢。
將存儲、檢索和查詢機制封裝起來是REPOSITORY實現的最基本特徵。
並不意味着每個類都需要一個REPOSITORY。
REPOSITORY通常不提交事務。

從領域驅動設計的角度來看,FACTORY和REPOSITORY具有不同的職責:FACTORY負責製造新對象,REPOSITORY負責查找已有對象。REPOSITORY應該讓客戶感覺那些對象好像駐留在內存中一樣。對象可能必須被重建,但它是同一個概念對象,仍然處於生命週期的中間。
REPOSITORY也可以委託一個FACTORY來創建對象。

一般情況下,模型的精化、設計和實現應該在迭代開發過程中同步進行。