實現領域驅動設計----第一章

帶着問題上路

  • 什麼是領域驅動設計(是什麼)
  • 爲什麼要做領域驅動設計(爲什麼要做)
  • 怎樣做領域驅動設計(怎樣做)
  • 其他的設計模式與領域驅動設計的區別(有類似爲什麼要做,但是是在取長補短的總結)

譯者序

就像在20世紀六七十年代出現了軟件危機之後,面向對象成爲了人們的救贖;瀑布式開發過程遇到瓶頸時,敏捷被搬上了舞臺。

讀到這想到了本科課程中《軟件開發》,第一次接觸到了瀑布模型,而後研究生課程中《現代軟件開發》,知道了管理程序員也不是件易事,要做代碼估算,要做時間估算,要做質量評估。

如何使用本書

DDD總覽
通用語言作用於某個限界上下文,它對於領域建模時非常重要的,不管你是在戰術上還是戰略上設計軟件模型,你都應該保證:在一個特定的限界上下文中只使用一套通用語言,並且保證它的清晰性和簡潔性。

戰略建模
限界上下文是一種概念上的邊界,領域模型便工作於其中。同時,限界上下文爲通用語言提供了一套環境,項目成員便通過通用語言來表達軟件模型。

在戰略設計的過程中,你將發現上下文映射圖是非常有用的。你的團隊將使用上下文映射圖來理解姓名的範圍。

架構
一種能夠支撐限界上下文的架構是六邊形(Hexagonal)架構,它可以輔助其他架構風格,比如面向服務架構、REST和時間驅動(Event-Driven)等。

戰術建模
我們在限界上下文中進行DDD的戰術建模。戰術設計的一個重要模式是聚合。
聚合可以由單個實體組成,也可以由一組實體和值對象組成,此時我們必循在聚合的整個生命週期中保證事務上的一致性。

聚合實列通過資源庫進行持久化,另外,對聚合的查找和獲取也通過資源庫完成。
在這裏插入圖片描述


我們通常忽略了模塊,但是正確地設計模塊同樣是重要的。簡單來講,我們可以將模塊看成是Java中的包或C#中的命名空間。如果只是機械式地設計模塊,而不是根據通用語言,那麼我們將得不償失。模塊中包含的領域對象應該是內聚在一起的。
在這裏插入圖片描述

第一章DDD入門

本章學習路線圖

  • 瞭解DDD可以爲你的項目和團隊帶來哪些好處
  • 如何確定你的項目是否適合採用DDD
  • 瞭解DDD的常見替代方案和它們將導致問題的原因
  • 學習DDD的基礎
  • 學習如何向你的管理層,領域專家和技術成員推銷DDD
  • 瞭解使用DDD時所面臨的挑戰
  • 看看一個正在學習採用DDD的團隊時如何工作的

什麼時領域模型?
領域模型時關於某個特定業務領域的軟件模型。通常,領域模型通過對象模型來實現,這些對象同時包含了數據和行爲,並且表達了準確的業務含義。

爲什麼我們需要DDD

  • 使領域專家和開發者在一起工作,這樣開發出來的軟件能夠準確地傳達業務規則。當然,對於領域專家和開發者來說,這並不表示單單地包容對方,而是將他們組成一個密切協作地團隊。
  • “準確傳達業務規則”地意思是說,此時地軟件就像如果領域專家是人員時所開發出來地一樣。
  • 可以幫助業務人員自我提高。沒有任何一個領域專家或者管理者敢說他對業務已經瞭如指掌了,業務知識也需要一個長期地學習過程。在DDD中,每個人都在學習,同時每個人又是知識地貢獻者。
  • 關鍵在於對知識地集中,因爲這樣可以確保軟件知識並不只是掌握在少數人手中。
  • 在領域專家,開發者和軟件本身之間不存在“翻譯”,意思是當大家都使用相同地語言進行交流時,每人都能聽懂他人所說的。
  • 設計就是代碼,代碼就是設計。設計是關於軟件如何工作的,最好的編碼設計來自於多次試驗,這得益於敏捷的發現過程。
  • DDD同時提供了戰略設計和戰術設計兩種方式。戰略設計幫助我們理解哪些投入是最重要的;哪些既有軟件資產是可以重新拿來使用的;哪些人應該被加到團隊中?戰術設計則幫助我們創建DDD模型中各個部件。



    貧血領域對象是不好的,因爲你花了很大的成本來開發領域對象,但是從中卻收益甚少。比如,由於存在對象-關係阻抗失配(Object-Relational Impedance),開發者需要將很多時間花在對象和數據存儲之間的映射上。這樣的代價太大,而收益太小。這樣的領域對象根本就不是領域對象,而只是將關係型數據庫中的模型映射到了對象上而已。這樣的領域對象更像是活動記錄(Active Record),此時你可以對架構做個簡化,然後使用事務腳本進行開發。

