Laravel 專案防爆架構:告別臃腫 Controller 與技術債
你的 Laravel 專案是否正走向技術債地雷區?資深工程師 Eric 警告,將所有邏輯塞入 Controller 是災難的開端。本文將揭示浪花科技的實戰「防爆架構圖」,教你如何運用 Service、Repository 與 Action 模式,徹底終結「義大利麵式程式碼」。我們透過實例重構一個 Fat Controller,展示如何打造一個高度內聚、易於測試與傳承的『積木城堡』。立即採用這套專業架構,將程式碼轉變為讓團隊驕傲的數位遺產,別讓失控的技術債拖垮你的業務!現在就聯繫我們,開始打造堅不可摧的數位產品!
你的 Laravel 專案是下一個技術債地雷?資深工程師的『防爆架構圖』,從零打造可傳承的程式碼帝國
嘿,我是浪花科技的 Eric。身為一個天天在 Code 海裡打滾的工程師,我看過太多令人讚嘆的專案,也拆過不少讓人想把電腦砸了的『義大利麵式程式碼』地雷。你是不是也遇過這種情況:剛接手的 Laravel 專案,打開 Controller,發現一個 function 就跟電話簿一樣厚,商業邏輯、資料庫查詢、第三方 API 呼叫、資料驗證…全都糾纏在一起。想改個小功能,卻像在玩踩地雷,深怕一動就引爆全站。
這就是典型的『技術債』,一開始求快,卻在未來用無數個加班夜晚來償還。今天,我不想跟你談什麼高深的理論,而是想分享一套我們在浪花科技身經百戰後,沉澱下來的 Laravel 10 專案架構最佳實務。這不只是一套規則,更是一種思維方式,能幫助你從源頭避免技術債,打造出一個不論是三個月後、還是三年後回頭看,都依然清晰、好維護、可擴展的『積木城堡』,而不是一團混亂的毛線球。
為何 Laravel 預設架構是『新手村』,而非『終點站』?
先別誤會,Laravel 預設的 MVC (Model-View-Controller) 架構非常優秀。它讓新手能快速上手,把 Controller、Model、View 各司其職,對於小型專案或原型開發來說,簡直是完美。但問題是,當專案規模擴大、商業邏輯變得複雜時,這個『新手村』的裝備就不夠用了。
開發者會很自然地把所有邏輯都往 Controller 裡塞,因為那是最直覺的地方。於是,你的 UserController 不只處理 HTTP 請求,還得負責:
- 驗證使用者輸入。
- 呼叫多個 Model 進行複雜的資料庫操作。
- 處理圖片上傳到 S3。
- 呼叫第三方服務發送歡迎郵件或簡訊。
- 根據不同條件組合複雜的查詢。
- 格式化回傳給前端的資料。
很快地,Controller 就會變得臃腫不堪,也就是我們常說的「Fat Controller」。這種程式碼不僅難以閱讀和維護,更可怕的是,它幾乎無法進行單元測試。你想測試發送郵件的邏輯嗎?抱歉,你得模擬一整個 HTTP 請求。這就是災難的開端。
後端架構三巨頭:Service, Repository, Action 的愛恨情仇
為了解決 Fat Controller 的問題,社群發展出了許多優秀的設計模式。其中,Service 層、Repository 模式和 Action 模式是最常被討論的三巨頭。它們不是互斥的,而是可以在同一個專案中各司其職、相輔相成的工具。身為工程師,囉嗦一點是應該的,我們得搞清楚每把武器的適用場景。
Service 層:你的商業邏輯總管
Service 層(服務層)是專門用來放置『商業邏輯』的地方。什麼是商業邏輯?簡單來說,就是那些跟「我們的生意怎麼做」有關的程式碼,例如「使用者註冊後,要建立會員資料、發送歡迎信、並給予 100 點紅利」。這些流程通常會跨越多個 Model,而且不應該被綁定在任何一個 Controller 裡面。
- 職責: 協調多個 Model 或其他 Service,完成一項完整的業務功能。
- 優點: 讓 Controller 變得很乾淨,只負責接收請求和回傳響應。商業邏輯可以被多個 Controller(例如 Web Controller 和 API Controller)重複使用。
- 範例:
OrderService,UserService,PaymentService。
Repository 模式:資料庫的『翻譯官』
Repository Pattern(倉儲模式)的初衷是將資料存取邏輯(無論是從資料庫、快取還是外部 API)從應用程式的其餘部分抽離出來。它扮演著應用程式和資料來源之間的中介層。
老實說,這年頭還在爭 Repository pattern 在 Laravel 中有沒有必要,其實有點像在爭 PHP 是不是最好的語言一樣…沒完沒了。反對者認為 Laravel 的 Eloquent ORM 本身就已經是個很強大的資料抽象層了,再加一層 Repository 只是徒增複雜度。我個人的看法是:對於 90% 的專案,直接用 Eloquent 就夠了。 但在某些特定情境下,Repository 依然非常有價值:
- 需要切換資料來源: 例如,你的產品資料可能一部分來自 MySQL,一部分來自外部的 ERP API。Repository 可以將這個複雜性隱藏起來,讓 Service 層用同樣的方式取得產品資料。
- 複雜的查詢邏輯: 當你有非常複雜、需要被多處複用的查詢時,可以將它們封裝在 Repository 的方法中,例如
getActiveUsersWithRecentOrders()。 - 強迫團隊遵守規範: 在大型團隊中,Repository 可以作為一個契約,確保大家用同樣的方式與資料庫互動。
Action 模式:單一任務的『特種兵』
Action 模式是近年來在 Laravel 社群中越來越流行的一種作法。它的核心思想非常簡單:一個類別,只做一件事情。 如果說 Service 像是一個負責多項事務的專案經理,那 Action 就是一個只專注於完成單一任務的特種兵。
- 職責: 執行一個單一、明確的動作。通常只有一個公開的方法,例如
execute()或handle()。 - 優點: 高度內聚、低耦合,非常容易理解和測試。可以避免 Service 層因為功能不斷增加而變得臃腫。
- 範例:
CreateUserAction,ProcessPaymentAction,UploadAvatarAction。
實戰演練:拆解一顆『肥 Controller 炸彈』
光說不練假把戲。讓我們來看一個實際的例子,如何將一個典型的 Fat Controller 方法,重構成清晰、可維護的結構。
改造前:令人崩潰的 Fat Controller
想像一下,你有一個使用者註冊的功能,Controller 長得可能像這樣:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;
class UserController extends Controller
{
public function store(Request $request)
{
// 1. 驗證
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
// 2. 處理頭像上傳
$avatarPath = null;
if ($request->hasFile('avatar')) {
$avatarPath = $request->file('avatar')->store('avatars', 'public');
}
// 3. 建立使用者
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
'avatar' => $avatarPath,
]);
// 4. 發送歡迎信
Mail::to($user->email)->send(new WelcomeEmail($user));
// 5. 分配預設角色 (假設有)
$user->assignRole('member');
return response()->json(['message' => 'User created successfully!', 'user' => $user], 201);
}
}
天啊,光看就頭痛。驗證、檔案處理、資料庫操作、郵件發送、權限分配…全都擠在一起。現在,我們來動手術。
改造後:清爽、可讀、可測試的『積木城堡』
我們的目標是讓 Controller 只做它該做的事:接收請求、呼叫核心邏輯、回傳響應。我們會使用 Action 模式來封裝「建立使用者」這個核心動作,並搭配 DTO (Data Transfer Object) 來傳遞結構化資料。
首先,我們建立一個 DTO 來承載使用者資料。我強力推薦使用 spatie/laravel-data 這個套件,它能讓 DTO 變得非常優雅。
1. 建立 UserData DTO (`app/Data/UserData.php`)
<?php
namespace App\Data;
use Spatie\LaravelData\Data;
use Illuminate\Http\UploadedFile;
class UserData extends Data
{
public function __construct(
public string $name,
public string $email,
public string $password,
public ?UploadedFile $avatar = null
) {}
}
2. 建立 CreateUserAction (`app/Actions/Fortify/CreateUserAction.php`)
我們將所有註冊邏輯都搬到這個 Action 裡。
<?php
namespace App\Actions\Users;
use App\Data\UserData;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;
class CreateUserAction
{
public function execute(UserData $data): User
{
$avatarPath = null;
if ($data->avatar) {
$avatarPath = $data->avatar->store('avatars', 'public');
}
$user = User::create([
'name' => $data->name,
'email' => $data->email,
'password' => Hash::make($data->password),
'avatar' => $avatarPath,
]);
Mail::to($user->email)->send(new WelcomeEmail($user));
$user->assignRole('member');
return $user;
}
}
3. 改造後的 Controller
現在,我們的 Controller 變得前所未有的清爽!
<?php
namespace App\Http\Controllers;
use App\Actions\Users\CreateUserAction;
use App\Http\Requests\StoreUserRequest; // 將驗證邏輯抽到 Form Request
use App\Data\UserData;
class UserController extends Controller
{
public function store(StoreUserRequest $request, CreateUserAction $createUserAction)
{
// 從 Request 自動轉換為 DTO
$userData = UserData::from($request);
// 執行 Action
$user = $createUserAction->execute($userData);
return response()->json(['message' => 'User created successfully!', 'user' => $user], 201);
}
}
看到了嗎?Controller 現在只剩下三行核心程式碼,每一行都只做一件事。它不關心密碼如何加密、頭像如何儲存、郵件如何發送。它只負責協調。這樣的程式碼,可讀性、可維護性、可測試性都提升了好幾個檔次。
不只是好看:『可演化架構』的四大核心價值
採用這樣的架構,得到的好處遠不只是程式碼變整潔而已,它為你的專案帶來了四大核心價值:
- 可維護性 (Maintainability): 當你需要修改註冊流程時(例如增加邀請碼功能),你只需要去
CreateUserAction這個檔案修改,而不用在龐大的 Controller 裡大海撈針。 - 可測試性 (Testability): 你可以輕易地對
CreateUserAction進行單元測試,模擬各種輸入,驗證輸出的使用者資料是否正確,而完全不需要啟動 HTTP 服務。 - 可擴展性 (Scalability): 如果未來你需要一個 CLI 指令來大量建立使用者,你可以在指令中直接呼叫
CreateUserAction,完美複用所有商業邏輯。 - 團隊協作 (Collaboration): 職責劃分清晰,前端工程師可以專注於 View 和 Controller,後端工程師可以專注於 Action 和 Service 的商業邏輯,大家可以並行開發,減少衝突。
延伸閱讀:成為 Laravel 架構大師的下一步
今天的分享只是個開始,良好的架構是一個不斷演進的過程。如果你想更深入了解,我們也準備了幾篇相關的文章,可以幫助你打通任督二脈:
- 肥 Controller 瘦不下來?Laravel 後台架構終極對決:Repository vs. Service vs. Action 模式,資深工程師帶你選對屠龍刀!
- Laravel 專案長不大?資深工程師的『可演化架構』指南,告別義大利麵程式碼!
- 不只會用,更要會『造』!Laravel 自訂驗證與 Middleware 黑魔法,打造無懈可擊的 API 防線
結論:你的程式碼,是你留給世界的數位遺產
我知道,一開始就導入這樣的架構,感覺會多寫一些檔案、多做一些規劃。但請相信我,這就像蓋房子前先畫好藍圖一樣,前期的投入會在專案的整個生命週期中,為你省下數不清的時間和精力。好的架構能讓你的專案走得更遠、更穩,也能讓你成為一個更專業、更有價值的開發者。
你的程式碼,不只是一行行的指令,它是你解決問題的思路、是你專業能力的體現,更是你留給下一位維護者(甚至是你自己)的數位遺產。希望今天的分享,能幫助你打造出讓自己和團隊都感到驕傲的程式碼帝國。
如果你對於如何將現有專案進行重構,或是想為新專案規劃一個穩固的架構感到困惑,浪花科技的團隊擁有豐富的實戰經驗,能為你提供專業的架構諮詢與開發服務。別讓失控的技術債拖垮你的業務,立即聯繫我們,讓我們一起打造堅不可摧的數位產品!
常見問題 (FAQ)
Q1: Repository 模式在 Laravel 10 中真的還有必要嗎?
A1: 這個問題在工程師社群爭論不休。我的建議是:如果你沒有明確的需求(如需要隨時抽換底層資料來源、或是有極度複雜且需共用的查詢邏輯),那麼直接使用 Eloquent 的豐富功能就足夠了。Eloquent 本身就是一個非常強大的 Active Record 實現,對於多數情境,再加一層 Repository 反而會過度設計。但如果你遇到了上述的特定場景,Repository 模式依然是個值得考慮的優秀工具。
Q2: 我應該在什麼時候使用 Action,什麼時候使用 Service?
A2: 一個簡單的判斷法則是:如果一個功能是「單一、明確的指令」,例如「建立一張訂單」、「上傳一張圖片」、「重設使用者密碼」,那麼它非常適合用 Action。如果一個功能是「一組相關操作的集合」,需要協調多個步驟或邏輯,例如一個 OrderService 可能包含 placeOrder()、cancelOrder()、applyDiscount() 等多個方法,那麼 Service 會是更好的選擇。Action 強調單一職責,而 Service 則是用來組織相關的商業邏輯。
Q3: 對於一個小型的個人專案,也需要這麼複雜的架構嗎?
A3: 不一定。軟體架構沒有銀彈,過度設計和缺乏設計一樣是問題。對於一個小型的、或是一次性的專案,使用 Laravel 預設的 MVC 架構快速開發是完全合理的。但如果你預期這個專案未來會成長、功能會擴充、或者會有其他人加入協作,那麼從一開始就採用更結構化的方法(例如將邏輯抽離到 Action 或 Service),會是一個非常有遠見的投資。
Q4: 什麼是 DTO (Data Transfer Object),為什麼在這套架構中它很重要?
A4: DTO 是一個只用來攜帶資料的簡單物件。它的作用是在不同層級之間(例如 Controller 和 Action/Service 之間)傳遞結構化、型別明確的資料。使用 DTO 的好處是:1. 讓你的程式碼意圖更清晰,一眼就知道需要哪些資料。2. 享受 IDE 的自動完成和靜態分析的好處,減少因打錯字或傳錯型別造成的錯誤。3. 將資料驗證和轉換的邏輯與核心商業邏輯分離。在現代化的 Laravel 架構中,DTO 扮演著讓各層之間溝通更安全、更可靠的關鍵角色。






