購物車與定價用 DTO,其餘模組直接用 Eloquent。
我的系統裡有兩種寫法。
橫幅、公告、商品、分類這些模組,後台 controller 就是這樣:
public function store(Request $request)
{
$data = $this->validated($request);
Banner::create($data);
return redirect()->route('admin.banners.index')
->with('status', "已新增橫幅:{$data['name']}");
}
驗證、建立、導回。沒有 Service、沒有 Repository、沒有 DTO。一個模組一個下午做完。
但購物車與定價那塊,有十幾個小類別,全部不碰資料庫,只負責算錢:CartInput、CartState、Adjustment、PricingRules、PricingPipeline、CheckoutState⋯⋯
同一個專案,同一個人寫的。差別只有一個:會不會算錢。
橫幅排序錯了,後台拖一拖就好。訂單金額算錯了,錢已經收了,你要退差額、重開發票、跟客服解釋、查有多少張單受影響。
能事後手動修的,不需要在架構上防它;修不回來的,才值得多寫幾個類別。
所以我判斷一個模組要用哪種寫法,只問一句:出錯的話,是後台改一下就好,還是要退錢?
有一個地方要特別分清楚:「促銷設定」跟「促銷計算」是兩個模組。 後台那個新增滿額折扣的 CRUD 畫面,就是普通的 Eloquent,跟橫幅沒兩樣;只有把那些設定拿來算錢的那一段,才走 DTO。很多人卡住,是因為把一個功能當成一個模組。
兩種寫法的交界,我集中在少數幾個 builder(PromotionCatalogBuilder、CartSession::state),負責把 Eloquent 撈出來的東西轉成純資料丟進核心。這些 builder 裡面沒有一行數學,只是換個形狀。而且是單向的:核心算完只回傳結果,由 controller 拿去寫 DB,核心自己不存檔。
全部模組都上重架構,做一個公告要開八個檔案,工程師會開始偷懶。全部直接寫 Eloquent,促銷邏輯會散在五個地方,沒有人敢動。
答案不在兩端,答案是分配。 把嚴謹度集中投資在會算錢的那 10%,其餘的地方,能有多快就多快。