爲什麼會有貧血領域對象

其中一個原因是:貧血領域對象反映了一種自然的過程式編程風格,但我並不認爲這是首要原因。軟件業中有很多開發者都是學着示例代碼做開發的,這並不是什麼壞事,只要示例代碼本身是好的。然而,通常情況是,示例代碼只是用儘可能簡單的方式來展示某個特定的概念或者API特性,而並不強調要遵循多好的設計原則。一些極度簡化的示例代碼總是包含了大量的getter和setter,於是這些getter和setter隨着示例代碼每天被程序員原封不動地來回複製。

我們一般的Dao模式存在的問題

  1. saveCustomer()業務意圖不明確
  2. 方法的實現本身增加了潛在的複雜性
  3. Customer領域對象根本就不是對象,而只是一個數據持有器(data holder)

如何使用DDD

通用語言和限界上下文同時構成了DDD的兩大支柱,並且它們是相輔相成的。

上下文術語
可以將限界上下文看成是整個應用程序之內的一個概念性邊界。這個邊界之內的每種領域術語、詞組或句子----也即通用語言,都有確定的上下文含義。

通用語言
是團隊共享的語言。領域專家和開發者使用相同的通用語言進行交流。事實上,團隊中每個人都使用相同的通用語言。不管你在團隊中的角色如何,只要你是團隊的一員,你都將使用通用語言。

使用了領域對象的行爲,這種行爲表示出了領域中的通用語言:

public class BacklogItem extends Entity{
	private SprintId sprintId;
	private BacklogItemStatusType status;
	...
	public void commitTo(Sprint aSprint){
		if(!this.isScheduledForRekease()){
			throw new IllegalStateException(
				"Must be scheduled for release to commit to sprint.");
				}
		if(this.isCommottedToSprint()){
			if(!aSprint.sprintId().equals(this.sprintId())){
				this.uncommitFromSprint();
			}
		}
		this.elevateStatusWith(BacklogItemStatus.COMMOTTED);
		this.setSprintId(aSprint.sprintId());
		DomainEventPublisher
							.instance()
							.publish(new BacklogItemCommitted(
								this.tenant(),
								this.backlogItemId(),
								this.sprintId()));
	}
	...
}											

此時客戶端的代碼

//客戶端同特定於領域的行爲將BacklogItem提交到Sprint中
backlogItem.commitTo(sprint);

以上的代碼將行爲暴露給客戶,行爲方法的名字清楚地表明瞭業務含義。這個領域的專家在建模時討論了以下需求:

允許將每一個待定項提交到衝刺中。只有在一個待定項位於發佈計劃(Release)中時才能進行提交。如果一個待定項已經提交到了另外一個衝刺中,那麼需要先將其回收。提交完成時,通知相關客戶方。

DDD並不笨重
DDD能夠很好地與敏捷項目框架結合起來,比如Scrum。DDD也傾向“測試先行,逐步改進”地設計思路。在你開發一個新的領域對象時,比如實體或值對象,你可以採用以下步驟進行:

  1. 編寫測試代碼以模擬客戶代碼時如何使用該領域對象地。
  2. 創建該領域對象以使測試代碼能夠編譯通過。
  3. 同時對測試和領域對象進行重構,直到測試代碼能夠正確地模擬客戶代碼,同時領域對象擁有能夠表明業務行爲地方法簽名。
  4. 實現領域對象地行爲,直到測試通過爲止,再對實現代碼進行重構。
  5. 向你地團隊成員展示代碼,包括領域專家,以保證領域對象能夠正確地反映通用語言。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章