Data Source Architectural Patterns#

本章介紹四種資料源架構模式:Table Data GatewayRow Data GatewayActive RecordData Mapper。它們提供不同層次的抽象來處理應用程式與資料庫之間的互動,從簡單的 SQL 封裝到完全解耦的映射層。

Table Data Gateway#

作為 Gateway 存取資料庫表的物件,一個實例處理該表中所有資料列。

意圖#

Table Data Gateway 將存取單一表或視圖的所有 SQL(select、insert、update、delete)集中在一個物件中。其他程式碼透過呼叫其方法與資料庫互動。

運作方式#

  • 具有簡單的介面,通常包含多個 find 方法、update、insert 和 delete 方法
  • 每個方法將輸入參數映射為 SQL 呼叫,並對資料庫連線執行
  • Table Data Gateway 通常是無狀態的,僅負責在程式碼與資料庫之間推送資料
  • 回傳查詢結果的方式是一個棘手問題:
    • 可回傳 Map 等簡單資料結構(但缺乏型別檢查)
    • 可回傳 Data Transfer Object(額外的物件但可在其他地方重用)
    • 可回傳 Record Set(概念上較亂但與 Table Module 搭配極佳)
  • 大多數情況下每張表一個 Gateway;非常簡單的情況可用單一 Gateway 處理所有表
  • 也可以為視圖或有趣的查詢建立 Gateway
  • 使用 Domain Model 時,可讓 Table Data Gateway 回傳適當的領域物件,但這會造成雙向依賴

Figure 10.1: Class diagram of data-set-oriented gateway

書中以 C# 範例展示 PersonGateway,包含 FindAllFindWithLastNameFindWhere 等查詢方法,以及 UpdateInsertDelete 等修改方法。另有 ADO.NET DataSet 版本,展示了使用 DataSetHolder 和 DataAdapter 的更進階形式。

何時使用#

  • 適合:與 Table Module 搭配特別好,產生 Record Set 資料結構供 Table Module 操作;也非常適合 Transaction Script
  • 不適合:搭配 Domain Model 時較少使用,因為 Data Mapper 能更好地隔離 Domain Model 與資料庫
  • Row Data Gateway 的選擇主要取決於如何處理多列資料:若結果集表示方式適合,偏好 Table Data Gateway
  • 封裝資料庫存取的好處之一是同一介面可同時用於 SQL 語句和預存程序

Row Data Gateway#

作為 Gateway 存取資料源中單一記錄的物件,每個資料列一個實例。

意圖#

Row Data Gateway 提供看起來完全像資料庫記錄結構的物件,但可透過程式語言的一般機制存取,所有資料源存取的細節都隱藏在介面之後。

運作方式#

  • 每個 Row Data Gateway 精確模擬資料庫中的單一記錄,每個欄位對應一個欄位,並處理資料型態轉換
  • Gateway 持有資料列的資料,並提供 insert 和 update 方法寫回資料庫
  • 尋找操作(find)通常放在獨立的 Finder 類別中,而非 Gateway 本身,因為靜態 find 方法會阻礙多型,且每張表通常有一個 Finder 類別和一個 Gateway 類別
  • Finder 回傳的 Gateway 物件會在 Registry 中使用 Identity Map 管理

Figure 10.2: Interactions for a find with Row Data Gateway

Row Data Gateway 與 Active Record 的關鍵區別在於:Row Data Gateway 只包含資料庫存取邏輯,不包含領域邏輯。若物件中有領域邏輯,那就是 Active Record。

何時使用#

  • 適合:搭配 Transaction Script 使用,將資料庫存取程式碼整潔地從業務邏輯中分離出來,便於不同 Transaction Script 重用
  • 不適合:搭配 Domain Model 時——若映射簡單,Active Record 能以更少的程式碼完成同樣工作;若映射複雜,Data Mapper 更擅長解耦
  • 當使用 Transaction Script 搭配 Row Data Gateway 時,可能會發現重複的業務邏輯自然適合放在 Gateway 中,此時邏輯會逐漸演變為 Active Record
  • Row Data Gateway 很適合用 Metadata Mapping 自動產生程式碼
  • 也可以搭配 Data Mapper 使用,特別是當 Row Data Gateway 由 metadata 自動生成而 Data Mapper 是手寫時效果很好

Active Record#

包裝資料庫表或視圖中一列資料的物件,封裝資料庫存取並在該資料上加入領域邏輯。

意圖#

