最常見領域驅動設計錯誤

banq發表於2024-04-23

DDD中的錯誤抽象比其他設計方法具有更大的破壞性影響。這篇文章分享了 DDD 中代價最高的設計錯誤;導致單一和緊密耦合系統盛行的一個常見錯誤。

背景
企業中存在很多臃腫而脆弱的客戶應用程式介面,而針對這種脆弱性提出的解決方案,最終會在客戶應用程式介面不可避免地變得過於繁瑣時,將其拆分成更小的、目的明確的服務。

既然企業是為客戶服務而存在的,那麼將 Customer 作為 API 來處理就顯得過於模糊和懶惰了。事實上,缺乏具體性正是導致這種臃腫和脆弱的原因,這一點您可以從訂單 API、產品 API、賬戶 API 等其他示例中輕易看出。

在大多數情況下,我們要建模的是圍繞這些概念的業務流程,而不是概念本身。這就是我們常犯的錯誤。

將中心概念誤認為是BC限制上下文
中心概念 是某一特定行業的關鍵理念,例如:

  • 銀行業的賬戶。
  • 保險業的保單
  • 供應鏈管理中的產品
  • 航空公司的預訂
  • 電子商務中的訂單
  • 電子商務中的客戶

”限制/限界/有界上下文(BC:Bounded Context)”是應用某些流程和規則的業務領域,這是中心概念在對每個限界上下文有意義的不同規則集下重複出現的地方, 例如:

  • 銀行這個BC中賬戶與貸款發放、賬單、債務催收、營銷和傳播等BC中賬戶不同。
  • 保險BC中的保單看起來與其他BC如承保、索賠和檢查不同。
  • 供應鏈管理這個BC中的產品看起來與計劃、採購、庫存管理和交付等BC不同。
  • 航空公司的預訂看起來與預訂、運營、貨運管理和忠誠度有所不同。
  • 電子商務中的訂單 與採購、供應鏈、履行和客戶支援看起來有所不同
  • 電子商務中的客戶 看起來與廣告、訂購和運輸不同。

這就是問題所在。由於它們的複雜性和出現的頻率,DDD 中的中心概念比我們意識到的更讓我們困惑。它們欺騙我們將它們視為真正的限界上下文。

例如構建一個Account API:

理論視角
首先,對賬戶採用單一視角有違DD 的核心原則,因為這會導致領域模型不靈活、臃腫。

不同的 "限界上下文BC "以獨特的視角看待賬戶,在某些情況下,賬戶甚至可能在整個業務中被稱為其他名稱(泛在語言UL)。

為了說明問題,下面列出了我們示例中的BC有界上下文及其關注的賬戶資訊:

  • 貸款發放側重於背景調查、風險評估以及利率和信用額度等賬戶條款。
  • 計費關注賬戶的及時支付和對賬單生成。
  • 市場營銷和溝通側重於客戶溝通偏好和賬戶付款提醒。
  • 債務催收透過降低利率或提供其他賬戶付款計劃來幫助有困難的客戶。在某些不幸的情況下,他們可能會將賬戶出售給討債公司--那些騷擾客戶並追討其資產的公司。

其次,DDD 是為了解決複雜的業務問題,軟體模型應反映業務流程。管理賬戶偏好是一個業務流程。收賬是一個業務流程。但賬戶不是。它是一個存在於不同業務流程中的概念。

實際角度
將賬戶視為一個BC限制上下文會導致負面的連帶後果。

1、不必要的耦合
首先,賬戶這個BC限制上下文變成了 "上帝領域",承擔了比它應該承擔的更多責任。因此,這就造成了與其他BC有界上下文的緊密耦合。

2、團隊摩擦
這種耦合會對圍繞問題組織團隊產生負面影響。所有團隊都必須與賬戶互動才能做出改變,這就增加了依賴性和摩擦。這會導致以下不良副作用之一:

  • 賬戶團隊超負荷處理來自其他團隊的功能請求,隨著請求數量的增加而導致延遲。
  • 賬戶團隊讓其他團隊為其變更提交拉取請求。這種方法仍然會造成延遲,因為賬戶團隊要審查大量請求。
  • 賬戶團隊的規模隨著工作量的增加而擴大,但團隊成員開始經常出現合併衝突。

遺憾的是,上述所有方法都妨礙了團隊的自主性和軟體釋出的可預測性。因此,組織在同時執行許多想法時會受到限制或限定。

3、單點故障和彈性問題
由於其他BC有界上下文會向賬戶請求資訊(或向賬戶傳播資訊),跨域互動就會變得非常頻繁。這將導致效能和彈性問題。

賬戶作為資訊源,也會成為整個系統的單點故障。例如,如果賬戶建立程式碼中的一個錯誤導致記憶體洩漏,就會中斷支付排程等無關領域的服務。這意味著不僅賬戶建立會受到影響,另一個關鍵業務功能--處理付款--也會受到影響。

