You are on page 1of 33

DDD引導

大綱
• 什麼是DDD?
• 領域分析
• Entity / Value Object
• Repository
• 事件驅動: Domain Driven
• 實務探討
什麼是DDD?
• DDD全名是Domain Driven Development(領域驅動開發),它具有以
下特性:
• 從領域的角度去分析系統
• 緊密結合客戶的領域專家(Domain Expert)和開發團隊
• 是一套完整的開發方法
領域(Domain)
• 軟體系統存在的目的在於解決特定問題
• 在解決問題之前,要先瞭解問題相關的專業知識
• 在現實世界中,專業知識體都可以稱之為 – 領域
• 瞭解相關知識後,模型化(Modeling)知識;繪出領域內部實體之間關聯性的
輪廓
採用DDD會有什麼好處
• 消除客戶/BA/PM/開發人員溝通的隔閡
• 程式碼即文件
• 減緩系統老化的時間
DDD的兩個部份: 戰略 & 戰術
• DDD在戰略上採用:
• 通用語言
• 限界文本(Bounded Context)
• DDD在戰術上採用:
• 實體(Entity)
• 值物件(Value Object)
• 領域服務(Domain Service)
• 倉儲(Repository)
• 領域事件(Domain Event)
• 防腐層(Anti-Disruption)
戰略的意義
• 在未開發的前期,先行分析系統和真正要解決的問題
• 建構系統組成的輪廓,以便後續開發時期的規劃和設計
• 及早瞭解各個子系統(模組)之間的關聯性
戰術的意義
• 讓程式碼的語意更加的豐富,實際落實:程式碼即文件的精神
• 避免發生資料導向(Data Driven)的設計模式
• 建構開發的風格(Style)一致性
領域分析
• 從使用者/領域專家所收集來的需求資訊,進行組織&歸納,最終將
未來要開發的系統概念化成一個個領域
• 分析的工作可能無法在第一次就完成,需要反覆和使用者/領域專
家確認多次後才能定案
• 在開發的過程中,也有可能發覺到一開始所分析的領域需要被刪減
/合併/增加,這些都是極有可能發生的
• 在眾多已被分析出來的領域中挑選其中一個作為核心領域(Core);
該領域是此產品最具競爭力的領域,需要傾全力開發
分析的過程
• 在與使用者/領域專家開會及討論需求的過程中,需要有意識的建
構出 – 通用語言
通用語言
• 專案失敗的原因之一: 溝通
• 通用語言:
• 團隊對於某件事物的定義是一致,並且有共識的
• DDD採用通用語言來消除溝通的隔閡
• 程式碼的命名需符合通用語言,甚至就連物件彼此協作的行為也需
要符合通用語言
準備進入開發
• 在進入開發階段的最後一哩,是對更貼近系統設計面的分析,這個
時候要用到的就是-限界文本
限界文本(Bounded Context)
• 針對領域而規劃的系統模組 or 子系統
• 若說領域是針對現實世界,那麼限界文本就是朝向軟體結構的世界
• 最佳實務: 一個領域一個限界文本
(實務: 一個領域會有多個限界文本,某些限界文本在特殊情況下會
跨多個領域)
• 限界文本之間具有: 上-下游 的關連性
關鍵概念
• DDD是開發方法,本身並沒有對於程式語言的設限,可以使用任意語
言去實現
• DDD本身並不是架構,意即開發人員可以將其套用在任何架構上,諸
如: N-Layer/Tier, Onion … 等
• 限界文本可以彼此獨立地採用各種架構和技術去實做
實體(Entity) / 值物件(Value Object)
• 實體/值物件是DDD戰術中最重要的兩個項目
• 實體:
• 每個物件都有識別碼(ID)
• 系統會追蹤它生命週期內的所有狀態的變化
• 值物件:
• 物件無識別碼(ID),系統以它擁有的屬性值組(Set)來比較兩個值物件是否相

