《實現領域驅動設計》系列(1) DDD入門

NO IMAGE

最近在學習Vaughn Vernon所著的《實現領域驅動設計》,發現這本身對我們如何設計一個產品及產品的實現過程有一定的指導作用。

1.DDD概述

領域驅動設計(DDD)作為一種軟體開發方法,它可以幫助我們設計高質量的軟體模型(軟體開發模型是指軟體開發全部過程、活動和任務的結構框架。軟體開發包括需求、設計、編碼和測試等階段,有時也包括維護。軟體開發模型能清晰、直觀地表達軟體開發全過程,明確規定了要完成的主要活動和任務,用來作為軟體專案的基礎)。領域模型的設計結果達到更專業的團隊合作,更高效的開發過程,更貼近業務需求的實現的效果。

2.為什麼需要DDD

真正的業務價值是能夠很好地符合業務戰略,並且可以將競爭優勢融合到解決方案中。但在軟體開發中我們常會遇到以下問題:

(1)業務人員所想對映到開發者所理解,不能完全反映出領域專家的思維模型。

(2)領域專家間意見不統一。

(3)軟體技術實現錯誤地改變軟體業務的規則。

注:領域專家並不是一個職位,他可以是精通業務的任何人。他們可能瞭解更多的關於業務領域的背景知識,他們可能是軟體產品的設計者,甚至有可能是銷售員。

DDD針對以上三個問題,提出了3個解決方案:

(1)建立通用語言。通用語言指產品組每位夥伴都可以理解的技術術語、業務術語等。

(2)服務於業務戰略方向。DDD的戰略設計用於清楚地界分不同的系統和業務關注點(即功能),這樣可以保護每個業務層面的服務。

(3)使用戰術設計建模工具,這些戰術設計工具使開發人員能夠按照領域專家的思維模型開發軟體。

使用DDD的原則:在最重要最複雜的業務場景下使用,對於那些可以輕易替換或業務流程並不複雜的系統來說,不建議使用。

3.簡單Deom:貧血領域物件

軟體業中有很多開發者都是學著示例程式碼做開發的,這並不是什麼壞事,只要示例程式碼本身是好的。然而,通常情況下是,示例程式碼只是用儘可能簡單的方式來展示某個特定的概念或者API特性,而並不強調要遵循好的設計原則。一些極度簡化的示例程式碼總是包含了大量的getter和setter,於是這些getter和setter隨著示例程式碼每天被程式設計師們原封不動地來回複製,當然還有一些歷史原因,如JavaBean的標準,這就不贅言了。如果你的領域物件中主要是些公有的getter和setter方法,並且幾乎沒有業務邏輯,或者即使有業務邏輯,也多數情況下需要呼叫那些getter和setter,那麼這個物件就被稱為貧血領域物件。

比如有這麼一個業務場景:我們現在開發使用的是一個Scrum模型,我們需要將一個待定項(Backlog Item)提交到衝刺(sprint)中,有以下程式碼:

public class BcaklogItem1 extends Entity{
private SprintId sprintId;
private BcaklogItemStatusType status;
...
public void setSprintId(SprintId sprintId) {
this.sprintId = sprintId;
}
public void setStatus(BcaklogItemStatusType status) {
this.status = status;
}
...
}

客戶端程式碼如下:

	bcaklogItem.setSprintId(sprintId);
bcaklogItem.setStatus(BcaklogItemStatusType.COMMITTED);

以上例子採用的是以資料為中心的方式,此時客戶程式碼必須知道如何正確地將一個待定項提交到衝刺中。這樣的模型是不能被稱為領域模型的。如何客戶程式碼錯誤的修改了sprintId,而沒有修改status會發生什麼呢?或者,如果在將來有另外一個屬性需要設值時又該怎麼辦?我們需要認真分析客戶程式碼來完成從客戶資料到BacklogItem屬性的對映。這種方式同時也暴露了BacklogItem的資料結構,並且將關注點集中在資料屬性上,而不是物件行為,因為這裡的getter和setter並沒有真正的業務價值。

4.貧血領域物件的解決思路

使用領域物件的行為,這種行為表達除了領域中的通用語言,將以上例子程式碼修改如下:

public class BcaklogItem2 extends Entity{	
private SprintId sprintId;
private BcaklogItemStatusType status;
...
public void commitTo(Sprint aSprint){
//檢測該待定項是否已在釋出計劃中
if(!this.isScheduledForRelease()){
throw new IllegalStateException("Must be scheduled for release to commit to sprint.");
}
//檢測該待定項是否已在另外的衝刺中,如果在則回收
if(this.isCommittedToSprint()){
if(!aSprint.sprintId().equals(this.sprintId())){
this.uncommitFromSprint();
}
}
this.elevateStatusWith(BcaklogItemStatusType.COMMITTED);
this.setSprintId(aSprint.sprintId());
//對外發布事件
DomainEventPublisher.instance().publish(new BacklogItemCommitted(
this.tenant,this.backlogItemId(),this.sprintId()));
...
}
}	

客戶端程式碼如下:

bcaklogItem.commitTo(sprint);

該例子中,客戶程式碼並不需要知道提交BacklogItem的實現細節,實現程式碼所表達的邏輯恰能描述業務行為。我們很容易地新增幾句程式碼,就可確保在釋出計劃之外的待定項是不能被提交的。

以上是DDD入門的一些簡介、術語和Demo,粗略的介紹了DDD的概念和基本應用,接下來的文章會詳細的介紹DDD個方面,今天到此為止啦。