🎯 什麼情境該想到我
當你「想用一個類別集中處理某張表的所有列,以資料表為單位組織業務邏輯」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:為資料庫中的每一張表(或視圖)建立一個類別,該類別的單一實例處理該表所有列的業務邏輯。
做法要點:
- 一個 Table Module 實例對應「一張表」而非「一列」;方法多以列的識別碼為參數。
- 常搭配 Record Set(記錄集):從資料庫查出資料放進記錄集,Table Module 對其操作。
- 介於交易指令稿與領域模型之間,能複用邏輯又貼近表格資料結構。
- 特別適合以資料集 / 記錄集為核心的平台環境。
★★ 誠實的結論:這個模式今天幾乎不用,但它的問題還活著
★ 它的原生環境是 .NET 的 DataSet / ADO.NET(2002 年):
一個 OrdersModule 物件包住「orders 這張表的所有列」,
方法收 orderID 參數:module.CalculateTotal(orderID)
→ ★★ 前提是「有一個強大的記錄集([[記錄集]])型別,且 UI 能直接綁定它」
→ 而那個前提在 Go、在現代 Web(JSON API + SPA)都不成立
★★ 所以這一頁的價值不是「教你怎麼用」,而是「認出你不小心寫成了它」。
★★ 你可能已經寫成表格模組了:Go 裡的樣貌
// ★ 這是表格模組 —— 只是沒有人這樣叫它
type OrderService struct{ db *sql.DB }
func (s *OrderService) CalculateTotal(ctx context.Context, orderID string) (Money, error)
func (s *OrderService) Cancel(ctx context.Context, orderID string) error
func (s *OrderService) IsEligibleForRefund(ctx context.Context, orderID string) (bool, error)
// ★★ 特徵:每個方法都收 orderID,自己去撈資料、算完就丟★ 這個寫法的三個具體問題:
① ★★ 每個方法各撈一次資料 ⇒ N 次查詢,而且各自看到的版本可能不同
IsEligibleForRefund 讀到的訂單狀態,可能和 Cancel 讀到的不一樣
② ★ 不變式無處可放 —— 沒有一個「訂單物件」能守住「已取消不可再取消」
③ ★ 每個方法都要重複「載入 + 檢查存在 + 檢查權限」
★★ 判準:你的「Service」的方法是不是每一個都以 entityID 開頭?
是 → 你在寫表格模組。升級路徑是 領域模型 或 交易指令稿(載入一次、傳物件)。
★ 它唯一還合理的場景:真正的「集合運算」
// ★ 有些操作本質上就是「對一整張表做事」,沒有單一實體可以承載
func (r *ReportQueries) MonthlyRevenueByRegion(ctx context.Context, m Month) ([]RegionRevenue, error) {
// ★ 一個 GROUP BY 查詢,載入領域物件反而荒謬
}★★ 這類「報表/批次/統計」的查詢不該經過領域模型——
它們是 CQRS 的讀側:直接寫 SQL、回傳扁平的 DTO。
→ ★ 而這時它不叫表格模組,叫 查詢物件 或「讀模型」。
不要為了「架構一致」而讓報表查詢也載入一萬個聚合。
★ 三種領域邏輯組織方式的取捨(一次看完)
| 交易指令稿 | 領域模型 | 表格模組 | |
|---|---|---|---|
| 資料與行為 | ★ 分開 | ★ 在一起 | 行為在型別上、資料在記錄集 |
| 一個型別負責 | 一個操作 | 一個實體 | ★ 一整張表 |
| 適合的複雜度 | 低—中 | ★ 高 | 中(且 UI 綁定記錄集) |
| ★ 今天在 Go | ★★ 預設選擇 | 複雜領域 | ★★ 幾乎不用 |
| 主要風險 | 規則重複 | 過度工程、聚合過大 | ★ N 次查詢、不變式無處放 |
★ 為什麼它在今天消失了(三個原因)
- ★
DataSet那類「智慧記錄集」在現代技術棧裡不存在(Go 只有sql.Rows)。 - ★ UI 不再直接綁資料表——JSON API + SPA 之間是 DTO,不是記錄集。
- ★ ORM 普及讓「載入一個物件」變得便宜,
entityID參數式的寫法失去理由。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
因為是「一實例對一表」,難以表達單一列自身的複雜行為與繼承。
-
業務邏輯極複雜、需要豐富物件關係時,領域模型更合適。
-
沒有記錄集基礎設施時吸引力下降。
-
★★ 這個模式今天幾乎不該主動選用。它的前提(智慧記錄集 + UI 直接綁定)在 Go 與現代 Web 都不成立。
-
★★ 但你可能已經不小心寫成它了:判準是「Service 的每個方法都收
entityID,自己撈資料」。 -
★ 那個寫法的代價:N 次查詢、各方法看到不同版本、不變式無處放、載入與檢查邏輯重複。
-
★ 報表/統計/批次查詢不該經過領域模型——那是 CQRS 的讀側,直接寫 SQL 回傳 DTO(查詢物件)。
-
★ 不要為了「架構一致」讓報表也載入一萬個聚合。
-
這一頁的用途是「認出反模式」,不是「照著做」。
🔗 相關工具
- 領域模型 Domain Model(更適合複雜業務的替代方案)
- 交易指令稿 Transaction Script(更簡單的替代方案)
- 企業應用架構模式
- 交易指令稿、領域模型 —— 另兩種組織方式,也是升級路徑
- 記錄集 —— 它的前提,而那個前提在今天不成立
- 查詢物件 —— 報表/讀側查詢的正確落點
- 工具-領域邏輯組織模式 —— 三種選擇的判準
- 依戀情結 —— 「方法都收 entityID 自己撈資料」的壞味道