Active Record 是一個同時攜帶資料與行為的物件。其大部分資料是持久化的,需要儲存到資料庫中。Active Record 採用最直接的方式——將資料存取邏輯放在領域物件中,使所有人都知道如何讀寫資料庫。

運作方式#

  • 本質上是一個與資料庫底層記錄結構緊密匹配的 Domain Model,每個 Active Record 負責自身的存取與載入
  • Active Record 的資料結構應精確匹配資料庫:每張表的每個欄位對應類別中的一個欄位
  • 典型的 Active Record 類別具備以下功能:
    • 從 SQL 結果集建構實例
    • 建構新實例以供後續插入
    • 靜態 finder 方法封裝常用 SQL 查詢並回傳 Active Record 物件
    • 更新資料庫並插入資料
    • 取得和設定欄位
    • 實作部分業務邏輯
  • getter/setter 方法可以做型態轉換(SQL 型態轉為記憶體中更適合的型態)
  • 這個模式不隱藏關聯式資料庫的存在,因此使用 Active Record 時較少看到其他物件關聯映射模式
  • 靜態 find 方法也可以分離到獨立類別中以利測試

書中以 Java 的 Person 類別為例,展示了 Active Record 如何在同一個類別中結合資料欄位(lastName、firstName、numberOfDependents)、資料庫操作(find、insert、update)與業務邏輯(getExemption 計算免稅額)。

何時使用#

  • 適合:領域邏輯不太複雜的情況,如 CRUD 操作、基於單一記錄的推導與驗證
  • 不適合:業務邏輯複雜時——若需要物件間的直接關係、集合、繼承等,Active Record 難以映射,應改用 Data Mapper
  • Active Record 與資料庫的緊密耦合意味著物件設計被綁定於資料庫設計,使兩者都難以獨立重構
  • 適合作為從 Transaction Script 演進的中間步驟:當感受到程式碼重複的痛苦時,可以逐步建立 Active Record,先用 Gateway 包裝表,再慢慢將行為移入

Data Mapper#

在物件與資料庫之間搬移資料的映射層,使兩者彼此獨立,也獨立於映射器本身。

意圖#

物件與關聯式資料庫使用不同的機制組織資料。當業務邏輯複雜時,建立物件模型(使用集合、繼承等)來更好地組織資料與行為是有價值的,但這會導致物件 schema 與關聯 schema 不匹配。Data Mapper 是一層軟體,將記憶體中的物件與資料庫分離,負責在兩者之間傳輸資料並相互隔離。

運作方式#

  • 記憶體中的物件甚至不需要知道資料庫的存在——不需要 SQL 介面程式碼,也不知道資料庫 schema
  • 簡單的 Data Mapper 將資料庫表逐欄位映射到等效的記憶體類別;複雜情況則需處理單一類別拆分為多欄位、多表的類別、繼承的類別等
  • 載入物件時,使用 Identity Map 檢查物件是否已載入;若未載入則從資料庫載入
  • 更新時,映射層需要理解哪些物件已變更、哪些是新建的、哪些已刪除——Unit of Work 是組織這些工作的好模式
  • 由於物件高度互連,需要在某處停止拉取資料,否則會載入整個資料庫——使用 Lazy Load 處理
  • 可以為每個領域類別或領域層級結構的根建立一個 Mapper;若使用 Metadata Mapping 則可只用一個 Mapper 類別
  • Finder 需要使用 Identity Map 維護已讀取物件的身份識別
  • 整個 Data Mapper 層可以被替換,用於測試或支援不同資料庫
  • 映射欄位到領域物件的存取是一個問題:可使用 reflection、相同 package 的可見性規則、帶狀態檢查的 public setter、或 rich constructor

Figure 10.3: Retrieving data from a database with Data Mapper

Figure 10.5: Defining a finder interface in the domain package

Data Mapper 的核心價值在於:開發 Domain Model 時可以完全忽略資料庫,無論在設計還是建置測試過程中。領域物件不知道資料庫結構是什麼,所有對應關係都由 Mapper 完成。

何時使用#

  • 適合:希望資料庫 schema 與物件模型能獨立演進時,最常見的情況是搭配豐富的 Domain Model
  • 不適合:業務邏輯簡單時——額外的映射層是不必要的成本;若不需要 Domain Model,很可能也不需要 Data Mapper
  • 若領域模型簡單且資料庫在領域模型開發者的控制下,領域物件直接存取資料庫(即 Active Record)是合理的;隨著事情變複雜,再將資料庫行為重構到獨立的映射層
  • 不必從頭自建完整的映射層——市面上有許多成熟的 ORM 產品可用,大多數情況建議購買而非自建