DDD三難困境解析

banq發表於2024-08-18

想象一下,在走鋼絲時,你試圖同時玩三個球。這似乎是一個極端且不可能完成的挑戰,但它卻完美地抓住了軟體開發中一個常見問題的本質,即 DDD 三難困境。

DDD(領域驅動設計)三難困境涉及三個經常相互衝突的目標:

  1. 領域模型封裝
  2. 領域模型純度
  3. 效能

開發人員經常發現自己需要平衡這三個相互競爭的方面,力求找到一個不會為了其他方面而犧牲其中一個方面的解決方案。

在軟體開發領域,領域驅動設計是一種強調技術專家和領域專家之間的協作以建立準確反映複雜業務規則的模型的方法。隨著你深入研究這個領域,你會發現建立一個將所有業務邏輯封裝在領域模型中、保持該模型的純度並高效執行的解決方案並非易事。本文探討了 DDD 三難困境的每個元素,透過引人入勝的示例說明了這些挑戰,並提出了克服這些障礙的策略。

在深入研究解決方案之前,讓我們先探討一下 DDD 三難困境的每個組成部分,以瞭解它們為什麼經常相互衝突。
1、領域模型封裝(完整性)
領域模型封裝是指領域模型應包含所有業務邏輯和規則。這一概念強調了隱藏實現細節並僅公開必要內容的重要性。它允許在模型周圍保持清晰的邊界,以便準確反映其所代表的現實世界領域。封裝可確保業務邏輯的更改只需要對模型進行最少的更改,從而實現更好的維護和可擴充套件性。

現實世界的類比
以智慧手機為例。它把複雜的操作和功能封裝在一個簡單的介面後面。使用者不需要知道硬體如何處理觸控輸入或資料如何透過網路傳送。他們只需要知道如何使用應用程式。這一抽象層允許使用者與手機互動,而不會被其內部工作原理所困擾。

2、領域模型純度
領域模型純度原則是,領域模型應不受外部關注(如資料庫互動、使用者介面或其他技術細節)的影響。這可確保模型專注於表示領域及其規則,並儘可能保持其乾淨和抽象。

現實世界的類比
想象一下,一位作家在樹林裡有一間專門的寫作小屋。這個空間沒有電視、網際網路或電話等干擾。小屋讓作家可以不受干擾地專注於創作故事。同樣,純領域模型就像作家的小屋,領域邏輯可以獨立於技術問題而存在。

3、效能
在 DDD 中,效能是指確保應用程式高效執行並滿足使用者對速度和響應能力的需求。效能考慮通常需要最佳化技術,這些技術可能涉及快取、延遲載入或資料庫索引。然而,這些技術有時會侵入領域模型,威脅其純度和封裝性。

現實世界的類比
想象一下,一家以從零開始烹製的美食而自豪的餐廳。雖然每道菜都很美味,而且製作精良,但每道菜的等待時間都超過一小時。雖然質量很好,但效能(服務速度)卻不足。同樣,領域模型可以設計精美,邏輯完整,但如果效能不佳,就不會讓使用者滿意。

應對三難困境
1、平衡封裝和純度
最大的挑戰之一是保持封裝性和純度。雖然封裝所有領域邏輯至關重要,但這樣做時不讓技術問題汙染模型也同樣重要。

策略:反腐敗層
可以使用防腐層 (ACL)作為領域模型和外部系統之間的緩衝,防止外部模型破壞內部領域邏輯。ACL 在兩個模型之間進行轉換,確保領域模型保持純粹,並且僅透過定義良好的介面與外界互動。

實現示例
假設您正在開發一個電子商務應用程式,其中域模型應該保持純淨和封裝,但應用程式需要與舊庫存系統互動。透過實現 ACL,域模型透過介面卡與庫存系統通訊。此介面卡將特定於域的請求轉換為舊系統可以理解的術語,反之亦然,從而保持封裝和純淨。

class  InventoryACL : 
    def  __init__ ( self, legacy_inventory_system ): 
        self.legacy_inventory_system = legacy_inventory_system 
    def  check_availability ( self, product_id ): 
        # 將領域請求轉換為遺留系統請求
        return self.legacy_inventory_system.query_availability(product_id) 
    def  update_stock ( self, product_id, quantility ): 
        # 將領域請求轉換為遺留系統請求
        return self.legacy_inventory_system.update_stock(product_id, quantility) 
# 透過 ACL 互動的領域模型
class  OrderService : 
    def  __init__ ( self, inventory_acl ): 
        self.inventory_acl = inventory_acl 
    def  place_order ( self, product_id, quantility ): 
        if self.inventory_acl.check_availability(product_id) >= quantility: 
            # 下訂單的業務邏輯
            self.inventory_acl.update_stock(product_id, quantility) 
            return  <font>"訂單下達成功" 
        return  “庫存不足”

2、平衡純度和效能
純度通常以犧牲效能為代價。一個完全純粹的領域模型可能會很慢,因為它需要在各層之間進行轉換,並嚴格遵守領域規則。

策略:CQRS(命令查詢職責分離)
CQRS將讀寫操作分離,讓領域模型在保持純粹的同時,還能最佳化效能。透過劃分這些職責,開發人員可以最佳化讀取操作以提高效能,而不會影響領域模型寫入操作的純粹性。

實現示例
在財務應用程式中,您可以使用 CQRS 來處理交易(寫入操作)和生成報告(讀取操作)。這樣,域模型就可以專注於交易邏輯,而讀取操作則可以單獨進行效能最佳化。

