- 英文原名:Domain-Driven Design: Tackling Complexity in the Heart of Software
- 作者:Eric Evans
- 譯者:趙俐 盛海艷 劉霞 等
- 審校:任發(fā)科
第一章 消化知識
有效建模的要素
- 模型和實現(xiàn)的綁定
- 建立一種基于模型的語言
- 開發(fā)一個蘊含豐富知識的模型
- 提煉模型
- 頭腦風暴和實驗
第二章 交流與語言的使用
務必要記住模型不是圖。圖的目的是幫助表達和解釋模型。
不能強制用圖來表示全部模型或設計,因為這樣會削弱圖的清晰表達的能力。
過猶不及。圖和表都有很強的表現(xiàn)力,但是如果試圖將所有信息都塞進圖表中,只會導致觀看者陷入茫然。在使用圖表時一定要記住這一點。
第三章 綁定模型和實現(xiàn)
如果模型不能直接幫助開發(fā)可運行的軟件,那么這種紙上談兵的模型又有什么意義呢?
模型是關聯(lián)領域和開發(fā)的紐帶,所以不能偏于一方。模型需要充分體現(xiàn)和說明領域自身的性質(zhì)和領域對象間的關聯(lián),但同時也需要是能夠用于實際開發(fā)的。
??模式:模型驅動設計 Model-Driven Design (MDD)
建模范式和工具支持
在本小節(jié)中,作者比較了Java、Prolog、C、Fortran等一系列編程語言,核心角度是建模范式。Java有一套完整的面向對象的建模范式(可能更常用的一個詞是設計模式),Prolog可以使用基于邏輯的建模范式,而Fortran在實現(xiàn)數(shù)學函數(shù)方面有它的優(yōu)勢。類似C這樣的純粹過程語言,沒有一套適用的建模范式,因此一般無法用于模型驅動設計。
??模式:親身實踐的建模者 Hands-On Modeler
將分析、建模、設計和編程工作過度分離會對MDD產(chǎn)生不良影響。
過度分工帶來弊病。軟件設計和開發(fā)中,角色分工也有“合久必分分久必合”的態(tài)勢。在前后端分離成為常態(tài)的今天,全棧開發(fā)又成為了眾多開發(fā)者追求的目標。測試驅動開發(fā)(TDD)讓開發(fā)者承擔起一部分測試職能,而DevOps的盛行,則讓開發(fā)和運維更加緊密地結合在了一起。眾多工具的誕生,也使得一個開發(fā)者獨力承擔設計、開發(fā)、測試和運維等工作變?yōu)榭赡堋?/p>
具體到MDD中來說,如果建模者自己完全脫離了軟件開發(fā)實踐,自然難以了解實際開發(fā)中會遇到的種種問題和困難,也就難以設計出適應于開發(fā)人員需要的模型。這就好像說技術管理人員、架構師等不能完全停止編程,否則勢必與實際開發(fā)脫節(jié)。
第四章 分離領域
??模式:分層結構 Layered Architecture
本書中將軟件架構分為四層,自上至下為:用戶界面層、應用層、領域層、基礎設施層。越靠上的層級與終端用戶越為接近。<br />靠上的層級可以通過調(diào)用下層元素的公共接口,直接使用或操作下層元素;但如果下層元素需要與上層元素通信,則需要依賴回調(diào)/觀察者等模式。
?為什么要抽出單獨的領域層?
如果與領域有關的代碼分散在大量的其他代碼之中,那么查看和分析領域代碼就會變得異常困難。對用戶界面的簡單修改實際上很可能會改變業(yè)務邏輯,而想要調(diào)整業(yè)務規(guī)則也很可能需要對用戶界面代碼、數(shù)據(jù)庫操作代碼或其他的程序元素進行仔細的篩查。這樣就不太可能實現(xiàn)一致的、模型驅動的對象了,同時也會給自動化測試帶來困難。
?如何創(chuàng)建能夠處理復雜任務的程序?
- 關注點分離。
- 分離的同時,也需要維持系統(tǒng)內(nèi)部復雜的交互關系。
?實現(xiàn)MDD的關鍵是?
領域層的分離。
?基礎設施層如何提供功能?
- 服務(Service)。例如:發(fā)送電子郵件、發(fā)送短信由基礎設施層實現(xiàn),應用層中的元素只需要直接請求發(fā)送消息,而不需要關心消息具體是如何發(fā)送的。
- 架構框架。例如:為領域對象提供抽象基類,以及關聯(lián)機制(比如MVC等框架)。
?如何最優(yōu)化使用框架?
不妄求萬全之策,而是有選擇性地運用框架來解決難點問題,以避開框架的很多不足之處。明智而審慎地選擇框架中最具價值的功能能夠減少程序實現(xiàn)和框架之間的耦合,使隨后的設計決策更加靈活。
也就是說,并不是用了框架,就要所有功能都使用框架提供的實現(xiàn)方式,或者按照框架的標準去實現(xiàn)。完全可以將其他的實現(xiàn)方式(在當前場景中更加合適或更加簡單的)與框架相結合
??模式:The Smart UI
這是與DDD相反的一種設計模式,基本理念是把所有的業(yè)務邏輯、業(yè)務規(guī)則都在用戶界面中實現(xiàn)。這種設計模式對于小型項目、能力不足的開發(fā)人員和開發(fā)團隊來說,尤其有效。<br />
<br />?為什么作者要在這里講Smart UI?<br />軟件開發(fā)過程中,設計方法、基礎設施等的選擇,一定要與項目的需求相匹配。
項目團隊常犯的錯誤是采用了一種復雜的設計方法,卻無法保證項目從頭到尾始終使用它。另一種常見的也是代價高昂的錯誤則是為項目構建一種復雜的基礎設施以及使用工業(yè)級的工具,而這樣的項目根本不需要它們。
第五章 軟件中所表示的模型
表示模型的三種模型元素模式
- Entity
- Value Object
- Service
模型中每個可遍歷的關聯(lián),軟件中都要有同樣屬性的機制。
?現(xiàn)實中的關聯(lián)往往非常復雜,如何對這些關聯(lián)進行有效的控制?
- 規(guī)定一個遍歷方向
- 添加限定符,以減少多重關聯(lián)
- 消除不必要的關聯(lián)
實例:
- 通過限定國家->人的單向關系,并加上時期作為限定符,可以有效控制國家->總統(tǒng)這一關聯(lián)。
- 用股票名稱作為投資的限定符,可以有效控制賬戶->投資這一關聯(lián)。
??模式:實體Entity(又稱Reference Object)
很多對象不是通過它們的屬性定義的,而是通過連續(xù)性和標識定義的。
最典型的例子:人
- 一個人5歲和50歲的長相、身高、體重都有極大差別,但是同一個人
- 兩個人的名字、出生日期、出生地可能都一樣,但是他們是兩個不同的人
用最簡單通俗的話來說,實體對象需要一個標識,或者說ID。標識必須是唯一的,不會沖突的。
有時,某些數(shù)據(jù)屬性或屬性組合可以確保它們在系統(tǒng)中具有唯一性,或者在這些屬性上加一些簡單約束就可以使其具有唯一性。
比如說日報、雜志等,一般來說就可以靠名稱、日期等唯一確定。這種情況下,就可以不需要再額外增加一個標識,或者ID。
??模式:值對象Value Object
很多對象沒有概念上的標識,它們描述了一個事物的某種特征。
一個錯誤的做法是給所有對象都加上標識。
跟蹤Entity的標識是非常重要的,但為其他對象也加上標識會影響系統(tǒng)性能并增加分析工作,而且會使模型變得混亂,因為所有對象看起來都是相同的。
值對象可以復制,也可以共享。Flyweight(享元)這一設計模式的核心就是值對象的共享。
?什么情況更適合用共享?
- 節(jié)省數(shù)據(jù)庫空間或者減少對象數(shù)量是一個關鍵要求
- 通信開銷很低
- 共享對象被嚴格限定為不可變
?什么情況下需要允許共享對象變更?
- Value頻繁改變
- 創(chuàng)建或刪除對象的開銷很大
- 替換有可能會打亂集群
- Value的共享不多,或者共享對集群性能沒有提升
??模式:服務Service
有時,對象不是一個事物。
?好的Service有什么特征?
- 與領域概念相關的操作不是Entity或Value Object的一個自然組成部分。
- 接口是根據(jù)領域模型的其他元素定義的。
- 操作是無狀態(tài)的。
?不同層級的Service如何劃分?
- 基礎設施層的Service不應該具有任何業(yè)務意義。比如發(fā)送郵件、發(fā)送短信等。
- 領域層的Service執(zhí)行一些細粒度的領域對象操作,避免把領域知識泄漏到應用層中。領域層Service可以幫助保持應用層和領域層之間的界限。比如,檢查一個轉賬請求是否合法。
- 應用層的Service執(zhí)行具體的業(yè)務。比如,執(zhí)行一個轉賬請求。
??模式:模塊Module(又稱Package)
高內(nèi)聚低耦合在Module的設計中尤為重要。
在分析一個Module的內(nèi)容時,應當很少需要參考那些與之交互的其他Module。
建模范式
?在面向對象為主的系統(tǒng)中混入非對象元素,需要注意哪些方面?
- 不要和實現(xiàn)范式對抗。
- 把通用語言作為依靠的基礎。
- 不要一味依賴UML。
- 保持懷疑態(tài)度。
第六章 領域對象的生命周期
??模式:聚合Aggregate
聚合實際上是構建邊界的過程。經(jīng)過聚合后,只有根Entity才具有全局標識,能夠被外部對象直接訪問。而聚合內(nèi)部的Entity只具有本地標識(可能全局不唯一),必須經(jīng)由根Entity進行訪問。
??模式:工廠Factory
如果創(chuàng)建對象或Aggregate的工作很復雜,或者暴露了過多的內(nèi)部結構,則可以使用Factory進行封裝。
這里討論的Factory,包含了OO設計模式中的Factory Method、Abstract Factory、Builder等多種設計模式。
?什么樣的Factory是好的Factory?
- 創(chuàng)建必須是原子的,并且必須保證所創(chuàng)建對象或Aggregate的所有固定規(guī)則。
- 生成的Entity在創(chuàng)建完成后可以添加可選元素
- 生成的Immutable Value Object所有屬性都初始化為正確的最終狀態(tài)
- 如果出錯,應該拋出異常,而不能創(chuàng)建一個錯誤的對象
- Factory應該被抽象為所需的類型,而不是所要創(chuàng)建的具體類。
??模式:倉庫Repository
我們可以通過對象之間的關聯(lián)來找到對象。但當它處于生命周期的中間時,必須要有一個起點,以便從這個起點遍歷到一個Entity或Value。
從數(shù)據(jù)庫中檢索一個已經(jīng)存在的對象,是對象的重建過程。
大多數(shù)對象都不應該通過全局搜索來訪問。
在設計之初,就需要確定哪些對象允許通過全局搜索進行訪問。
相關的技術包括:
- 將SQL封裝到Query Object中
- 使用Metadata Mapping Layer實現(xiàn)對象和表之間的轉換
這些技術的問題在于,它們并沒有考慮領域模型中的概念,而是對數(shù)據(jù)庫操作的封裝。
Repository可以用來封裝這些解決方案,并將我們的注意力重新拉回到模型上。
Repository相當于它所對應的那個類型的所有對象的集合的一個替身/代理。
?使用Repository的好處?
- 提供了一個獲取持久化對象和管理對象生命周期的簡單模型
- 使應用程序、領域設計、持久化技術解耦
- 體現(xiàn)了有關對象訪問的設計決策
- 可以很容易地被替換為“啞實現(xiàn)”(dummy implementation),以便調(diào)試和測試之用
Repository中的查詢分為兩種:硬編碼的和基于Specification的。原則上,應該同時提供這兩類查詢。對于常用的查詢,比如根據(jù)ID查找用戶,應該提供對應的硬編碼查詢接口;而同時,需要提供基于Specification的,具有一定靈活度的接口,以便用戶實現(xiàn)一些更復雜的查詢功能(條件查詢、多表關聯(lián)……)。
客戶代碼可以忽略Repository的實現(xiàn),但開發(fā)人員不能。
對開發(fā)人員來說,必須考慮Repository底層的實現(xiàn)細節(jié),考慮所使用的數(shù)據(jù)庫等的特性。
?使用Repository的注意點?
- 對類型進行抽象
- 充分利用與客戶解耦的優(yōu)點
- 將事務的控制權交給客戶
第七章 使用語言:一個擴展的示例
以一個貨物運輸?shù)膽脼榘咐?,重點體現(xiàn)了前述的分層結構、實體、值對象、模塊、聚合、工廠、倉庫等模式的應用。
第八章 突破
持續(xù)重構讓事物逐步變得有序。
重構的原則是始終小步前進,始終保持系統(tǒng)正常運轉。
但如果更改模型的益處遠遠超過了工期延長的代價,那就做吧!Just do it!
迭代的不僅僅是產(chǎn)品功能,還有領域模型。隨著開發(fā)的不斷深入,以及與領域專家的深入溝通,可以對領域有更加深入的認識,從而不斷優(yōu)化模型的設計。
第九章 將隱式概念轉變?yōu)轱@式概念
??模式:規(guī)格Specification
這一模式借鑒了邏輯編程范式中的謂詞概念(可分離、可組合的規(guī)則對象)。<br />Specification實際創(chuàng)建的是一種特殊的Value Object。
?Specification的用途?
- 驗證對象是否滿足某些需求
- 從集合中選擇一個或一些對象
- 指定在創(chuàng)建新對象時需要滿足某些需求
實際上,Specification最大的好處就是它把這三件事統(tǒng)一到了一起。
可以通過關鍵組件的模型驅動原型來緩解合作開發(fā)的僵局。
第十章 柔性設計
柔性設計是對深層建模的補充。
??模式:釋義命名接口Intention-Revealing Interfaces
如果開發(fā)人員為了使用一個組件而必須要去研究它的實現(xiàn),那么就失去了封裝的價值。
命名類和操作時要描述它們的效果和目的,而不要表露它們是通過何種方式達到目的的。這樣可以使客戶開發(fā)人員不必去理解內(nèi)部細節(jié)。
??模式:無副作用函數(shù)Side-Effect-Free Function
只有實現(xiàn)了無副作用的函數(shù),才能安全地對多個操作進行組合,而不必深入研究其實現(xiàn)細節(jié)。
??模式:斷言Assertion
借助斷言,可以把操作的后置條件和類及Aggregate的固定規(guī)則表述清楚。
??模式:概念輪廓Conceptual Coutour
把設計元素分解為內(nèi)聚的單元,在連續(xù)的重構過程中觀察變與不變的規(guī)律,尋找能夠解釋這些變化模式的底層規(guī)律,就可以得到概念輪廓。
??模式:獨立類Standalone Class
互相依賴使得模型和設計難以理解、測試和維護。
盡力把最復雜的計算提取到獨立的類中。
??模式:閉合操作Closure of Operation
讓操作的返回類型與其參數(shù)類型相同。
函數(shù)式編程中的map、filter都是閉合操作。
聲明式設計
?如何實現(xiàn)聲明式設計?
- 反射
- 代碼生成
?聲明式設計的局限性?
- 聲明式語言表達能力的局限
- 自動生成代碼與手寫代碼并存,破壞了迭代循環(huán)
第十一章 應用分析模式
分析模式專注于一些最關鍵和最艱難的決策,并闡明了各種替代和選擇方案。它們提前預測了一些后期結果,而如果單靠我們自己去發(fā)現(xiàn)這些結果,可能會付出高昂的代價。
第十二章 將設計模式應用于模型
??模式:策略Strategy
把過程中的易變部分提取到模型的一個單獨的“策略”對象中。
??模式:組合Composite
第十三章 通過重構得到更深層的理解
有三件事是必須要關注的:
- 以鄰域為本
- 用一種不同的方式來看待事物
- 始終堅持與領域專家對話
第十四章 保持模型的完整性
模型最基本的要求是應該保持內(nèi)部一致:
- 術語總具有相同的意義
- 不包含互相矛盾的規(guī)則
如果兩個團隊使用了不同的模型,而并沒有意識到這一點,他們的代碼被組合到一起時,往往就會發(fā)生問題。
大型系統(tǒng)領域模型的完全統(tǒng)一既不可行,也不劃算。
需要預先決定什么應該統(tǒng)一,什么不能統(tǒng)一。
??模式:限界上下文Bounded Context
限界上下文定義了每個模型的應用范圍。
Bounded Context不是Module。它們是具有不同動機的不同模式。Bounded Context往往借助Module來實現(xiàn),但有時人們也會在一個Context內(nèi)部劃分出多個Module(這實際上可能導致模型分裂)。
??模式:持續(xù)集成Continuous Integration
持續(xù)集成可以有兩個級別的操作:
- 模型概念的集成
- 實現(xiàn)的集成
??模式:上下文圖Context Map
描述模型之間的聯(lián)系點,明確所有通信需要的轉換,并突出任何共享的內(nèi)容。
對各個Bounded Context的聯(lián)系點的測試特別重要。
——最好由負責不同Bounded Context的團隊合作開發(fā)。
??模式:共享內(nèi)核Shared Kernel
共享內(nèi)核實際上就是領域層的基礎設施共享。通過把領域模型中不同團隊都統(tǒng)一共享的子集提取出來作為一個獨立的模塊,可以以較小的代價獲得較大的收益。
??模式:客戶/供應商Customer/Supplier
下游團隊相當于上游團隊的客戶。
要實現(xiàn)好的客戶/供應商模式,對兩個團隊領導者之間的配合有較高的要求。
??模式:跟隨者Conformist
如果上游設計的質(zhì)量不是很差,而且風格也能兼容的話,那么最好不要再開發(fā)一個獨立的模型。
如果不愿意兼容框架的風格和模型,那么為什么要選擇使用這個框架呢?
與C/S模式相比,跟隨者模式適用于上游團隊沒有意愿,或者客觀上不可能適應下游團隊的需求的情形。
??模式:隔離層Anticorruption Layer
?如果要與一個規(guī)模龐大的遺留系統(tǒng)進行集成(這時根本不存在一個“上游團隊”,無法使用C/S模式;遺留系統(tǒng)往往存在大量問題,也難以使用跟隨者模式),要怎么做?
創(chuàng)建一個隔離層,以便根據(jù)客戶自己的領域模型來為客戶提供相關功能。
一種可行的組織方式是結合Facade、Adapter和轉換器。
- Facade屬于另一個系統(tǒng)的Bounded Context。它幫助我們簡化對所需特性的訪問,并隱藏其他特性。
- Adapter用于包裝對象,使其符合另一個系統(tǒng)的要求。
- 轉換器服務于所屬的Adapter,實際進行對象或數(shù)據(jù)的轉換。
??模式:各行其道Separate Way
集成未必是必要的。
??模式:開放主機服務Open Host Service
定義一個協(xié)議,把你的子系統(tǒng)作為一組Service供其他系統(tǒng)訪問。
??模式:定義語言Published Language
定義一個專用的領域語言,來實現(xiàn)不同模型之間的數(shù)據(jù)交換。
如何決定Bounded Context的大?。?/h2>
選擇較大的Bounded Context:
- 用戶任務之間的流動更加順暢
- 一個內(nèi)聚模型比起兩個模型+映射要更容易理解
- 兩個模型間的轉換可能很困難
- 共享語言,溝通更清楚
選擇較大的Bounded Context:
- 用戶任務之間的流動更加順暢
- 一個內(nèi)聚模型比起兩個模型+映射要更容易理解
- 兩個模型間的轉換可能很困難
- 共享語言,溝通更清楚
選擇較小的Bounded Context:
- 減少溝通開銷(因為團隊規(guī)模小了)
- 便于持續(xù)集成
- 對模型抽象層級(進而對人員建模技巧)的要求低
- 可以滿足一些特殊需求
Context的迭代
- Separate Way -> Shared Kernel
- Shared Kernel -> Continuous Integration
- Anticorruption Layer -> 不再需要
- Open Host Service -> Published Language
第十五章 精煉
精煉是把一堆混雜在一起的組件分開的過程。
??模式:核心領域Core Domain
??模式:通用子領域Generic Subdomain
識別出那些與項目意圖無關的內(nèi)聚子領域。把這些子領域的通用模型提取出來,并放到單獨的Module中。任何專有的東西都不應放在這些模塊中。
?如何進行Generic Subdomain的開發(fā)?
- 購買解決方案
- 使用開源方案
- 外包
- 自研
對于通用子領域而言,尋求外部的現(xiàn)成方案,往往是一個更好的選擇。因為在這上面投入過多的精力,會分散對于核心領域的注意力,同時消耗過多的資源。
??模式:領域愿景說明Domain Vision Statement
DVS面向的是開發(fā)者,而不是軟件的使用者。務必注意這一點。
??模式:突出核心Highlighted Core
編寫一個非常簡短的文檔(3~7頁),用于描述Core Domain以及Core元素之間的主要交互過程。
??模式:內(nèi)聚機制Cohesive Mechanism
把概念上的內(nèi)聚機制分離到一個獨立的輕量級框架中,用一個釋義命名接口來暴露框架的功能。領域中的其他元素就可以只關心如何表達問題,而不需要關心具體實現(xiàn)。
?Cohesive Mechanism與Generic Subdomain的區(qū)別?
- Generic Subdomain描述的是領域的一個方面,只是相對沒那么重要
- Cohesive Mechanism并不表示領域,而是用來解決描述性模型所提出來的一些復雜的計算問題
??模式:分離核心Segregated Core
??模式:抽象核心Abstract Core
第十六章 大型結構
??模式:演變結構Evolving Order
??模式:系統(tǒng)隱喻System Metaphor
??模式:職責分層Responsibility Layer
- 嚴格分層:上層只能訪問緊鄰的下層
- 松散分層:上層可以訪問所有下層
??模式:知識級別Knowledge Level
??模式:可插拔組件框架Pluggable Component Framework
第十七章 領域驅動設計的綜合運用
制定戰(zhàn)略設計決策的6個要點
- 決策必須傳達到整個團隊
- 決策過程必須收集反饋意見
- 計劃必須允許演變
- 架構團隊不必把所有最好、最聰明的人員都吸收進來
- 戰(zhàn)略設計需要遵守簡約和謙遜的原則
- 對象的職責要專一,而開發(fā)人員應該是多面手