Domain Logic Patterns#

本章介紹四種組織領域邏輯的模式:Transaction ScriptDomain ModelTable ModuleService Layer。它們代表了從簡單程序式到豐富物件導向的不同設計策略,適用於不同複雜度的業務需求。

Transaction Script#

以程序(procedure)組織業務邏輯,每個程序處理來自展示層的單一請求。

意圖#

Transaction Script 將每一筆業務交易的所有邏輯組織為單一程序,直接呼叫資料庫或透過薄的資料庫包裝層操作。每筆交易都有自己的 Transaction Script,共用的子任務可以抽取為子程序。

運作方式#

  • 領域邏輯主要以交易為單位組織,例如「訂房」的邏輯(檢查空房、計算費率、更新資料庫)全部放在同一個程序中
  • 可以將多個 Transaction Script 放在同一個類別中,每個類別定義一個相關主題區域;也可以每個 Transaction Script 獨立一個類別,使用 Command 模式
  • Transaction Script 不應包含任何展示邏輯的呼叫,以便於修改與測試

Figure 9.1: Using commands for Transaction Script

書中以 Revenue Recognition(收入認列) 為範例:一個公司販售三種產品(文字處理器、試算表、資料庫),每種產品的收入認列規則不同。Transaction Script 直接用 if/else 判斷產品類型來決定如何分配收入。

何時使用#

  • 適合:業務邏輯簡單的應用程式,只需少量邏輯且效能開銷小
  • 不適合:當業務邏輯變得複雜,Transaction Script 之間容易出現重複程式碼,且難以維持良好的設計狀態
  • 可以從 Transaction Script 重構為 Domain Model,但這是一個較大的改動;因此若預期邏輯會變複雜,早期採用 Domain Model 是更好的選擇
  • 即使是物件導向愛好者,也不應排除 Transaction Script——許多簡單問題用簡單方案能更快完成

Domain Model#

同時包含行為與資料的物件模型。

意圖#

Domain Model 建立一個由相互連接的物件組成的網絡,每個物件代表某個有意義的個體(可大如一間公司,可小如訂單上的一行)。物件同時包含資料與行為,將流程緊密聚集在其操作的資料附近。

運作方式#

  • 在應用程式中插入一整層模擬業務領域的物件,包括模仿業務資料的物件與捕捉業務規則的物件
  • Domain Model 會和資料庫模型相似,但仍有顯著差異:Domain Model 混合了資料與流程,擁有多值屬性、複雜的關聯網絡,並使用繼承
  • 存在兩種風格:
    • 簡單 Domain Model:每個領域物件大致對應一張資料庫表,可使用 Active Record
    • 豐富 Domain Model:使用繼承、策略模式等 GoF 設計模式,與資料庫設計可能截然不同,需要 Data Mapper
  • 應盡量減少 Domain Model 與系統其他層的依賴,保持低耦合
  • 常見的擔憂是領域物件「膨脹」,但作者建議:先把行為放在自然適合的物件中,等真正膨脹時再處理

Figure 9.3: Class diagram for a Domain Model

書中範例使用 Strategy 模式 處理不同產品的收入認列規則。Contract 委託給 Product,Product 再委託給 RecognitionStrategy 的子類別(CompleteRecognitionStrategy、ThreeWayRecognitionStrategy)。整個計算過程中沒有任何條件判斷——決策在建立產品時就已設定。

何時使用#

  • 適合:業務規則涉及複雜的驗證、計算和推導,且規則經常變動
  • 不適合:只需簡單的 null 檢查和幾筆加總時,Transaction Script 更合適
  • 團隊是否熟悉物件導向設計是重要因素;一旦習慣就很難回到 Transaction Script
  • 使用 Domain Model 時,首選 Data Mapper 處理資料庫互動,以保持 Domain Model 獨立於資料庫
  • 可搭配 Service Layer 提供更清晰的 API

Table Module#

以單一實例處理資料庫表或視圖中所有資料列的業務邏輯。

意圖#