# 寫操作的命令模型
class  TransactionService : 
    def  __init__ ( self, transaction_repository ): 
        self.transaction_repository = transaction_repository 
    def  perform_transaction ( self, account_id, amount ): 
        # 交易的業務邏輯
        transaction = Transaction(account_id, amount) 
        self.transaction_repository.save(transaction) 

# 讀操作的查詢模型
class  ReportService : 
    def  __init__ ( self, report_repository ): 
        self.report_repository = report_repository 
    def  generate_monthly_report ( self, account_id ): 
        # 針對效能的最佳化查詢
        return self.report_repository.get_monthly_report(account_id)

封裝也會影響效能,因為緊密封裝的模型可能需要多層轉換和驗證,從而降低操作速度。

策略:領域事件
領域事件允許領域模型釋出更改或重要事件,而無需直接呼叫外部服務。這樣可以將領域邏輯與效能關鍵型操作分離開來,因為偵聽器可以非同步響應這些事件。

實現示例
假設有一個社交媒體應用程式,使用者操作會觸發各種系統操作。透過使用域事件,域模型可以釋出“UserPostedPhoto”之類的事件,並且各種偵聽器可以獨立處理快取、通知和分析。

# 領域模型
class  UserService : 
    def  __init__ ( self, event_dispatcher ): 
        self.event_dispatcher = event_dispatcher 

    def  post_photo ( self, user_id, photo ): 
        # 釋出照片的業務邏輯
        self.event_dispatcher.dispatch( <font>"UserPostedPhoto" , { "user_id" : user_id, "photo" : photo}) 

# 事件監聽器
def  cache_listener ( event_data ): 
    # 快取邏輯
    print ( f
"為使用者{event_data[ 'user_id' ]}快取照片"

def  notification_listener ( event_data ): 
    # 通知邏輯
    print ( f
"為使用者釋出的照片​​傳送通知{event_data[ 'user_id' ]} "

# 事件排程器
class  EventDispatcher : 
    def  __init__ ( self ): 
        self.listeners = {} 

    def  register_listener ( self, event_name, listener ): 
        if event_name not  in self.listeners: 
            self.listeners[event_name] = [] 
        self.listeners[event_name].append(listener) 

    def  dispatch ( self, event_name, event_data ): 
        if event_name in self.listeners: 
            for listener in self.listeners[event_name]: 
                listener(event_data) 

# 設定
dispatcher = EventDispatcher() 
dispatcher.register_listener(
"UserPostedPhoto" , cache_listener) 
dispatcher.register_listener(
"UserPostedPhoto" , notification_listener) 

user_service = UserService(dispatcher) 
user_service.post_photo(
"123" , "beach_photo.jpg" )

實施方案和示例
上面討論的策略為平衡 DDD 三難困境提供了一個框架。以下是一些實際示例和解決方案,可進一步說明這些概念:

變體 1:微服務架構
在微服務架構中,可以透過設計每個服務時重點關注特定領域來解決 DDD 三難問題,從而實現封裝、純度和效能的精細管理。每個微服務都可以擁有自己的領域模型,獨立最佳化效能,同時保持封裝和純度。

示例:電子商務系統
電子商務平臺可以有單獨的微服務來處理訂單、付款和庫存。每項服務都封裝了其域邏輯,透過 API 進行互動以保持純度。可以對每項服務單獨應用效能最佳化。

解釋:

  • 訂單服務、支付服務、庫存服務:分別表示為節點A、B、C。它們透過API相互通訊,保持了微服務架構的原則。
  • 子圖:每個服務都封裝在一個子圖中,以指示其獨立的域邏輯。這在視覺上突出了關注點的分離和每個服務邏輯的封裝。
  • 雙向箭頭:<-->服務之間的箭頭表示透過定義良好的API進行相互通訊和互動,保證純度和封裝性。
  • 註釋:每個服務在其子圖中都有註釋,描述其特定的領域邏輯和職責,反映瞭如何管理 DDD 三難困境。

變體 2:模組化整體式
模組化單體可以透過將應用程式劃分為不同的模組(每個模組負責特定領域)來解決 DDD 三難問題。這些模組保持了封裝性和純粹性,同時利用共享基礎架構來提高效能。

例如:財務應用
在財務應用程式中,可以建立用於帳戶管理、交易和報告的模組。每個模組都封裝了其邏輯,不受外部關注,並共享一個公共資料庫以最佳化效能。

解釋:

  • 賬戶模組、交易模組和報告模組:分別表示為節點 A、B 和 C。它們透過介面與公共資料庫(節點 D)互動並相互互動。
  • 通用資料庫:共享資料庫是節點 D,所有模組都連線在此以最佳化效能。
  • 箭頭和介面:箭頭表示模組之間的互動和介面,確保邏輯的封裝和純粹。

結論
領域模型封裝、領域模型純度和效能的 DDD 三難困境對軟體開發人員來說是一個巨大的挑戰。然而,透過了解每個元件的複雜性並採用防腐層、CQRS、領域事件、微服務和模組化單體等策略,開發人員可以有效地解決這一三難困境。

要在這些相互競爭的目標之間取得平衡,需要深入瞭解領域、應用程式的需求以及每個決策中固有的權衡。透過應用本文討論的策略,開發人員可以建立可靠、可維護且效能卓越的系統,以忠實地代表複雜的領域。
 

相關文章