你的 Controller 還在身兼多職?導入 Service Layer,打造可無限擴展的 Laravel Admin 後台架構!
嗨,我是浪花科技的 Eric。身為一個天天在 Laravel 專案裡打滾的資深工程師,我看過太多讓人頭痛的程式碼。其中最常見的「災難現場」,大概就是那個什麼都做的「萬能 Controller」。你是不是也看過這種 Controller?一個 function 動輒一兩百行,裡面有驗證、有資料庫操作、有商業邏輯、有檔案上傳、甚至還發送 Email 跟 LINE 通知。每次要改個小功能,都像在拆炸彈,深怕動到哪裡就整個炸掉。
我跟你講,這種寫法,短期內好像很快,但長期來看根本是技術債的溫床。專案一大,團隊一多,維護成本就會高到讓你懷疑人生。今天,我就要來跟你聊聊如何解救你那過勞的 Controller,導入一個在大型專案中幾乎是標配的架構模式:Service Layer(服務層)。這不只是個酷炫的名詞,更是打造一個乾淨、可維護、高彈性的 Laravel Admin 後台架構的關鍵鑰匙。
為什麼你的 Controller 會變成「肥宅」?認識 MVC 的美麗與哀愁
在我們動手重構之前,得先搞懂問題的根源。Laravel 作為一個優雅的 MVC (Model-View-Controller) 框架,其設計初衷是這樣的:
- Model (模型): 負責與資料庫互動,處理資料的存取與商業邏輯的核心。
- View (視圖): 負責呈現資料,也就是使用者看到的 UI 介面。
- Controller (控制器): 作為 Model 和 View 之間的橋樑,接收 HTTP 請求,調用 Model 處理資料,再把結果傳給 View。
這聽起來很完美,對吧?但魔鬼藏在細節裡。MVC 框架並沒有明確規定「商業邏輯」到底該放哪裡。新手開發者,甚至是不少有經驗的工程師,很自然地就會把所有跟請求處理相關的邏輯,通通塞進 Controller 裡。結果就是,Controller 越來越胖,胖到難以管理,我們稱之為「Fat Controller」。
一個典型的「肥宅 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. 驗證輸入資料
$validatedData = $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' => $validatedData['name'],
'email' => $validatedData['email'],
'password' => Hash::make($validatedData['password']),
'avatar' => $avatarPath,
'last_login_at' => now(),
]);
// 4. 觸發使用者註冊事件 (例如:發送歡迎信)
Mail::to($user->email)->send(new WelcomeEmail($user));
// 5. 記錄操作日誌
activity()->log('New user created: ' . $user->name);
// 6. 回傳成功訊息
return redirect()->route('users.index')->with('success', '使用者新增成功!');
}
}
看看上面這段程式碼,一個 `store` 方法就做了六件事!驗證、檔案處理、寫入資料庫、寄信、寫 Log… 這種程式碼有什麼問題?
- 難以重複使用:如果今天你需要寫一個 API 端點,或是一個 CLI 指令來建立使用者,你就得把這整坨邏輯複製貼上一次。完全違反了 DRY (Don’t Repeat Yourself) 原則。
- 難以測試:想對「建立使用者」這個核心功能寫單元測試?你得模擬一個完整的 HTTP Request,處理檔案上傳,還要 Mock Mail 跟 Log 的 Facades。我的天,光想就累了。
- 違反單一職責原則 (SRP):Controller 的職責應該是處理 HTTP 請求與回應,而不是包山包海地處理所有商業邏輯。
導入 Service Layer,讓 Controller 成功瘦身!
Service Layer (服務層) 是一個抽象層,專門用來封裝和組織你的「商業邏輯」。它像是一個專案經理,坐在 Controller 和 Model (或 Repository) 之間,負責協調所有事情。Controller 只需要跟這位專案經理(Service)說:「嘿,幫我建立一個新使用者」,然後把必要的資料(Request)交給它。至於背後要經過多少複雜的流程,Controller 一概不管。
第一步:建立你的 Service
我們通常會在 `app` 資料夾底下建立一個新的 `Services` 目錄來存放所有的 Service 類別。讓我們來建立一個 `UserService`:
app/Services/UserService.php
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;
class UserService
{
public function createUser(array $data, $avatarFile = null): User
{
// 處理上傳的頭像
$avatarPath = null;
if ($avatarFile) {
$avatarPath = $avatarFile->store('avatars', 'public');
}
// 建立使用者資料
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'avatar' => $avatarPath,
'last_login_at' => now(),
]);
// 觸發使用者註冊事件 (例如:發送歡迎信)
Mail::to($user->email)->send(new WelcomeEmail($user));
// 記錄操作日誌
activity()->log('New user created: ' . $user->name);
return $user;
}
}
看到了嗎?我們把所有跟「建立使用者」這個商業流程相關的邏輯,全部搬進了 `UserService` 的 `createUser` 方法裡。這個方法只專注做一件事,而且它的輸入和輸出都非常明確。
第二步:改造你的 Controller
現在,我們可以來重構原本的 `UserController`。利用 Laravel 強大的「依賴注入 (Dependency Injection)」,我們可以輕鬆地在 Controller 中使用 `UserService`。
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreUserRequest; // 使用 Form Request 做驗證
use App\Services\UserService;
class UserController extends Controller
{
protected $userService;
// 透過建構子注入 UserService
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function store(StoreUserRequest $request)
{
// 驗證邏輯已經被抽離到 StoreUserRequest
$validatedData = $request->validated();
// 呼叫 Service 處理核心商業邏輯
$this->userService->createUser(
$validatedData,
$request->hasFile('avatar') ? $request->file('avatar') : null
);
// Controller 只負責最後的回應
return redirect()->route('users.index')->with('success', '使用者新增成功!');
}
}
看看現在的 `store` 方法,是不是清爽多了?
- 驗證分離:我們使用了 Laravel 的 Form Request (`StoreUserRequest`) 來處理驗證邏輯,讓 Controller 更乾淨。
- 職責單一:Controller 現在只做三件事:接收請求、呼叫 Service、回傳回應。這才是它該做的工作!
- 邏輯集中:所有建立使用者的邏輯都在 `UserService`,未來如果 API 或其他地方也需要這個功能,直接呼叫 `UserService` 就好,不用再寫一次。
工程師的小囉嗦:Service vs. Repository,不要再搞混了!
講到 Service Layer,一定會有人跳出來問:「這跟 Repository Pattern (倉儲模式) 有什麼不一樣?」這問題問得好,因為很多人真的會把這兩者混為一談。
簡單來說,它們的職責不同:
- Repository Pattern:它的核心目的是「抽象化資料存取」。它定義了一個介面,讓你的應用程式可以跟資料來源(通常是資料庫)溝通,而不用管底層是 MySQL、PostgreSQL 還是什麼鬼。Repository 的方法通常是很單純的 CRUD 操作,例如 `find($id)`, `getAll()`, `create(array $data)`。
- Service Layer:它的核心目的是「封裝商業邏輯」。它會「使用」Repository 來存取資料,並在之上編排、組合出一個完整的商業流程。例如,`createUser` 這個「流程」就可能包含:呼叫 `UserRepository::create()` 新增資料、處理檔案上傳、發送通知信…等好幾個步驟。
一個比喻:Repository 就像是你的倉庫管理員,你跟他說要拿哪個貨架上的東西 (`find`) 或把新貨物放進去 (`create`),他就會幫你處理好。而 Service 則是採購部經理,他要下一個採購單(一個商業流程),他會告訴倉庫管理員(Repository)要去進貨,同時還要通知財務部付款(發送通知)、通知品管部驗貨(寫 Log)等等。兩者各司其職,合作無間。
結論:為你的 Laravel Admin 後台架構打好地基
導入 Service Layer 看似多了一些檔案和步驟,但對於一個需要長期維護和擴展的 Laravel Admin 後台來說,這絕對是百利而無一害的投資。它強迫你思考商業邏輯的邊界,讓你的程式碼結構更清晰、職責更分明。
當你的 Controller 瘦下來了,你的商業邏輯可以輕鬆被重複使用和測試,新加入的團隊成員也能更快地理解整個系統的運作方式。這不只是在寫「能動的程式碼」,而是在打造一個「健康的軟體架構」。身為工程師,我們追求的不就是這種優雅和從容嗎?別再讓你的 Controller 過勞了,今天就動手幫它瘦身吧!
延伸閱讀
- 肥 Controller 瘦不下來?Laravel 後台架構終極對決:Repository vs. Action 模式,資深工程師帶你選對屠龍刀!
- Laravel 後台架構再進化!導入『模組化設計』,打造可無限擴展的企業級後台
- 你的 Laravel 專案是技術債炸彈還是傳世藝術品?Laravel 10 專案架構最佳實務指南
需要專業的 Laravel 架構規劃與開發支援嗎?
如果你正在為複雜的後台系統架構感到頭痛,或是希望為你的團隊導入更穩健的開發實踐,浪花科技的團隊擁有豐富的 Laravel 企業級應用開發經驗。我們不只寫 Code,我們更專注於打造穩固、可擴展的系統架構。歡迎點擊這裡與我們聯繫,讓我們聊聊如何讓你的專案更上一層樓!
常見問題 (FAQ)
Q1: Laravel 中的 Service Layer(服務層)到底是什麼?
A1: Service Layer 是在 Controller 和 Model 之間的一個抽象層,專門用來封裝和處理應用程式的「商業邏輯」。它的主要目的是讓 Controller 保持精簡,只負責處理 HTTP 請求與回應,而將複雜的業務流程(例如:建立使用者、處理訂單、產生報表等)集中到對應的 Service 類別中,從而提高程式碼的可重用性、可測試性和可維護性。
Q2: Service 和 Repository Pattern(倉儲模式)有什麼根本上的不同?
A2: 兩者職責不同。Repository 的主要職責是「抽象化資料存取」,它作為應用程式與資料庫之間的溝通介面,專注於資料的 CRUD 操作。而 Service 的職責是「編排商業邏輯」,它會呼叫一個或多個 Repository 來操作資料,並組合其他操作(如發送郵件、寫入日誌)來完成一個完整的業務流程。簡單說,Repository 管「怎麼存取資料」,Service 管「用這些資料做什麼事」。
Q3: 什麼時候我應該開始在我的 Laravel 專案中使用 Service Layer?
A3: 當你發現以下情況時,就是引入 Service Layer 的好時機:1. 你的 Controller 方法變得越來越長、越來越複雜。2. 某段商業邏輯需要在多個地方被重複使用(例如,Web Controller 和 API Controller 都需要建立訂單)。3. 你想為核心的商業邏輯編寫單元測試,但因為邏輯和 HTTP 層耦合太深而變得困難。即使是中小型專案,及早引入也能為未來的擴展打下良好基礎。
Q4: 對於一個很簡單的 CRUD 專案,建立 Service Layer 會不會是過度工程化 (Over-engineering)?
A4: 這是一個很好的問題。如果你的專案真的非常單純,例如一個只有幾個欄位的文章管理系統,且可預見的未來不會有複雜的商業邏輯(例如,發布文章前不需審核、不需通知、不需數據分析),那麼直接在 Controller 中處理可能還算可以接受。但身為工程師,我們通常要為未來做打算。多數專案會隨著時間演進,今天的「簡單」可能就是明天的「複雜」。建立 Service Layer 是一個良好的開發習慣,初期投入的少量時間成本,往往能在後續的維護和擴展中獲得巨大回報。






