iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
Software Development

一個好的系統之好維護基本篇 ( 馬克版 )系列 第 19

Day-19: Domain-Driven Design 提升團隊合作與軟體維護性的關鍵 ( 概略 )

  • 分享至 

  • xImage
  •  

同步至 medium

https://ithelp.ithome.com.tw/upload/images/20241003/20089358Td32AaoqVu.png


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-Driven Design 還沒出現時,事實上還沒有一個軟體工程設計方法論是以 domain 為出發點,並且有辦法與業務、產品一起結合在一起,就算是 OOAD 我覺得比較算是工程自已建模的方法,而不是與實際的業務、產品、工程結合在一起的方法,但我自已覺得 DDD 有很多基礎是站在 OOAD 上,所以應該算是升級版。

然後我們先來看一下《Implementing Domain-Driven Design》中提到的為什麼要 DDD 的幾個重點:

  1. 讓領域專家與開發者一起工作,這樣開發出來的程式碼會更接近業務端,然後對於團隊來說可以更有效的進行合作。
  2. 程式碼更接近領域與業務端,這也代表維護性會更高。
  3. 可以幫助業務人員自我提高,沒有一個人說對業務已經了如指掌了,在整個 DDD 過程中,每個人都在學習,但同時每個人也是知識貢獻者。
  4. 關鍵在于對知識的集中。
  5. 開發者、程式碼、領域專家、PM 不在需要翻譯,因為大家都用相同的語言交流。
  6. 設計就是程式碼,程式碼就是設計。
  7. DDD 戰略可以讓我們知道那些是最重要的,戰術幫我們創建 DDD 模型中各部件。

每一個都都有點文謅謅,但是每一個又是使用過 DDD 一段時間後才有感覺的東西,然後我自已說說我自已覺得為什麼要用的原因 :

  1. 它的戰略方法之一 Event Storming 讓我們的產品、業務、工程這裡團結在一起釐清流程、問題、與重要的東西 ( 這個在 Event Storming 時,會談談我的發現 )。
  2. 我們根據 Event Storming 的結果產生出基本戰術模型,這使我們程式碼更接近業務面,有助於我們更好的進行維護。
  3. 它可以讓我們更知道 Domain 的全貌。
  4. 它給了我們一個大系統的解耦與切分方向 ( 但實際上我們還沒切 )
  5. 它讓我們更好的應對業務複雜性。

它實際上就帶給了我們這一些的好處,總結以下我自已覺得用了它最好的點在於 :

它可以讓我們軟體工程維護性 + 團隊(產品、工程、營運)合作 Level Up

然後接下來簡單說到成本,我自已覺得比較花時間的在於以下的東西 :

  • 團隊成員的知識建立: 很多時後我覺得要導入一個東西的難點有一點就是在於人,要如何讓人接受呢 ? 某些方面我可以理解那些人的想法,就是一個新技術(事實上不算新了)之類的,我不用也是可以把程式碼寫出來。這個的話我自已是用讀書會與小地方改變開始 (例如先從 domain model ),慢慢讓他們知道好處。
  • 舉辦 Event Stomring: 但這個通常我比較容易碰到的問題在於,團隊一定要有一個人可以主持與可以判斷那些東西要舉辦,並且那個也有辦法導致到團隊流程中,不然很容易打水漂。
  • 整理 Event Stomring: 整理結束後的產出,並且能文件與程式碼化。

不過認真的說咱們家現在也還只是在試跑 Event Storming 的階段,還沒到全部導入流程中 ~ 這個還有一段路要走啊 ~ 因為現在還不是每個團隊都有人可以判斷要不要舉辦+主持啊 ~

接下來說了那麼多,我們就先來簡單 review 一下 DDD 的那些元素。

備註:
戰略方法不是只有 Event Storming,例如 Domain Storytelling 也是,我只是沒有跑過,所以就沒說太多 ~

戰略的基本元素

DDD 的戰略基本的核心就是 :

透過 event storming ( 或其它 ),來理解、拆解 domain,並且找到 ubiquitous language 與 solution space 的 domain model,最後定義好那個 solution 的範圍 bounded context。

https://ithelp.ithome.com.tw/upload/images/20241003/20089358VqzoSxzWhv.jpg

然後接下來比較詳細的說一下每一個。

1. Domain、Problem Space、Solution Space

首先我覺得可以用這句話來表達它們的關係 :

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: 就是 PM 們提的想解決的問題。
  • Solution Space: 就是我們的系統 ( 備註: 但也不代表一個 solution 就是一個微服務,比較準確來說是 bounded context )。

然後一個 problem space 可能會有多個 solution space,例如 :

  • problem space: 使用者想要購買課程,並且可以看課。
  • solution space: puchase service 可以解決購買,然後 video service 可以解決看課。

然後至於實務上 problem space 與 solution space 的數量關係我是都有看過,可以分成如下 :

  • 一個 Problem Space 對多個 Solution Space: 就是這個 problem space 很複雜,例如我們整個課程產品就可能會跨了金流、看課品、分潤等 solution space。
  • 一個 Problem Space 對一個 Solution Space: 這個算是比較簡單的,例如 auth service 就專門用來處理 auth 相關的 problem space。
  • 多個 Problem Space 對一個 Solution Space: 代表這個 service 可以解決很多問題,但反過來想就是它是不是個包山包海的 solution space ?