• 當某個屬性值異動時,它就是一個新的值物件;已經與舊的不同
細說實體
• 在現實世界中,某些東西需要被追蹤它的變化,為了要能夠追蹤它
的變化,就需要用識別碼(ID)來快速地區分不同的東西
• 容易與值物件混淆,在茫茫需求之中難以識別何者該為實體?何者
為值物件?
• 資料庫的綱要(Schema)設計,不建議採用實體為資料表的映射;應該
是要使用值物件
• 相同名稱的實體在不同限界文本中是完全不相干且不同含義的
不同限界文本的實體資料同步
• 雖然相同名稱的實體在不同限界文本中,但是其部份屬性的資料是
相同甚至兩個物件的屬性完全相同
• 同步限界文本的兩個具有相同屬性的實體可以採用:
• 防腐層 – 當實體更新後,呼叫另一個限界文本的防腐層進行資料同步
• 領域事件 - 實體更新後發出領域事件,另一個限界文本訂閱此事件,並進行
資料的同步
細說值物件
• 值物件在領域中,它是一種度量衡單位會使用到的物件,它不需要
被追蹤,因此,也就不需要識別碼
• 實例:
住址, 游戲角色屬性值…
• 值物件在賦值給變數方面就如同在程式碼中對一個主要型別的變
數進行賦值一樣:
int a = 10;
這個10就如同一個值物件,當程式碼要賦與a變數另一個值時:
a = 20;
可將值物件看作如同上例的常數值那般,每個值彼此獨立,當內容
改變,就是一個全新的值物件
實體與值物件如何辨識
• 在一段需求描述中,可以從中將重要的關鍵名詞挑出作為候選人,
接著進行下述假設問題:
• 它需要被追蹤嗎?
• 屬性值的不同代表著是另一個不同的東西?
• 這個名詞是一種度量衡的單位?
• 上述假設性問題,如果答案是: false, true , true 那麼它必定為值物

不建議將實體作為參數
• 不使用實體作為參數可以避免領域知識洩漏到外層,進而導致商業
邏輯散佈在各個地方,造成維護的複雜度提升
• 採用值物件作為參數傳遞,是較為推荐的做法
聚合
• 在領域中,會探討到某些場景活動,而這些場景活動中參與者會由
其特性而被摘錄成為實體或是值物件。
• 場景活動映射到DDD中,即為: 聚合
• 聚合是由一個實體(註: 亦名為根實體)所擔任,在這個實體中會有許
多的實體與值物件組合
• 聚合無法直接存取另一個聚合中的實體,需透過這個聚合的根實體
來達成
貧血/富血物件
• 當一個類別只有Get/Set這些屬性而沒有方法,則稱其為貧血物件;
反之,則為富血物件
• DDD傾向採用富血物件,因為使用貧血物件很容易造成維護上的困
難,主要在於無法完整地使用一個物件表達通用語言中所描述的概

• 一個只有屬性的物件,其處理的邏輯和方法勢必是被拆離到另一個
物件身上,如此一來,想要瞭解程式碼背後的知識就需要自行在腦
中拼湊它完整的長相
領域服務(Domain Service)
• 為了能夠充份表達通用語言中所描述的內容,實體上某些方法並不
適合放在該實體身上
• 例: 跨聚合的兩個實體進行協作
• 領域服務的存在不是為了將實體/值物件身上的方法都移轉到它身
上,它是一個迫不得已的存在
• 領域服務建議採用無狀態
• 防腐層中的物件多為領域服務(Proxy)
倉儲(Repository)
• 資料庫存在的目的在於將系統中的物件當前的狀態保存下來,這是
因為應用程式伺服器的記憶體有限,而物件數量無限
• 實體和值物件的狀態會被保存到資料庫中,並且使用倉儲物件將其
還原回到被保存之前的狀態
• 一個實體會由其它實體和值物件所構成,因此其結構十分的複雜,
如果沒有一個物件負責承擔還原實體/值物件的工作,會造成系統
維護的複雜度
論資料庫系統
• 現行資料庫已不在以關聯式資料庫為唯一選擇,其餘還有許多
NoSQL可以選擇
• NoSQL資料可分為四大類:
• Key/Value Pair
• Document
• Column
• Graph
• 每個限界文本可依儲存的模式和成本進行資料庫系統選擇上的考

