iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
Software Development

ERP 架構師筆記:定義驅動的框架設計系列 第 2

Day 2:Definition-Driven 架構總覽

  • 分享至 

  • xImage
  •  

Day 2:Definition-Driven 架構總覽

昨天結尾說今天要談框架該替使用者決定哪些事。

框架與函式庫的差別常被說成「你呼叫函式庫,框架呼叫你」。這句話描述的是機制,不是代價。真正的代價是控制權反轉之後,框架的預設值就變成你的預設值:它決定資料長什麼樣子、請求怎麼流動、擴充點開在哪裡。你可以不同意,代價是繞過它,而繞過一套框架通常比不用它更貴。

所以評價一套框架不該問「它能做什麼」。能做的事越多,代表它替你決定的事越少,同樣的決定,每一張表單都得再做一次。

本篇回答 Definition-Driven 框架必須先定案的四個問題,其餘設計全部是這四個答案的推論:

  1. 系統的唯一真相放在哪一層
  2. 哪些層由它衍生
  3. 哪些留給應用自己做
  4. 這一整套在什麼樣的系統上不成立

這四題沒有一題可以留白。框架不回答,用它的人就會各自回答,而且答案彼此不同:這個模組把真相放在 Entity,那個模組放在資料表,第三個放在前端的表單設定檔。昨天收斂的三個老問題,很大一部分就是這樣長出來的。


一、唯一真相放在哪一層

先澄清一件容易混淆的事:Code-First 的堆疊裡並不缺定義。API 契約是一份定義,Migration 腳本是一份定義,資料庫的中繼資料也是。差別不在有沒有定義,而在方向:那些定義是被倒出來的產物,改它們沒有意義,下一次產生就會被蓋掉。

所以問題不是「要不要有定義檔」,是箭頭該往哪邊指。候選只有三個:

候選 代表典範 問題
程式碼 Code-First 型別系統夠寬,什麼語意都塞得進去。代價是真相的讀者只有編譯器,改真相就要走編譯、打包、部署
資料庫 Database-First 資料是最終事實,但型別系統太窄
定義 Definition-Driven 需同時滿足兩個條件

資料庫窄在哪裡:它知道 total_amountdecimal(18,4),但不知道這個數字是金額、數量還是匯率,不知道該不該四捨五入、顯示幾位、必不必填、能不能修改、要不要出現在查詢清單上。這些語意它承載不了,最後還是得有另一個地方補上,真相又散開了。

定義要當真相,兩個條件缺一不可

  • 夠寬,寬到能承載結構以外的語意
  • 它是資料,不是程式碼(昨天整篇的主題)

這套框架裡一個欄位能宣告的東西,可分成六類:

類別 宣告的內容 資料庫放得下嗎
結構 欄位名、資料型別、長度、預設值
行為 必填、唯讀、是否顯示 部分
呈現 標題、控件型態、顯示格式、寬度
語意 數值種類、幣別欄位、計量單位欄位
關連 關連來源表單、欄位對應、顯示欄位
安全 資料範圍角色、敏感資料分類

程式碼六類都放得下,但要靠一疊 attribute 堆出來,堆完還是得有人把它們解析成資料才用得上。

這份宣告的寬度是刻意的:要一次蓋住 UI、資料庫與驗證三個面向。

唯一真相不是宣稱出來的,是相依方向決定的

任何框架都可以在文件裡寫「本框架以某某為唯一真相」。要驗證,看相依方向就夠了:真相那一層應該誰都不依賴,而所有人依賴它。

定義層這個套件的引用清單極短,它不知道有資料庫、不知道有 HTTP、不知道有 UI。反過來,資料存取、業務邏輯、API 契約、用戶端、每一個前端,全部引用它。

這條相依方向帶來兩個好處:

  • 定義層可以單獨被工具處理。定義編輯器、產生器、批次檢核腳本,只需引用這一個套件,不必把整個資料庫與 API 堆疊拖進來。昨天說「上千份定義可以寫一支腳本掃過去」,前提就在這裡。
  • AI 也是受益者。要讓它理解一套系統,餵定義比讓它掃整個程式碼庫有效率,結論也更可靠。差別不在檔案大小,在於關係是宣告出來的還是推論出來的:問「這張訂單牽連到哪些表單」,讀定義是把已經宣告好的關連讀出來;讀程式碼要追型別、追查詢、追組態,再推論意圖,而推論會出錯。