Table Module 為每張資料庫表提供一個類別,該類別的單一實例包含所有作用於該表資料的程序。與 Domain Model 的主要差別在於:如果有多筆訂單,Domain Model 會有多個訂單物件(每筆一個),而 Table Module 只有一個物件處理所有訂單。

運作方式#

  • Table Module 的關鍵差異是它對操作的物件沒有身份識別(identity) 的概念;若要取得特定員工的地址,需傳入某種 ID(如 employeeModule.getAddress(long employeeID)
  • 通常搭配以表為導向的資料結構使用,表格資料是 SQL 查詢的結果,存放在 Record Set
  • Table Module 在 Record Set 上提供明確的方法介面,將行為與資料封裝在一起
  • 多個 Table Module 經常需要協作,共同操作同一個 Record Set

Figure 9.4: Several Table Modules collaborate with a single Record Set

  • Table Module 可以是實例或靜態方法的集合;使用實例可以用現有的 Record Set 初始化,也支援繼承
  • 查詢可以包含在 Table Module 中,也可以透過額外的 Table Data Gateway 處理
  • 典型互動流程:Table Data Gateway 組裝 Record Set → 建立 Table Module → Table Module 處理業務邏輯 → 修改後的 Record Set 傳回展示層 → 展示層使用表格感知元件顯示 → 修改後回傳 Table Module 驗證 → 儲存

書中以 C# 範例展示 Table Module。每個 Table Module 類別繼承自共同的基底類別,持有一個 DataTable 參考。Contract 類別的 CalculateRecognitions 方法根據產品類型分配收入認列記錄,直接操作 DataSet 中的資料。

Figure 9.6: Database schema for revenue recognition

何時使用#

  • 適合:使用以表為導向的資料存取方式(如 Record Set),且資料結構是系統核心,特別在 Microsoft COM/.NET 環境
  • 不適合:需要完整的物件導向能力(如物件間的實例關係、多型)時,Domain Model 更佳
  • 若 Domain Model 中的物件與資料庫表相對簡單且相似,Table Module 可能優於 Domain Model + Active Record 的組合
  • 在 Java 環境中不常見 Table Module,因為 Row Set 不像 .NET 的 DataSet 那樣普及

Service Layer#

以一層服務定義應用程式的邊界,建立可用操作的集合,並協調每個操作中應用程式的回應。

意圖#

Service Layer 從客戶端層的角度定義應用程式的邊界及其可用操作集。它封裝應用程式的業務邏輯,控制交易並協調操作中的回應。

運作方式#

  • 業務邏輯可分為兩種:
    • 領域邏輯(domain logic):純粹與問題領域相關(如計算收入認列)
    • 應用邏輯(application logic):與應用程式職責相關(如通知合約管理員、發布訊息到中介軟體),有時稱為「工作流邏輯」
  • 兩種實作變體:
    • Domain Facade 方式:Service Layer 是一組薄的 facade,不包含業務邏輯,所有邏輯由 Domain Model 實作
    • Operation Script 方式:Service Layer 是一組較厚的類別,直接實作應用邏輯,並委託給領域物件處理領域邏輯
  • 建議從本地呼叫的 Service Layer 開始,需要時再加上 Remote Facade 或實作遠端介面
  • 識別操作的起點是使用案例模型和 UI 設計;大多數企業應用的使用案例是 CRUD 操作,與 Service Layer 操作幾乎一對一對應

Figure 9.8: RecognitionService EJB class diagram

Service Layer 將應用邏輯從領域物件中分離出來,使純領域物件類別在不同應用程式間更具可重用性,也使應用邏輯層更容易替換(例如改用工作流引擎)。

何時使用#

  • 適合:應用程式有多種客戶端(UI、資料載入器、整合閘道),且使用案例涉及跨多個交易資源的複雜回應需要原子性協調
  • 不適合:只有一種客戶端且使用案例回應不涉及多個交易資源時,Page Controller 可以直接控制交易並委託給資料源層
  • 一旦預見第二種客戶端或第二個交易資源,從一開始就設計 Service Layer 是值得的