一、何為領域驅動設計
在啟動一個軟件項目時,我們應該關注軟件涉及的領域。軟件的最終目的是增進一個特定的領域。
- 如何理解你的領域
最佳的方式是讓軟件成為領域的一個映射。軟件需要包含領域里重要的核心概念和元素,并精確實現他們之間的關系。也就是說,軟件需要對領域進行建模。 - 構建領域知識
舉個例子:飛機飛行控制系統-
每一個飛行器有一個出發機場和目的機場
img -
飛機飛行計劃
img -
路線是有一些小的區間組成,可以看做一些方位點
img -
實際上飛行員會把這些點看做地球上的映射
img -
駕駛員離開前會接到詳細的飛行計劃,飛行計劃包括:飛行路線、巡航高度、巡航速度和飛機的類型等等
img -
我們并不對飛行器的類型顏色等感興趣,而是對“飛行”感興趣,“飛行”才是系統的核心概念
img
-
二、通用語言
-
對公共語言的需要
在設計過程中,我們傾向于使用自己的方言,但是沒有一種方言能成為一種公共的語言,因為他們都不能滿足所有的需要。
通用語言:使用模型作為語言的主干。要求團隊在進行所有的交流時都使用一致的語言,在代碼中也是這樣。在共享知識和推敲模型時,團隊會使用演講、文字和圖形。
-
創建通用語言
我們可以使用文檔來表現模型。每一張小圖包含模型的一個子集。這些圖會包含若干個類以及他們之間的關系。然后向圖中添加文本,來表現圖不能表現的行為和約束。
文檔與模型必須保持同步。陳舊的文檔使用了錯誤的語言,或不能如實的反饋模型都是沒什么用的。
另外兩種可用的通用語言:- UML 圖:在原素較少時比較有幫助,系統較大時 UML 會難以閱讀
- 代碼:代碼能夠完成其功能,但不一定能表達清楚其所作的事情
三、模型驅動設計
我們應該如何完成從模型到代碼的轉換?
一個推薦的設計技術是創建分析模型,它被認為是與代碼設計相互分離的、通常是
由不同的人完成的。
一種更好的方法是將領域建模和設計緊密關聯起來。模型在構建時就考慮到軟件實
現和設計。
-
模型驅動設計的基本構成要素
img -
分層架構
img
領域驅動設計的一個通用的架構解決方案包含了4 個概念層:
概念層 | 內容 **用戶界面/展現層** | 負責向用戶展現信息以及解釋用戶命令 **應用層** | 很薄的一層,用來協調應用的活動。它不包含業務邏輯。它也不保留業務對象的狀態,但它保留有應用任務的進度狀態 **領域層** | 本層包含關于領域的信息。這是業務軟件的核心所在。在這里保留業務對象的狀態。對業務對象和它們狀態的持久化被委托給了基礎設施層 **基礎設施層** | 本層作為其他層的支撐庫存在。它提供了層間的通信,實現對業務對象的持久化,包含對用戶界面層的支持庫等作用
實體
有一類對象看上去好像擁有標識符,它的標識符在歷經軟件的各種狀態變更后仍能保持一致。對這些對象而言,重要的不是其屬性,而是其延續性和標識,對象的延續性和標識會跨越甚至能夠超出軟件系統的生命周期。我們把這樣的對象稱為實體。
當一個對象可以用其標識符而不是它的屬性來區分時,可以將標識符作為在模型中該對象定義的主要部分。使類的定義保持簡單并專注于生命周期的延續性和標識符。
實體是領域模型中非常重要的對象,并且它們應該在建模過程開始時就被考慮。-
值對象
有的時候我們需要包含一個領域對象的某些屬性。我們對它是哪一個對象并不感興趣,而是只關心它所擁有的屬性。用來描述領域的特定方面、并且沒有標識符的一個對象,叫做值對象。
如果值對象是可共享的,那么它們應該是不可變的。值對象應該保持很小、很簡單。
比如:
img
一個客戶會跟其姓名、街道、城市、州相關。最好用一個單獨的對象來包含這些地址信息,客戶對象會包含一個對這個對象的引用。街道、城市、州應該歸屬于一個對象,因為它們在概念上屬于一體的,而不應該作為客戶對象分離的屬性。 -
服務
服務的3 個特征:- 服務執行的操作代表了一個領域概念,這個領域概念無法自然地隸屬于一個實體或者值對象。
- 被執行的操作涉及到領域中的其他的對象。
- 操作是無狀態的。
當使用服務時,保持領域層的隔離非常重要。很容易弄混屬于領域層的服務和屬于基礎設施層的服務。當我們在設計階段建立模型時,我們需要確保將領域層與其他的層隔離開來。
-
模塊
軟件代碼應該具有高層次的內聚性和低層次的耦合度。雖然內聚開始于類和方法級別,它也可以應用于模塊級別。推薦的做法是將高關聯度的類分組到一個模塊,以提供盡可能大的內聚性。最常用到的兩個內聚是通信性內聚(communicational cohesion)和功能性內聚(functional cohesion)。
-
聚合
聚合是針對數據變化可以考慮成一個單元的一組關聯的對象。聚合使用邊界將內部和外部的對象劃分開來。每個聚合都有一個根。這個根是一個實體,并且它是外部可以訪問的唯一的對象。
聚合的一個簡單的例子如下圖所示。客戶(Customer)是聚合的根,并且其他所有的對象都是內部的。如果需要地址(Address),一個它的副本將被傳遞給外部對象。
img -
工廠
為復雜對象和聚合創建實例的職責,應該轉交給一個單獨的對象。雖然這個對象本身在領域模型中沒有職責,但它仍是領域設計的一部分。提供一個接口來封裝所有復雜的組裝過程,客戶不需要引用正在初始化的對象所對應的具體類。將整個聚合當作一個單元來創建,強化它們的不變量。
有時工廠是不需要的,一個簡單的構造器就足夠了。在如下情況下應該使用構器:- 構造過程并不復雜。
- 一個對象的創建不涉及到其他對象的創建,可以將所有需要的屬性傳遞給構造器。
- 客戶對實現很感興趣,可能希望選擇使用策略(Strategy)模式。
- 類是特定的類型,不存在到層級,所以不用在一系列的具體實現中進行選擇。
-
資源庫
資源庫扮演了一個全局可訪問對象的存儲地點
使用一個資源庫,它的目的是封裝所有獲取對象引用所需的邏輯。領域對象不需處理基礎設施,以得到領域中對其他對象的引用。只需要從資源庫中獲取它們,于是模型重獲它應有的清晰和專注。
需要注意的是,資源庫的實現可能會非常像是基礎設施,然而資源庫的接口卻是純粹的領域模型。
img
四、面向深層理解的重構
-
持續重構
重構是不改變應用的行為而重新設計代碼使得它更好的過程。
重構通常是非常謹慎的,按照小幅且可控的步驟進行。 凸現關鍵概念
約束是一個很簡單的表達不變量的方式。無論對象的數據如何變化,不變量都要得到保持。簡單的實現方式是將不變量的邏輯放在一個約束中。
處理過程(process)通常在代碼中被表達為procedure。從我們開始使用面向對象語言后我們就不再用一種過程化的方法,所以我們需要為處理過程選擇一個對象,然后給它添加行為。最好的實現過程的方式是使用服務。
規約是用來測試一個對象是否滿足特定條件的。規則應該被封裝到其自身的一個對象中,這將成為客戶的規約,并且被保留在領域層中。
五、保持模型的一致性
當存在多個團隊,有不同的管理和協作時,我們會面對一系列不同的挑戰。
img
- 界定上下文
主要的思想是定義模型的范圍,定出它的上下文的邊界,然后盡最大可能保持模型的統一。
明確定義模型所應用的上下文,根據以下因素來明確設置邊界:團隊的組織結構、應用的特定部分中的慣例、物理表現(例如代碼庫、數據庫Schema)。 - 持續集成
我們需要這樣一個集成的過程,以確保所有新增的元素和模型原有部分能夠和諧相處,在代碼中也被正確地實現。 - 上下文映射
上下文映射(Context Map)是描繪不同的界定上下文和它們之間關系的一份文檔。它可以是像下面所展示的一個圖表(diagram),也可以是其他任何形式的文檔,細節層次可以有所不同。重要的是,要讓每個在項目中工作的人都能夠分享并理解它。 - 共享內核
img
需要指派兩個團隊同意共享的領域模型子集。這個明確被共享的東西有特殊的狀態,在沒有咨詢另一個團隊之前不能做修改。
共享內核的目的是減少重復,但是仍保持兩個獨立的上下文。 - 客戶-供應商
有的時候兩個子系統之間存在特殊的關系:一個子系統嚴重依賴另一個。兩個子系統所在的上下文是不同的,并且一個系統的處理結果被作為另外一個的輸入。它們沒有共享的內核,因為有這樣一個內核從概念上說是錯誤的,或者兩個子系統要共享代碼在技術上不可能實現。
這個時候,應在兩個團隊之間建立一個清晰的客戶/供應商關系。在制定計劃的過程中,讓客戶團隊扮演和供應商團隊打交道的客戶角色。對滿足客戶需求的任務進行協商并做出預算,讓每個人都理解相關的承諾和日程表。 - 順從者
客戶非常依賴于供應商,然而供應商卻不依賴客戶,這時客戶-供應商關系就不可行了。最明顯的做法是將它與供應商分離開,完全自力更生。 -
防崩潰層
img
防崩潰層也許包含多個服務。每一個服務都有一個相應的Facade,對每一個Facade我們為之添加一個適配器。我們不應該為所有的服務使用一個適配器,因為這樣會將很多功能混在一起,從而導致雜亂無章。
我們必須再添加一個組件。適配器將外部系統的行為包裝起來。我們還需要對象和數據轉換(object and data conversion),可以使用一個轉換器(translator)來完成這個任務。它可以是一個非常簡單的對象,有很少的功能,滿足數據轉換的基本需要。如果外部系統有一個復雜的接口,最好在適配器和接口之間再添加一個額外的Facade。這會簡化適配器的協議,將它和其他系統分離開來。 - 隔離通道
隔離通道模式適合于以下情況:一個企業應用可由幾個較小的應用組成,而且從建模的角度來看彼此之間有很少或者沒有公共之處。 - 開放主機
定義一個能以一組服務的形式訪問你的子系統的協議。將這個協議開放出來,使得所有需要和你做集成的人都能使用它。 - 提煉
即使在我們改進和創建很多抽象之后,一個大的領域還是會有一個大的模型。就是在做了很多次重構之后,模型依然會很大。對于這樣的情況,就需要做一次提煉了。其思路是定義一個代表領域本質的核心域(Core Domain)。提煉過程的副產品將是包含了領域中其他部分的普通子域(Generic Subdomain)。
初讀《領域驅動設計》,回想做過的項目,感覺之前的設計還有很多不足之處在里面。希望讀完可以邊回顧邊實踐,再在文中加入一些自己的體會和感悟~~