Day 24:幫 Panel 寫測試,Testable 方法組合怎麼用 結尾留下一句話,上線前檢查清單,環境變數、快取與佇列,是接下來要處理的內容。今天要處理的正是這句話,也是第七階段「效能、測試與部署前準備」的最後一天。
這套後台走到昨天,功能有測試把關,效能也處理過大量資料的情境,商品能不能被正確建立、批次匯入的正確資料能不能寫入、錯誤資料能不能被攔下,這幾個關鍵行為現在都有自動化測試在背後看著。看起來已經萬事俱備。但測試驗證的終究是程式邏輯本身對不對,還沒有驗證這套系統擺到一台真正的正式環境伺服器上,環境設定是否也準備妥當。
這個落差值得攤開來講清楚。過去二十四天的示範操作,幾乎全部發生在本機開發環境裡。本機開發環境圖的是方便,改一行程式碼想立刻看到效果,出錯了想第一時間看到完整的錯誤堆疊,這些需求跟正式環境的需求剛好相反。除錯模式、快取策略、佇列處理,這三項設定在本機開發時,用的通常是最寬鬆、最方便除錯的預設值,寬鬆到平常根本不會意識到它們的存在。這些設定原封不動帶到正式環境,不會讓程式碼出現任何語法錯誤,測試也照樣能通過,但實際運作起來,可能悄悄曝露不該讓使用者看到的內部細節,也可能讓使用者站在畫面前面等一件明明可以在背景默默完成的事。
這裡有個容易被忽略的比喻。在家裡的排練室彩排一百次,跟真正站上舞台是兩回事,舞台的燈光音響條件不會跟排練室一模一樣,排練室裡聽起來剛好的音量,換到舞台上可能刺耳到蓋過整場演出。本機開發環境就是這間排練室,正式環境才是真正的舞台,同一份程式碼,換了一個環境設定,行為可能有明顯落差。
今天的任務範圍先講清楚,不新增任何新功能,商品、訂單、客戶這幾個模組的邏輯完全沿用前面二十四天已經定案的內容。要做的事情,是收斂一份跟示範專案目前已有功能直接相關的部署前檢查清單,聚焦環境變數、快取、佇列這三個項目,逐一示範每一項在正式環境該調整的地方,並在檢查清單走完之後,回頭完整回顧這個階段四天的進度。
第一項要檢查的,是 .env 檔案裡的環境變數,其中最先該處理的是 APP_DEBUG 這個開關。
本機開發時,APP_DEBUG 幾乎都設成 true,程式碼哪裡出錯,瀏覽器畫面會直接秀出完整的錯誤堆疊,連出錯的那一行程式碼、當下的變數內容都攤在畫面上,這對開發階段找問題非常方便。問題是這個開關一旦忘記關掉,直接帶進正式環境,任何使用者只要不小心觸發一個錯誤,就能在瀏覽器裡看到資料庫連線字串、檔案系統路徑,甚至部分程式碼片段。這不是理論上的風險,而是任何一台掃描正式環境弱點的自動化工具都會嘗試觸發的第一步,找一個容易出錯的網址,看看回傳的錯誤畫面願不願意多說幾句話。
# .env(正式環境)
APP_ENV=production
APP_DEBUG=false
APP_ENV 設成 production、APP_DEBUG 設成 false 之後,同樣的錯誤發生,使用者看到的只是一頁籠統的錯誤提示,內部細節則被導向伺服器端的日誌檔案,只有能登入伺服器的人才看得到。這一步調整起來只是改兩個字,卻是整份檢查清單裡最容易被忽略、代價也最高的一項,因為本機開發時這個開關幾乎天天開著,開著開著就會忘記它其實存在。
第二項是資料庫連線帳密、第三方服務金鑰這類敏感資訊的管理方式。這幾天示範專案陸續接上了資料庫、檔案儲存,這些連線資訊全部集中放在 .env 檔案裡,透過環境變數讀取,而不是寫死在程式碼裡,這是 Laravel 專案本來就有的慣例,這裡不重新解釋這套機制怎麼運作。真正要提醒的是 .env 檔案本身不該被提交進版本控制系統,.gitignore 裡理應已經排除這個檔案,正式環境的 .env 內容跟本機開發環境的 .env 內容也不會是同一份,兩邊的資料庫、金鑰理應完全獨立。這件事說起來像是常識,但常識性的疏忽往往就是敏感資訊外洩最常見的成因,檢查清單裡特別把它列出來,就是要在部署前的這一刻,再確認一次這件事沒有被跳過。
第二項檢查,是快取。這一項直接呼應 Day 22:資料變多之後,表格為什麼開始變慢 已經處理過的效能問題,快取解決的是另一個層次的效能成本。
Laravel 本身提供了組態設定與路由這兩種框架層級的快取機制。本機開發時,這兩種快取通常不會啟用,因為一旦啟用,修改 .env 或路由定義之後,要記得手動清除快取才會生效,這對每天都在改設定、調路由的開發階段反而是種阻礙,寧可犧牲一點效能,換取隨改隨生效的便利。但正式環境的性質完全相反,設定跟路由定案之後不會頻繁異動,這時候如果沒有啟用快取,等於每一次請求進來,Laravel 都要重新讀取、解析一遍散落在各處的設定檔案跟路由定義檔案,這是白白浪費掉的效能,啟用方式只是兩道指令:
php artisan config:cache
php artisan route:cache
這兩道指令跑完,設定跟路由都被序列化成一份單一的快取檔案,之後每次請求直接讀這份快取,不用重新解析散落各處的原始檔案。唯一要留意的是,之後如果又調整了 .env 或路由定義,記得重新跑一次這兩道指令,不然正式環境讀到的還是舊的快取內容,這是啟用框架層級快取之後容易踩到的另一個坑。
框架層級的快取解決的是「重複解析設定檔案」這種每次請求都要付出的固定成本,跟 Day 22 處理的資料查詢效能是不同層次的問題,但兩者的精神相通,能夠一次算好、重複使用的東西,就不該每次請求都重新算一遍。回到 Day 22 已經處理過的情境,訂單列表頁靠關聯預先載入跟資料庫索引解決了查詢次數過多跟單一查詢緩慢這兩個成因,但如果儀表板首頁的統計 Widget 底層查詢邏輯本身比較重,例如要即時計算當月訂單總額、當月新增客戶數這類需要掃過大量資料才能算出的數字,即使查詢已經盡量優化過,每次有人打開首頁就重新算一次,仍然是不必要的重複成本。這種情境適合替查詢結果加上一段短時間的快取:
use Illuminate\Support\Facades\Cache;
protected function getStats(): array
{
return Cache::remember('dashboard.order-stats', now()->addMinutes(5), function () {
return [
'total_amount' => Order::whereMonth('order_date', now()->month)->sum('total_amount'),
'new_customers' => Customer::whereMonth('created_at', now()->month)->count(),
];
});
}
Cache::remember() 的意思是,第一次呼叫時真的去資料庫算一次,算完的結果存進快取,接下來五分鐘內不管幾個人打開首頁,都直接讀快取裡現成的數字,不再重新查一次資料庫。五分鐘的時間差,對一份給管理者參考用的統計數字來說完全可以接受,換來的是首頁不管被打開幾次,最重的那段查詢只需要真正執行一次。這是 Day 22 已經建立的效能處理思路的延伸,先前處理的是讓查詢本身變快,這裡處理的是讓查得快的結果不需要重複算,兩件事疊加起來,才是快取這一項在正式環境真正該發揮的作用。
第三項檢查,是佇列。這一項直接呼應 Day 23:檔案上傳與資料匯入匯出,正式環境常踩的坑 建立的批次匯入功能。
本機開發時,佇列工作預設是同步執行,QUEUE_CONNECTION 設成 sync,一段程式碼被丟進佇列,跟直接呼叫這段程式碼幾乎沒有差別,請求送出後原地等待處理完成,處理完才回應。資料量小的時候,這個等待短到感覺不出來,開發階段測試功能,也樂得省下另外啟動一個背景處理程序的麻煩。
Day 23 建立商品批次匯入功能時已經點出這件事,匯入幾百筆商品資料的成本,跟灌入大量測試資料的成本本質相同,都是短時間內大量寫入資料庫。如果匯入工作繼續維持同步執行,管理者上傳一份幾百筆的商品清單,瀏覽器就得原地等待系統把這幾百筆資料逐列驗證、逐列寫入完成才回應,資料量再大一點,甚至可能因為處理時間超過伺服器設定的請求逾時上限,讓使用者收到一則不明不白的逾時錯誤,完全搞不清楚匯入到底成功了沒有。這就像一間餐廳把外帶單直接交給廚房現場做,客人站在櫃檯前面等到菜出爐才能走,換成佇列處理,則是後廚接單之後先讓客人拿號碼牌去外面坐,菜做好再廣播通知,客人不需要為了一件不急著馬上完成的事,站在原地枯等。
正式環境要把佇列真正用起來,第一步是把連線方式從同步換成真正的佇列驅動,常見的選擇是資料庫或 Redis:
# .env(正式環境)
QUEUE_CONNECTION=database
換成 database 之後,被丟進佇列的工作不會立刻執行,而是先寫進一張佇列資料表,等待背景處理程序來取用。這裡就出現了本機開發時最容易被忽略的一個環節,佇列裡的工作要真正被執行,伺服器上必須持續運作著一個背景處理程序:
php artisan queue:work --tries=3
這道指令在本機開發時常常沒有特別啟動,因為 sync 模式下工作本來就同步執行,感覺不出佇列處理程序存不存在,即使沒啟動也沒有任何異狀。正式環境如果同樣忘記啟動這個處理程序,或是伺服器重新啟動之後這個程序沒有跟著自動復原,佇列裡的工作會安靜地堆積在資料表裡,永遠不會被執行,管理者點了匯入卻感覺畫面卡住沒有反應,這種問題不會在錯誤日誌裡留下明顯線索,因為程式碼本身沒有出錯,只是沒有人去執行它。實務上會搭配 Supervisor 這類程序監控工具,確保 queue:work 這個背景處理程序意外中斷後能自動重新啟動,具體的監控工具設定不在今天的範圍內,這裡只需要記住一件事,啟用佇列不是改一行設定就結束,還得確認真的有一個持續運作的程序在消化佇列裡的工作。
環境變數、快取、佇列這份檢查清單走完,適合停下來完整回顧第七階段這四天走過的路徑。
Day 22:資料變多之後,表格為什麼開始變慢 把商品、訂單的資料量刻意灌大到貼近真實批發商的營運規模,讓過去二十一天感受不到的效能問題第一次現形,並找出關聯沒有預先載入、資料庫欄位缺乏索引這兩個常見成因,逐一對症處理。Day 23:檔案上傳與資料匯入匯出,正式環境常踩的坑 接著補上商品照片上傳、商品批次匯入、訂單資料匯出這三個真實業務會用到的檔案處理功能,也點出檔案儲存位置跟格式驗證這兩個正式環境容易踩的坑。Day 24:幫 Panel 寫測試,Testable 方法組合怎麼用 定案 Testable 這個方法組合,替風險最高的批次匯入功能寫出第一批自動化測試,讓程式邏輯不再只靠手動點擊驗證。今天則把環境變數、快取、佇列這幾項容易被忽略的正式環境設定,收斂成一份檢查清單,讓程式邏輯之外的環境條件也真正準備妥當。
把這階段前後對照一下,落差相當具體。這個階段開始之前,這套後台功能齊全,商品、訂單、客戶、庫存的操作一應俱全,但完全沒有被檢驗過大量資料下的實際表現,也沒有任何一行自動化測試在背後把關,更沒有人認真想過它被放到一台正式環境的伺服器上,該注意哪些跟本機開發環境不一樣的地方。階段結束的今天,這套後台第一次具備了「準備好上線」這件事實際需要的完整條件,效能有底、關鍵功能有測試保護、環境設定也收斂成一份清單,三者疊加起來,才構成一套真正經得起檢驗的系統。
這條路徑也呼應系列一直在講的那條主線,陽春後台逐步加深。前面幾個階段做的事情,補的是使用者一眼就能看到的東西,多一個 Resource、多一個關聯欄位、多一張圖表 Widget,每完成一項,畫面上就多一分變化,成就感直接可見。這個階段補上的東西性質不同,效能調校、自動化測試、環境設定,沒有一項會在畫面上長出新按鈕或新頁面,商品列表看起來跟四天前一模一樣。但少了這幾樣,這套系統就只是一套在示範環境裡看起來還不錯的展示品,經不起真實流量、真實資料量、真實伺服器環境的考驗。這種進展不像新增一個 Resource 那樣直觀,卻是一套可上線系統真正不可或缺的地基,地基埋進土裡之後看不見,但整棟建築能不能撐得住,全靠它。
這套後台走到今天,功能、關聯、驗證、權限、儀表板都已經齊備,今天再補上效能、測試、部署設定,前面七個階段各自負責的面向,如今一項一項疊加了起來。
但回頭看 Day 01:為什麼是 Filament,跟手刻後台與其他套件比起來差在哪 定案的示範情境,這套寵物用品批發商後台原本規劃了商品、訂單、客戶、庫存、報表五個模組。前四個模組這二十五天來,已經被反覆處理過無數次,商品從一個陽春的 Resource 一路加上表單、驗證、關聯、匯入匯出、效能調校;訂單串起客戶跟訂單明細,疊上角色權限跟多租戶隔離;客戶則貫穿整個系列,是關聯、篩選、匯出功能反覆練手的對象。唯獨報表模組,從系列第一天定案至今,從頭到尾都還沒有真正動手做過。
報表模組從零開始,從需求到 Resource 骨架,是接下來要處理的內容。