
Day 4 談到,當便當系統真的開始被使用後,一筆訂單就不再只是一列資料。
它會影響店家要準備幾份、不同樓層怎麼分餐,甚至牽涉到使用者的餘額與信任。
但那時候我還沒有完全意識到另一件事。
一開始很多流程之所以看起來很簡單,不一定是因為系統真的很簡單。
而是因為:
環境偷偷幫系統回答了很多問題。
例如:
當系統只服務固定店家時,這些問題甚至不需要被「設計」。
答案一直都一樣。
所以我很自然地把它們當成背景。
直到後來開始增加其他店家。
我第一個直覺也很單純:
多一家店,不就是多一個選項嗎?
做下去才發現,完全不是。
假設一開始系統只有一家便當店。
那很多事情可以非常直覺:
店家 = 固定
菜單 = 固定
價格 = 固定
截止時間 = 固定
甚至根本不需要特別問:
這份菜單是誰的?
因為只有一個答案。
也不用特別想:
這個價格從哪一天開始有效?
因為大家看的都是現在這份菜單。
這種做法在系統早期沒有什麼問題。
反而很合理。
因為當時最重要的是把真實流程跑起來,而不是先建立一套可以支援所有未來可能性的抽象模型。
但第二家店出現之後,原本那些「不用回答的問題」就全部回來了。
當店家從一變成二,表面上看起來最直覺的改法就是:
Order
+ vendor
畫面再多一個下拉選單:
請選擇店家
好像就結束了。
但很快會發現,跟著店家一起變動的東西遠比一個名稱多。
| 原本像常數的東西 | 多店家後要回答的問題 |
|---|---|
| 店名 | 這筆訂單到底屬於哪一家店? |
| 菜單 | 每家店現在使用哪一份? |
| 價格 | 同一餐點在不同時間價格是否一樣? |
| 截止時間 | 每家店、每種開團方式是否相同? |
| 停售狀態 | 某個餐點今天還能不能訂? |
| 菜單圖片/連結 | 管理者與使用者要看哪一個版本? |
這時候我才清楚感覺到:
原本是環境常數的東西,開始變成 Domain Data。
這和「多加幾個欄位」是兩件完全不同的事。
一般欄位很容易只考慮:
現在值是多少?
但店家、菜單、價格這類資料麻煩的地方,是它們會變,而且「現在的答案」不一定能代表「當時的事實」。
例如今天菜單價格是:
A 餐 100 元
幾個月之後變成:
A 餐 110 元
如果系統只保存現在價格,那一個很自然的問題就出現了:
三個月前那張 100 元的訂單,現在到底應該顯示多少?
如果直接拿現在菜單重新組畫面,歷史就可能被今天的狀態改寫。
店名也一樣。
系統裡曾經出現過名稱不一致的情況,因此後來需要做店家的 canonicalization,也就是:
系統內部先判斷「它到底是不是同一家店」。
但這不代表要把所有舊訂單上的文字全部改成今天最新的名字。
因為「現在怎麼稱呼這家店」和「當時那筆訂單記錄了什麼」是兩種不同責任。
所以後來我會先把這類資料粗略分成三種:
| 類型 | 例子 | 主要責任 |
|---|---|---|
| Current Configuration | 現在店名、目前菜單、截止設定 | 支援現在的操作 |
| Effective Data | 某段期間有效的菜單、價格 | 判斷某時間點應使用什麼 |
| Historical Snapshot | 訂單成立時的品項、價格、店家資訊 | 保留已發生的事實 |
這裡最重要的不是一定要做出多複雜的版本表。
而是先辨認:
哪些資料可以直接覆蓋,哪些不能。
否則很容易發生一件危險的事:
今天改了一份菜單,昨天的世界也跟著變了。
對現在畫面來說,那可能只是「資料同步」。
對歷史訂單來說,卻是在改寫事實。
後來系統裡出現了 Vendor Hub。
如果只看畫面,它很容易被理解成:
做了一個管理店家的新頁面。
但對我來說,它更像是一個訊號。
代表「店家」已經不再只是散落在程式裡的一個字串。
它開始需要集中管理:
換句話說,Vendor Hub 不是因為我突然想做後台。
而是因為 Domain 已經長到:
這些東西需要有一個正式的位置。
回頭看,這次最大的技術收穫不是 Vendor Hub 本身。
而是一個之後很常用的檢查方式。
看到一個目前寫死、固定或只有單一答案的東西時,我會問:
如果其中兩三題開始回答「會」,
那它通常已經不只是常數。
它正在變成:
需要被正式建模的 Domain Data。
這裡也有另一個很容易踩的坑。
知道「寫死的東西未來可能變」之後,很容易開始走向另一個極端:
那乾脆所有東西一開始都做成設定檔、版本表、規則引擎。
這通常也不是好答案。
如果 Day 3 的第一版就先為:
全部建模,
很可能只是把還沒發生的問題提前做一遍。
所以我現在比較傾向:
先讓真實變化證明抽象化的必要,再把已經出現的變化整理成模型。
不是看到任何常數就消滅它。
而是當現實已經出現第二種答案時,不再假裝只有一種答案。
Day 3 的 GAS + Sheets 解決的是:
怎麼快速把流程搬到手機。
Day 4 開始面對的是:
當資料真的承擔訂單與金額責任後,要怎麼讓它可信。
到了 Day 5,我看到的是另一種成長:
原本藏在環境裡的假設,開始一個一個被拉進系統裡。
店家不再固定。
菜單不再只有一份。
價格會變。
截止方式會變。
歷史也不能跟著現在一起改。
系統變複雜的原因,不是我突然想加更多功能。
而是現實世界開始給出:
第二個答案。
而一套原本只為「唯一答案」設計的系統,
就必須開始學會怎麼管理差異。
這不是純概念示範,而是一套持續開發中的便當訂購系統。文章著重「為什麼」,GitHub 保留實際程式碼、文件與演進紀錄。
GitHub:https://github.com/henryfir456/bento-order-app
做到這裡,GAS + Sheets 仍然可以繼續工作。
功能也沒有突然壞掉。
讓我開始重新思考架構的,不是「使用人數一多,系統就撐不住」,而是一件更日常、也更容易被忽略的事:
幾乎每一個主要操作,都要等。
登入,等個 1~3 秒。
打開訂單資料,等。
需要重新取得資料,再等。
最後送出訂單,還是要等。
單看其中任何一次,好像都不算嚴重。
系統最後也都有正常回應。
但這是一個每天都會使用的便當系統。
當這些等待散落在整個操作流程裡,「功能可以完成」和「操作起來順不順」開始變成兩件不同的事。
我也第一次明確意識到:
系統效能不一定要慢到不能用,才值得處理。
下一篇,我想談的就是這件事:
GAS 明明還能用,我為什麼還是開始想把它搬走?