省下來的 token 是附帶結果。至於定義管不到的業務判斷,該讀程式碼的還是得讀。


二、哪些層由定義衍生

昨天用的是白話:UI、資料庫、CRUD SQL、驗證規則。精確一點的清單分兩種,多數是產生:

產生什麼 依據
單筆表單版面 表單結構(主檔區塊 + 明細區塊)
清單版面 表單結構宣告的清單欄位
開窗選取版面 表單結構宣告的可查詢欄位
資料表結構 表單結構的每一張表
INSERT / UPDATE / DELETE 表單結構的欄位集合
SELECT,含跨表 JOIN 表單結構的欄位與關連宣告
跨層傳輸的資料容器結構 表單結構的欄位集合

另一種是求值,框架不造東西,而是拿定義裡宣告的規則與運算式去算:驗證規則在存檔與刪除的時點求值,計算欄在使用者改動相關欄位的當下重算。

需求收斂出新的共通行為,就會再長出對應的機制。像報表的查詢條件畫面,本質上也是一組欄位加上編輯器,用版面定義描述得出來。

按「誰在用它」收攏:

表單結構定義
│
├─ 產生
│   ├─ 版面三種:單筆表單 / 清單 / 開窗選取  →  各端畫面
│   ├─ 資料表結構  →  資料庫
│   ├─ SQL 兩種:增修刪 / 查詢(含跨表 JOIN)  →  資料存取
│   └─ 跨層資料容器結構  →  跨層傳輸
│
└─ 求值
    ├─ 驗證規則  →  存檔與刪除的時點
    └─ 計算欄  →  使用者改動相關欄位的當下

產生的是程式碼,還是物件

同樣從定義長出東西,發生在建置期與執行期,結果完全不同:

  • 建置期產生器:產出程式碼。定義改了要重新產生、重新編譯、重新部署,昨天那條交付路徑一步都沒少,前面省下的好處在最後一關全部還回去。
  • 執行期衍生:產出記憶體裡的物件。定義換了,下次讀就是新的。「改定義到生效沒有編譯打包部署」成立的位置就在這裡。

換來的代價有兩個:

  • 編譯期型別檢查沒了,欄位名打錯要到執行期才知道(Day 9 會談這筆帳怎麼補)
  • 快取失效從最佳化變成必解題。定義為了效能一定會被快取,框架就得有一套通知機制,讓某個節點改掉的定義傳到其他每一個節點。

三、哪些留給應用

框架該停在哪裡,有兩條規則,一正一反。

正面:凡是制式化、標準化的行為,都該收斂成框架的機制。

這不只是少寫幾行程式:

  • 同一件事只有一套做法,要修就修那一處(省維護成本)
  • 新人、顧問、接手的人只要學一次,看懂一張表單就看得懂全部(省教育成本,這一項最容易被低估)

昨天列的那些管不動的症狀,全是「沒有通用機制」開出來的帳單,而且是按人頭、按年重複開的。

反面是一句判別法:這件事,全世界的 ERP 做起來是不是都一樣?一樣的由框架做,不一樣的留給應用。

依這條線,框架不碰三塊:

  • 業務判斷:狀態怎麼轉、什麼情況不准出貨,每一家都不一樣
  • 報表與批次的 SQL:多表 JOIN、彙總、動態條件、效能調校,硬套定義只會更複雜
  • 應用外殼:登入畫面、連線設定、導覽選單、視窗與佈景

不碰不等於不管。這三塊交出去之後,框架反而多出兩件必須做好的事:

  • 一張註冊表,把表單代號對到你寫的那個類別
  • 幾個位置明確的擴充點,讓你的邏輯掛得進存檔與刪除的流程

這兩件做得好不好,直接決定應用寫起來是順的還是彆扭的(第四章展開)。

交出去也不代表複雜度變少。定義能收掉的是重複,收不掉業務本身的難度;那些難的地方沒有消失,只是被集中到一處,交給知道它難在哪裡的人來寫。

第三塊在案例裡看得最清楚:Northwind 的前端有一個各端共用的 UI 專案,手寫的畫面只有四個(主視窗、登入、連線設定、承載表單的那一層),全部是外殼。八張業務表單沒有一張要寫畫面程式。

