iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Software Development

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

Day 29:定義、框架與應用程式碼各負責哪一段

  • 分享至 

  • xImage
  •  

Day 29:定義、框架與應用程式碼各負責哪一段

昨天結尾說今天回到案例,把這些機制逐項歸位。

Day 1 說整套 Northwind 的八張表單裡只有一張需要自己寫程式,並答應系列收尾前把整套應用攤開逐項對帳;那一張裡裝了什麼,Day 3 已經交代過。今天把每一個行為拆開,看每一段由誰負責。

本篇說明:

  1. 系統行為的三個層次與各自的判準
  2. 一張表單長出來的東西,各層次分別出現了什麼
  3. 每一次呼叫都會跑的那些,應用各付出了什麼
  4. 手寫的程式剩下哪些、怎麼收斂到框架,以及刻意不走定義的地方

一、系統的行為分三個層次

系統裡的每一個行為拆開之後,每一段都落在下面三個層次之一:

層次 判準
定義 這個行為由一份定義檔決定,改那份檔案就會變
框架內建 這一段是框架的程式在跑,應用沒有為它寫程式
應用程式碼 有人手寫了 C#,換一套 ERP 就得重寫一次

一個行為可以跨好幾個層次

以明細的金額為例,拆開來分屬三個層次:

這一段 層次
金額的算式 定義,寫在欄位的 ValueExpression
捨到幾位 定義,查幣別主檔
什麼時候重算、什麼時候捨入 框架內建
把各列金額加總寫回主檔 應用程式碼

所以下一節的表上,有幾列寫的是「定義加框架內建」。

表上記的是現況

後面幾張表記的是案例現在的樣子,不一定是該在的位置。該在哪裡,用 Day 2 的判別法判斷:全世界的 ERP 做起來是不是都一樣,一樣的由框架做,不一樣的留給應用。上面那個加總就是例子,每一家都一樣,現在卻還寫在應用程式碼裡。

定義也不全是應用寫的

共用與紀錄兩個分類的 TableSchema 直接用框架的預設;部門、員工、稽核規則與權限那幾張,是照 Day 4 的做法從框架預設複製過來,員工另外加了兩個欄位。


二、一張表單長出來的東西

Day 2 列過由定義衍生的東西:多數是產生,另一種是拿宣告的規則與算式去求值。實際跟著一張表單出現的行為,比那份清單多幾項:

行為 落在哪個層次 案例上的樣子
欄位、型別、允不允許 NULL、唯一性 定義 一份 FormSchema 宣告完
資料表建立與後續升級 定義加框架內建 定義是每張實體表一份 TableSchemaDbCategorySettings 上一列;比對現況與下 DDL 是框架的
單筆、清單與開窗選取的版面 定義加框架內建 單筆表單讀應用寫的八份 FormLayout,清單與開窗選取由 FormSchema 的欄位集現排
增修刪查的語句 框架內建 查詢依這一次用得到的欄位現組,含跨表 JOIN;新增與修改帶整張表的欄位
跨表關連 定義加框架內建 一段宣告同時決定編輯器、選完回填、JOIN 與存進去的值
跨層傳輸的資料容器 框架內建 沒有實體類別、沒有 DTO、沒有對應設定
驗證規則與計算欄 定義 全應用只有訂單宣告了 FormRule 與一個 ValueExpression
新增一列的預設值 框架內建 依欄位型別填,日期欄填的是使用者時區的今天
對外開放的動作 框架內建 八張表單共用同一組,差別只有 ProgId
選單與程式註冊 定義 MenuSettings 一項、ProgramSettings 一列
定義之間對不對得起來 框架內建 隨套件出貨的建置期診斷,案例伺服端建置時就會檢查

加一張表單要碰的地方全部落在定義那個層次:一份 FormSchema、每張實體資料表一份 TableSchema、一份 FormLayoutDbCategorySettings 一列、ProgramSettings 一列、MenuSettings 一項。前三項是新檔案,後三項各加一行。建表的程式也不必動,它照 DbCategorySettings 登錄的表逐一建立或升級。

定義那個層次也會出錯,而且不會有人報錯。明細的 discount 只宣告了型別、沒有給位數,於是它建成一個小數位數為零的欄位;案例用的 SQLite 會原樣保存小數,折扣又全部是零,所以從來沒踩到。

至於應用程式碼那個層次:八張表單裡七張是零行。ProgramSettings 上那七列只寫了 ProgId 與顯示名稱,代表整條流程走預設。


三、每一次呼叫都會跑的那些

下面這些事每一次呼叫都會發生,最後一欄是應用為它付出的:

一次呼叫會發生的事 落在哪個層次 應用付出的
端點、請求解析、方法派發 框架內建 一個空的控制器子類
序列化與傳輸型別綁定 框架內建 伺服端零行;除了桌面,其餘三個前端的建置檔上各留了一筆放行
壓縮、加密、金鑰推導 框架內建 開機那幾行:主金鑰種子、payload 選項、自動建立旗標
存取控制、API 金鑰檢查、權限的三軸 框架內建 沒有另外宣告
登入、session、使用者時區與語言 框架內建 程式零行,時區與語言讀的是使用者那一列種子資料
錯誤契約與使用者看得到的訊息 框架內建 訊息文字寫在 FormRule 的屬性上,訂單的程式則寫在丟出的例外裡
稽核 框架內建 SystemSettings 上一段開關、DbCategorySettings 上幾列,程式零行
時區換算 框架內建 程式零行,應用自己那兩個時間欄位都是日曆日
數值捨入 框架內建 兩個欄位標了 NumberKind,另外部署幣別主檔、公司本幣設為 USD
定義快取與跨節點失效 框架內建 DbCategorySettings 上一列通知表