避免陷阱
1、分解與封裝
當我對自己的經驗進行評估時,我意識到其中很大一部分都用在了系統的分解和封裝上。

  • 分解是將問題分解成更小的塊。這些小塊通常類似於必須確定範圍的業務規則。這就是封裝的作用所在。
  • 封裝就是定義業務規則的邊界,同時保護這些規則不會洩漏到其他領域。它定義了其他系統如何訪問這些規則並與之互動。

DDD 也不例外。我們現在知道,一個賬戶在不同情況下有多種表徵,因此我們應該評估它是否可以分解成其他BC限制上下文?

下一步是考慮如何封裝業務規則,使其在BC有界上下文中受到範圍限制和保護。換句話說,我們應該考慮如何精確地表示中心概念 "賬戶"。

這就是聚合的作用所在:聚合體是賬戶的封裝單元。它們負責根據業務流程維護資料的完整性。它們可以在自己的 "BC有界上下文"中透過合適的應用程式介面(API)定義訪問規則。

下面是一個例子,說明我們如何在不同的 "BC有界上下文 "中將 "賬戶 "分解為 "聚合":

  • 在債務催收BC中,賬戶的概念被稱為 "催收賬戶"(CollectionAccount),以表示其不同於良好賬戶的地位。如果需要授予一個特定的付款計劃(比如不計利息),就必須透過 CollectionAccount 來進行更改。
  • 在賬單BC下,賬戶付款是中心概念(DDD聚合)。使客戶能夠付款、檢視對賬單和付款歷史記錄是該 "BC有界上下文 "的特定操作示例。

從實現的角度來看,您應該期望債務催收BC中的 CollectionAccount 與賬單BC中的 AccountPayments 有不同的模式和行為集。這完全正常,事實上,DDD 原則也鼓勵這樣做。

結束語
領域驅動設計是對複雜的業務流程和規則進行建模。領域驅動設計中的一個常見錯誤是將中心概念(如金融服務中的 "賬戶"、電子商務中的 "客戶 "和保險中的 "保單")錯誤地建模為BC有界上下文或應用程式介面。要扭轉這種錯誤,尤其是在使用領域驅動設計時,代價相當高昂。

要避免這一陷阱,關鍵在於將設計與建模的業務流程聯絡起來。這些中心概念在領域驅動設計中確實起著至關重要的作用。但它們的作用是作為BC有界上下文整體一致性的維護者和控制者(又名:DDD聚合),而不是作為BC有界上下文字身。

什麼是BC:Bounded Context
"bounded" 在英文中可以表示限制、限定和限界,但它們的語境略有不同。

  • 限制 (restriction): 當我們說某物或某活動有界限或受到限制時,可以使用 "bounded"。
    • "The government has set bounded restrictions on travel."
    • "這項政策對旅行設定了限制。"
  • 限定 (limitation): 有時 "bounded" 可以表示對某事物進行了限定或確定範圍,但這通常側重於界定一種特定的範圍或條件。
    • "The time for completing the project is bounded to six months."
    • "完成專案的時間被限定在六個月內。"
  • 限界 (bound): 這個詞也可以表示一種限制,但更多地側重於某物的邊界或範圍。
    • "The discussion was bounded by time constraints."
    • "討論受到時間限制。"

"限制"、"限定"、"限界"和"受限"這四個詞在某些情況下可以表達類似的意思,但它們在用法和含義上有一些細微的區別。

  • 限制 (xiànzhì): 指對某事物的範圍、數量、活動等做出約束或限定,以防止其超出一定的界限。它通常暗示一種更為嚴格和強制性的約束。比如:
    • "政府制定了交通限制,禁止在市中心區域停車。"
    • "這項法律對公司的活動做出了嚴格的限制。"
  • 限定 (xiàndìng): 通常指明確規定某事物的特定條件或範圍,但並不一定暗示強制性的限制。它可能更偏向於在某種程度上定義範圍或條件。比如:
    • "這份契約對員工的加班進行了限定,每週不超過20小時。"
    • "這個方案限定了專案的時間框架,必須在六個月內完成。"
  • 限界 (xiànjiè): 指的是某事物的邊界或範圍,在這個邊界內活動或表現。它通常用於描述某個領域的界限。比如:
    • "這個概念的限界不僅限於科學領域,還涉及哲學和文化。"
    • "這項計劃的限界是確保在預算內完成專案。"
  • 受限 (shòuxiàn): 指的是受到限制或約束的狀態。它暗示某個實體或行為受到某種程度的限制。比如:
    • "由於資源受限,我們必須精打細算地運用它們。"
    • "這項政策使得公司的發展受到了一定程度的限制。"

總的來說,這些詞在一些情境下可以互換使用,但在特定的語境中,它們可能會有細微的差別,比如表達的強調、涵義的範圍等。

相關文章