線畫在哪裡一眼就看得出來:人手寫的部分,恰好就是定義沒有接管的那一段。


四、架構模式:三個來源,各取一段

前三題答完,這套框架的形狀就定了,第四題留到下一節。中間先處理一個常被拿來描述它的說法:N-Tier、Clean Architecture 與 MVVM 的混血。「混血」很容易變成裝飾詞,所以逐項講清楚取了什麼、沒取什麼。

來源 取了什麼 沒取什麼
N-Tier 層次邊界與跨層資料容器。一次請求走前端 → API → Business Object → Repository → 資料存取基礎設施 → 資料庫,每層只認相鄰那層。三者中取得最完整
Clean Architecture 相依方向向內(第一節那條),翻專案引用清單就能驗證 對 Domain 的要求。純粹的做法是每個業務概念一個強型別 Domain Entity,而上千張表逐一建模正是昨天講的規模成本本身
MVVM 三個角色一個不缺:Model 是跨層資料容器、View 由版面定義渲染、ViewModel 是框架提供的 DataObject 無,差別只在 ViewModel 由誰寫

那個 DataObject 的職責一項不缺:包住資料容器、提供欄位讀寫給畫面繫結、發出值變更與未存檔狀態的通知、管理明細列編輯的開始與取消、向後端載入存檔刪除。差別是它不逐張手寫,形狀由定義決定。代價是畫面繫結的不再是 order.CustomerId 這種屬性,而是一個欄位名字。

跨層的資料容器也不是自訂型別,那個選擇的代價 Day 9 會專門談。


五、什麼樣的系統不該套

進場前先問三個問題,任何一個答不過去就該認真考慮別套。

Q1:你的畫面是不是制式表單?
定義能驅動版面的前提是版面模式收斂:目前收斂出來的是主檔的欄位區塊加上明細區塊。儀表板、圖表、拖拉式排程、地圖都不在模式裡。框架的回答不是「也支援」,而是這條路不走定義,你自己寫。

Q2:你的請求是不是一筆單據?
跨層資料容器是自描述的,代價是每次回應都帶著欄位結構一起送。一張訂單值得,每秒上萬筆的事件流不值得。

Q3:你的系統要活多久?
成本集中在前期,回報要三到五年才顯現。做完就結案、三年後整套重寫的系統,付了前期成本卻收不到回報。

三個條件同時成立的場景不多,而企業內部管理系統剛好全中:財務、採購、庫存、銷售、人資、客戶關係,畫面幾乎都是制式表單,請求幾乎都是單據,一上線就會活很久。

這些界線不是「框架還沒做到」,是做到了會更糟。一套什麼都能做的框架,等於什麼決定都沒替你做,而框架的價值恰恰就在那些決定上。


Northwind 案例:一份定義做了多少事

案例的訂單在定義裡只有一份表單結構,它交代了:

  • 主檔配一張明細
  • 主檔上三處跨表關連(客戶、員工、貨運商),都是開窗挑一筆回來
  • 明細每一列再挑一項產品
  • 金額不給人填,由數量、單價與折扣算出來
  • 存檔前要過三條驗證規則

寫完這一份,第二節那份清單全部到位:資料表照著建、清單與開窗選取的版面直接長出來、增修刪與查詢的 SQL 由框架自己組、資料跨層傳的時候帶著結構一起走、驗證規則到存檔那一刻會被檢查。沒有一項要另外寫程式。

同一份定義,四個前端一起讀:桌面、瀏覽器、iOS、Android,共用同一個 UI 專案。


框架的價值不在它能做多少,而在它分得清哪些該由它決定:一千次都一樣的事歸它,每次都不一樣的事歸你。這條線畫歪了兩邊都是負擔,畫得太保守,同一件事你要重做一千次;畫得太貪心,你要花更多力氣去繞過它。

明天讓案例正式登場。


本系列同步發表於 HackMD,完整目錄


上一篇
Day 1:為什麼 ERP 需要 Definition-Driven 框架
下一篇
Day 3:Northwind 案例的八張表單與自訂業務物件
系列文
ERP 架構師筆記:定義驅動的框架設計4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言