另外有一組機制,案例用得很少:

  • 沒有用到:業務外掛、權限模型、計量單位主檔,以及外掛設定的維護介面
  • 接上了但只有一份:客製層、多語系與幣別主檔,案例仍然是一家公司、一份客製、一種幣別,譯文也只有訂單那一張

它們要等到「第二個」出現才划得來:第二家公司、第二種語言、第二個角色、第二種幣別。所以這一組不能算進「框架替應用省下的」,它們是這套設計比較貴的部分,在第二個出現以前,付出去的成本看起來像純粹的浪費。


四、手寫的程式剩下哪些

訂單那五件事

OrderBO 只覆寫了框架切出來的其中兩個步驟:取一筆新資料,以及存檔之前。後者長這樣:

protected override void DoBeforeSave(SaveContext context)
{
    base.DoBeforeSave(context);   // FormRule 與 ValueExpression 先在這裡跑完

    var dataSet = context.DataSet;
    var master = OrderDataSet.MasterRow(dataSet);

    OrderDataSet.RequireAtLeastOneDetail(dataSet);
    EnforceStatusRules(dataSet, master);
    AssignOrderNumber(master);
    OrderDataSet.ComputeTotal(dataSet);
}

這裡是四件,第五件在取新資料那一步:

這一件 為什麼它還在程式裡
狀態轉換是否合法 真正的業務判斷。哪些狀態能跳、確認之後還能不能改明細,每一家訂的都不一樣
產生訂單編號 制式行為,該由框架收斂而還沒收斂
至少要有一列明細 跨列聚合。逐列求值的規則模型看得到一列的欄位,看不到這張單有幾列
明細加總寫回主檔 同上。框架已經負責每一列什麼時候被捨入,把捨過的值加起來這一步還在外面
訂單日期預設今天 框架本來就做了,取的是使用者時區的今天;這一行取的是伺服器的今天

從應用收斂到框架

表單自訂的邏輯與客製,會沿著同一條路往框架收:

步驟 發生什麼 案例上的樣子
應用先寫 框架還沒有對應的機制,寫在覆寫 BO 的步驟或業務外掛裡 明細金額、必填檢查與上表五件,一開始都寫在 OrderBO
判斷要不要收 用 Day 2 的判別法:每一家做起來是不是都一樣 狀態轉換每一家都不同,停在應用
框架接手 能宣告的寫進定義,不必宣告的由框架內建 明細金額改成 ValueExpression、必填檢查改成 FormRule、日期預設今天由框架內建;訂單編號與跨列加總還沒有對應的機制
應用刪掉舊程式 已經被接手的那一段從 BO 拿掉 金額與必填檢查已經刪了,訂單日期那一行還在

應用外殼

伺服端除了訂單,其餘程式都在描述這個部署,跟表單無關:

  • 多資料庫:程式裡只註冊資料庫驅動,案例用的是 SQLite。共用、公司、紀錄三個分類在 DatabaseSettings 各有一筆連線,目前都指向同一個檔案。要拆成不同資料庫只改這份設定,換一家資料庫再加上驅動的註冊,表單定義都不必動。
  • 其餘:主機接線、開發用的跨來源設定、空的控制器子類、示範帳號,以及建表與塞示範資料。

前端那一側

前端同樣跟表單無關:

  • 多前端:桌面、瀏覽器、iOS、Android 共用一個 UI 專案,各自只有幾行開機程式碼,宣告只走遠端、指定端點與金鑰存在哪裡。
  • 透過 Connector 呼叫後端:表單要用的每一個動作都有對應的方法。送出時帶上 API 金鑰與登入拿到的令牌,內容先壓縮再加密;瀏覽器那一個拿不到加密用的金鑰,只做到壓縮。
  • UI 專案裡沒有為某一張表單寫的檔案:裝表單的容器拿的是 ProgId,導覽清單從 MenuSettings 長出來。表單從八張變八十張,這一側不用改。

哪些地方是刻意不走定義的

  • 真正的業務判斷:案例上只有狀態轉換一件
  • 報表與批次的 SQL:多表 JOIN、彙總、動態條件、效能調校,硬套定義只會更複雜,框架給的是一條自己寫 Repository 的路
  • 深度的畫面客製:FormLayout 描述的是欄位怎麼排,不是一個儀表板該長什麼樣

這幾塊是邊界,不是待辦。待辦是框架還沒做完的部分,例如訂單編號與跨列加總。


小結

八張表單裡七張是零行,應用外殼與前端都跟表單有幾張無關。

三個層次跟表單數量的關係各不相同。定義那個層次線性成長,而且它該這樣長,因為每一張表單本來就有自己的欄位、自己的關連、自己要顯示什麼。框架內建那個層次是常數,一次呼叫要做的事不會因為表單變多而變多。應用程式碼那個層次跟的是規則,不是表單:框架還沒接住的每一條,每一張用到它的表單都得寫一次,最後該留下的只有每一家各自不同的那幾條。

訂單那五件事裡,有四件遲早可以離開應用:訂單編號等框架收斂,至少一列明細與明細加總等定義能跨列計算,訂單日期那一行直接刪掉。狀態轉換會一直留下,因為每一家都不同。Day 3 結尾說的「只在真正需要判斷的地方寫」,在這個案例上指的就是它。

明天是最後一天,把三十天的東西收成全圖。


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


上一篇
Day 28:DateTime 的時區轉換
下一篇
Day 30:三段開發深度與框架全圖
系列文
ERP 架構師筆記:定義驅動的框架設計30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言