倉儲物件的歸屬
• 在多層(N-Layer)架構中,倉儲物件歸屬於領域層
• 倉儲物件本身的工作是還原實體/值物件,因此,它身上會有: 如何建
構實體,這些相關的底層知識
• 在採用洋蔥(Onion)架構時,可以只在領域層定義倉儲物件的介面,
實做類別則是放在基礎建設層(Infrastructure)
• 這是因為洋蔥架構希望領域層不要有太多的框架/類別庫汙染
工廠與倉儲
• 與倉儲相對映的就屬工廠了;工廠是常見的設計模式之一
• 當應用層的物件需要建構一個新的實體時,若此實體極為複雜,則
它需要一個工廠將其建構的細節進行封裝
• 反之,若該實體已經存在過系統中,只是當前被序列化到資料庫中,
則需要改用倉儲來將其還原
• 兩者近乎等價,只是使用時機不同
事件驅動: Domain Event
• 領域事件在DDD原提出者的書中並沒有太多的著墨,但其後續以它
所發展出事件驅動的架構風格卻蓬勃發展
• 領域事件所代表的是實體的狀態變化
一個方法的二分法
• 程式語言中的函數可以被二分為:
• 查詢(Query)
• 命令(Command)
• 查詢本身並不會改變問題的答案,因此,這類型的方法都具有冪等

• 命令會更動到實體的屬性,因此,實體會在其命令型的方法執行完
畢之前,發出領域事件
事件驅動
• 事件驅動的設計方式是一種古老的設計手法
• 作業系統就是採用這種架構
• Web系統從前沒有這麼複雜的系統架構需求,但是在現代,它變得極
為複雜,而事件驅動是偏向人類思考和認知世界的模式,有助於對
抗系統設計的複雜性
• 當實體A發出領域事件後,對這個事件感興趣的其它實體會接收並
做一系列相關聯的處理,但是當初發出事件實體A並不需要知道其
它實體是怎麼運作的
• 鬆耦合
CQRS/CQS
• CQRS/CQS是常見的事件驅動架構,該架構對於高流量型系統極為
適合
• CQRS/CQS將使用者的請求區分成:查詢(Query)和命令(Command),
並且將其拆分成兩個不同處理的子系統
• CQRS/CQS在命令方面的請求採用最終一致性的策略,以期能夠負
擔瞬間暴衝的使用者請求
• 命令與查詢分別有各自的資料庫系統,但命令執行完畢之後會同步
兩個資料庫
• 查詢所使用的資料庫,其綱要採用非正規化的使用者查詢導向設計
領域事件與CQRS
• DDD可選用CQRS作為其架構,當決定採用CQRS之後,領域事件的地
位就會大幅上升
• 當命令請求抵達應用層之後,應用層物件會使用倉儲物件來取得實
體/值物件,接著呼叫實體上相應的命令方法,而實體的命令方法在
執行結束之前會發出領域事件
• 該領域事件會由事件處理器(Hanlder)進行處理(例:儲存值到資料庫)
• 事件處理器會再發出另一個領域事件給查詢子系統中可以處理此事件的
處理器
• 查詢子系統的事件處理器會將收到的事件資料進行再處理,爾後儲存到查
詢所使用的資料庫中
領域事件的實務
• 限界文本之間相互獨立,但會彼此會有溝通的需求,除了可以藉防
腐層之外,還可以使用領域事件配合Service Bus來達成
• 領域事件可以讓各個模組和限界文本之間保持鬆耦合
• 為避免領域事件散射的狀況太過發散,建議在設計領域事件之間有
個團隊的設計會議,規劃並避免發散

You might also like