Domain-Driven Design 是由 Eric Evans 在 2003 年時《Domain-Driven Design: Tackling Complexity in the Heart of Software》中提出的一種軟體設計方法論,但是我真的不得不說,如果沒有後來那本由 Vaughn Vernon 在 2013 年時出的《Implementing Domain-Driven Design》,我還真的不知道它在說什麼…… 有點需要重實作反過來看那本書才有感覺……
然後接下來這本篇文章就要簡單的談一下 DDD。
然後上面這張用 AI 圖我自已覺得他有抓到 DDD 的精華 :
就我自已的感想,在 Domain-Driven Design 還沒出現時,事實上還沒有一個軟體工程設計方法論是以 domain 為出發點,並且有辦法與業務、產品一起結合在一起,就算是 OOAD 我覺得比較算是工程自已建模的方法,而不是與實際的業務、產品、工程結合在一起的方法,但我自已覺得 DDD 有很多基礎是站在 OOAD 上,所以應該算是升級版。
然後我們先來看一下《Implementing Domain-Driven Design》中提到的為什麼要 DDD 的幾個重點:
每一個都都有點文謅謅,但是每一個又是使用過 DDD 一段時間後才有感覺的東西,然後我自已說說我自已覺得為什麼要用的原因 :
它實際上就帶給了我們這一些的好處,總結以下我自已覺得用了它最好的點在於 :
它可以讓我們軟體工程維護性 + 團隊(產品、工程、營運)合作 Level Up
然後接下來簡單說到成本,我自已覺得比較花時間的在於以下的東西 :
不過認真的說咱們家現在也還只是在試跑 Event Storming 的階段,還沒到全部導入流程中 ~ 這個還有一段路要走啊 ~ 因為現在還不是每個團隊都有人可以判斷要不要舉辦+主持啊 ~
接下來說了那麼多,我們就先來簡單 review 一下 DDD 的那些元素。
備註:
戰略方法不是只有 Event Storming,例如 Domain Storytelling 也是,我只是沒有跑過,所以就沒說太多 ~
DDD 的戰略基本的核心就是 :
透過 event storming ( 或其它 ),來理解、拆解 domain,並且找到 ubiquitous language 與 solution space 的 domain model,最後定義好那個 solution 的範圍 bounded context。
然後接下來比較詳細的說一下每一個。
首先我覺得可以用這句話來表達它們的關係 :
Domain = Problem Space + Solution Space
首先我們先說一下 problem space,它事實上就是咱們 pm 提的那些要解決使用者的 problem 的那些需求,這個名詞嚴格來說不是 DDD 中發明的,再產品的領域好像本來就有,例如這篇文章中所提到的。
史丹佛產品課: 問題領域vs.解題領域 (Problem Space vs. Solution Space)
就我自已的認知,文中所提到的 problem space 與 DDD 中提到的,我覺得是相同的,然後我覺得比較不同的點在於 solution space,以 DDD 這裡提的比較接近是系統服務面的方法來決定 problem space。
然後這裡比較用工程師看的懂的東西來整理這幾個關係比較接近 :
然後一個 problem space 可能會有多個 solution space,例如 :
然後至於實務上 problem space 與 solution space 的數量關係我是都有看過,可以分成如下 :
嚴格來說那種比較好我自已覺得都有優缺,但理想上 1 對 1 的應該是最好維護與管理的,但我也知道實務上很難,因為很多東西都想重用。
上面不是有提到 domain 了,然後它就是 problem space 加 solution space,然後接下來它又將 domain 分成三個,為了可以讓我們判斷那些 domain 需要我們花心力 :
現階段很多公司,很多單一系統所處理的 domain 已經過於龐大了,不先想辦法切分,你就會看到一團大泥球,你完全切不動,然後最慘的就是後端工程師,因為全公司平台上的 domain 都是你們做的,這導致每個部門一有事就來問你們,而如果後端工程師少的情況下,幾乎每個工程師都會變成最理解公司 domain 的人。我待過的公司就是這樣… 慘。
至於如何找出,請等下一篇 event storming。
簡單的說就是一個詞,每個人所理解的意思是相同的,然後之後都用這個詞來溝通與建模
如果沒有它會發生 :
至於如何找出,請等下一篇 event storming。
目的是要知道我們所寫的程式碼 ( 解決方案 ),要處理的範圍情境是什麼
為什麼要談到 domain 一定有討論 bounded context 呢 ?
因為一個名詞在不同地方,意思就換了,那個
地方
就是個Bounded Context
如果沒有它會怎麼樣 ?
那就是包山包海,你想想你們的 user service 之類的,它事實上就是沒有 bounded context 概念。
有的話就會根據 bounded context 切成不同的 service,例如 authUserService 或 userPurchaseCourseService 之類的,會有給它一個範圍。
至於如何找出,這裡會開一篇給 Bounded Context,因為它有點接近在討論軟體工程中最難的部份 :
切分
主要的目的就是理解咱們 bounded context 的關係。
這個東西也可以幫助我們理解我們整個服務的關係圖,而且我自已也覺得它可以幫助我們理解整個系統的全貌。
戰略解釋了,接下來就是戰術方面,就是比較接近程式碼面的東西,但這裡我就先只簡單說 3 個比較核心的東西。
事實上它就是我們之前說的 domain model,然後在這本書中它比較詳細的定義特點如下 :
Day-14: 提升維護性與降低複雜度的好方法之 Domain Model
它就是一堆 entity + value object 的組合,基本上它有以下的特點;
下面為簡單的範例,就是 order 與 suborder 的情境,然後由於 order 是 aggrgate root,所以 suborder 退款一定只能從 order 執行。
// Entity
class SubOrder {
constructor(
public readonly id: SubOrderId,
public readonly price: Money,
private status: SubOrderStatus = SubOrderStatus.PAID
) {}
refund() {
if (this.status !== SubOrderStatus.PAID) {
throw new Error("SubOrder is not in a refundable state.");
}
this.status = SubOrderStatus.REFUNDED;
}
}
// Aggregate Root
class Order {
private subOrders: Map<string, SubOrder> = new Map();
private totalRefunded: Money;
constructor(public readonly orderId: string, private totalPrice: Money) {
this.totalRefunded = Money.zero(totalPrice.currency);
}
refundSubOrder(subOrderId: SubOrderId) {
const subOrder = this.subOrders.get(subOrderId.id);
if (!subOrder) {
throw new Error("SubOrder not found.");
}
if (subOrder.isRefunded()) {
throw new Error("SubOrder has already been refunded.");
}
subOrder.refund();
this.totalRefunded = this.totalRefunded.add(subOrder.getPrice());
}
}
是業務領域中重要的事件,用於
通知
其他系統某些業務狀態的變更。工程上最重要的目的就是解耦
概念上就是下面這張圖,然後可以想成每一個 aggregate 的公開方法都會產生一個 domain event,這個之後的文章會比較詳細的說明。
本文簡單的討論了 Domain-Driven Design 的基本核心概念,其中核心概念又可以分為 :
我自已覺得最難的部份應該是戰略上,因為需要和不少人協作,並且這東西也要導入整個 sprint 流程中都是需要溝通與心力,但是如果少了這個,我也不敢保證說我的軟體很接近業務,很好維護。
而至於戰術層面上我自已覺得比戰略算簡單多了,因為事實上裡面很多核心的概念不是在 DDD 時就發明的,例如 aggregate,這東西很早在就有 domain model 這個東東,之後會說的 repository 也是,但是這個還是會有不少坑的,這個之後的文章會說到 ~ 今天這篇只是 DDD 的簡略。