嚴格來說那種比較好我自已覺得都有優缺,但理想上 1 對 1 的應該是最好維護與管理的,但我也知道實務上很難,因為很多東西都想重用。

2. Core Domain、Supporting SubDomain、Generic SubDomain

上面不是有提到 domain 了,然後它就是 problem space 加 solution space,然後接下來它又將 domain 分成三個,為了可以讓我們判斷那些 domain 需要我們花心力 :

  • Core Domain : 最有價值、最核心、花費最大力氣在開發。
  • Supporting SubDomain : 提供 core domain 所需的功能。
  • Generic SubDomain : 所有系統都需要用到他,但不是核心,也就是說丟給外包也可以的部份。

現階段很多公司,很多單一系統所處理的 domain 已經過於龐大了,不先想辦法切分,你就會看到一團大泥球,你完全切不動,然後最慘的就是後端工程師,因為全公司平台上的 domain 都是你們做的,這導致每個部門一有事就來問你們,而如果後端工程師少的情況下,幾乎每個工程師都會變成最理解公司 domain 的人。我待過的公司就是這樣… 慘。

至於如何找出,請等下一篇 event storming。

3. Ubiquitous Language

簡單的說就是一個詞,每個人所理解的意思是相同的,然後之後都用這個詞來溝通與建模

如果沒有它會發生 :

  • 缺少統一語言,導致人們對其他人的語言,可能會理解錯誤,又或是需要花時間翻譯,這樣會導致在建立 domain 時可能出錯。
  • 缺少統一語言時,新成員會很難理解組員在說什麼,因為每個人都用自已的字來說明某個東西的意思。

至於如何找出,請等下一篇 event storming。

4. Bounded Context

目的是要知道我們所寫的程式碼 ( 解決方案 ),要處理的範圍情境是什麼

為什麼要談到 domain 一定有討論 bounded context 呢 ?

因為一個名詞在不同地方,意思就換了,那個地方就是個Bounded Context

如果沒有它會怎麼樣 ?

那就是包山包海,你想想你們的 user service 之類的,它事實上就是沒有 bounded context 概念。

有的話就會根據 bounded context 切成不同的 service,例如 authUserService 或 userPurchaseCourseService 之類的,會有給它一個範圍。

至於如何找出,這裡會開一篇給 Bounded Context,因為它有點接近在討論軟體工程中最難的部份 :

切分

5. Context Map

主要的目的就是理解咱們 bounded context 的關係。

這個東西也可以幫助我們理解我們整個服務的關係圖,而且我自已也覺得它可以幫助我們理解整個系統的全貌。

https://ithelp.ithome.com.tw/upload/images/20241003/20089358AqcRQUzrlf.png

戰術的基本元素

戰略解釋了,接下來就是戰術方面,就是比較接近程式碼面的東西,但這裡我就先只簡單說 3 個比較核心的東西。

  • entity
  • aggregate
  • domain event

Entity

事實上它就是我們之前說的 domain model,然後在這本書中它比較詳細的定義特點如下 :

  • 唯一標識: 就是有 id,它是獨一獨二的。
  • 具有生命周期: 可以經歷不用的狀態或階段,但準確的來說不是叫你有個叫 status 的欄位,而是說這裡面的欄位變化是在那個生命周期的。

Day-14: 提升維護性與降低複雜度的好方法之 Domain Model

Aggregate

它就是一堆 entity + value object 的組合,基本上它有以下的特點;

  • 包含這裡面的 entity 變化,一定只能透過 aggregate root 來進行。
  • 它就是我們 transaction 的最小單位。

下面為簡單的範例,就是 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());
  }
}

Domain Event

是業務領域中重要的事件,用於通知其他系統某些業務狀態的變更。工程上最重要的目的就是解耦

概念上就是下面這張圖,然後可以想成每一個 aggregate 的公開方法都會產生一個 domain event,這個之後的文章會比較詳細的說明。

https://ithelp.ithome.com.tw/upload/images/20241003/20089358IhOjj7knpp.jpg

小結

本文簡單的討論了 Domain-Driven Design 的基本核心概念,其中核心概念又可以分為 :

  • 戰略 ( Strategic Design ): 重於整體架構,目的在解決更高層次的業務問題。
  • 戰術 ( Tactical Design ): 重於將上面戰略得到的結果,實踐至軟體架構中。

我自已覺得最難的部份應該是戰略上,因為需要和不少人協作,並且這東西也要導入整個 sprint 流程中都是需要溝通與心力,但是如果少了這個,我也不敢保證說我的軟體很接近業務,很好維護。

而至於戰術層面上我自已覺得比戰略算簡單多了,因為事實上裡面很多核心的概念不是在 DDD 時就發明的,例如 aggregate,這東西很早在就有 domain model 這個東東,之後會說的 repository 也是,但是這個還是會有不少坑的,這個之後的文章會說到 ~ 今天這篇只是 DDD 的簡略。


上一篇
Day-18: Typescript 編譯器守護者
下一篇
Day-20: Event Storming 經驗談
系列文
一個好的系統之好維護基本篇 ( 馬克版 )30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言