2025年9月29日 星期一

Laravel 架構設計:從 MVC 到分層架構 (Controller–Service–Repository) 與依賴注入

Laravel 架構設計:從 MVC 到分層架構 (Controller–Service–Repository) 與依賴注入

在 Laravel 開發中,大家最熟悉的模式莫過於 MVC (Model–View–Controller)。它能快速上手,讓我們在短時間內完成一個功能。但隨著專案規模擴大,MVC 也會逐漸顯露出一些限制,例如 Controller 或 Model 過於臃腫、測試困難、耦合度過高。

這時候,許多團隊會選擇採用 分層架構 (Controller–Service–Repository),並搭配 Laravel Service Container 的 依賴注入 (Dependency Injection, DI),來提升系統的可維護性與可測試性。本文將帶你深入比較這兩種架構,並分析它們的優缺點。

MVC 與分層架構的差異

傳統 MVC

在 Laravel 的典型 MVC 中:

  • Controller:接收請求、驗證輸入、呼叫 Model,最後回傳 View/JSON。

  • Model:同時負責資料存取與部分業務邏輯。

  • View:呈現資料。

這樣的設計在小型專案中非常高效,但隨著邏輯變複雜,Controller 和 Model 會逐漸變成「胖子」(Fat Controller / Fat Model)。

分層架構 (Controller–Service–Repository)

分層架構則將職責進一步拆分:

  • Controller 層:只負責「流量控制」,不直接操作資料庫。

  • Service 層:專注於業務邏輯,例如「計算折扣」、「檢查權限」、「處理交易流程」。

  • Repository 層:專注於資料存取,對上層隱藏資料來源細節(MySQL、Redis、API)。


實際案例:一個電商的訂單流程

假設我們要處理一個「建立新訂單」的功能。

MVC 架構的實作

PHP
// app/Http/Controllers/OrderController.php
class OrderController extends Controller {
    public function create(Request $request) {
        // 1. 驗證輸入
        $request->validate([...]);

        // 2. 業務邏輯與資料庫操作
        $user = User::find($request->user_id);
        if (!$user) {
            return response()->json(['error' => 'User not found'], 404);
        }

        $product = Product::find($request->product_id);
        if (!$product || $product->stock < $request->quantity) {
            return response()->json(['error' => 'Product stock not enough'], 400);
        }

        // 計算總價、套用優惠券...
        $totalPrice = $product->price * $request->quantity;
        // ... (更多複雜邏輯)

        // 3. 建立訂單與更新庫存
        $order = Order::create([
            'user_id' => $user->id,
            'total_price' => $totalPrice,
            // ...
        ]);
        $product->decrement('stock', $request->quantity);

        return response()->json($order, 201);
    }
}

問題:

  • Fat Controller: 所有邏輯都擠在 Controller 裡,變得難以閱讀與維護。

  • 難以測試: 如果要測試「建立訂單」的邏輯,必須連線到真實資料庫,無法進行快速單元測試。

  • 耦合度高: 如果訂單邏輯在其他地方(例如後台批次處理)需要重複使用,必須複製貼上這段程式碼。

分層架構的實作

首先,我們建立服務層和資料庫層。

1. Repository Layer (資料庫存取)

我們定義介面,讓 Service 只需要依賴介面,而非具體的實作。

PHP
// app/Repositories/Interfaces/OrderRepositoryInterface.php
interface OrderRepositoryInterface {
    public function createOrder(array $data);
}

// app/Repositories/OrderRepository.php
class OrderRepository implements OrderRepositoryInterface {
    public function createOrder(array $data) {
        return Order::create($data);
    }
}

2. Service Layer (業務邏輯)

這是處理所有業務規則的核心。

PHP
// app/Services/OrderService.php
use App\Repositories\Interfaces\OrderRepositoryInterface;

class OrderService {
    protected $orderRepository;

    public function __construct(OrderRepositoryInterface $orderRepository) {
        $this->orderRepository = $orderRepository;
    }

    public function createNewOrder(array $data) {
        // 1. 檢查商品庫存
        $product = Product::find($data['product_id']);
        if (!$product || $product->stock < $data['quantity']) {
            throw new \Exception('Product stock not enough');
        }

        // 2. 計算價格、處理優惠券...
        $totalPrice = $product->price * $data['quantity'];
        // ... (複雜業務邏輯)

        // 3. 呼叫 Repository 建立訂單
        $order = $this->orderRepository->createOrder([
            'user_id' => $data['user_id'],
            'total_price' => $totalPrice,
            // ...
        ]);

        // 4. 更新庫存
        $product->decrement('stock', $data['quantity']);

        return $order;
    }
}

3. Controller Layer (流量控制)

現在 Controller 變得非常輕量,只負責接收請求與呼叫服務。

PHP
// app/Http/Controllers/OrderController.php
use App\Services\OrderService;

class OrderController extends Controller {
    protected $orderService;

    public function __construct(OrderService $orderService) {
        $this->orderService = $orderService;
    }

    public function store(Request $request) {
        $request->validate([...]);

        try {
            $order = $this->orderService->createNewOrder($request->all());
            return response()->json($order, 201);
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }
}

分層架構帶來的優勢:

  • 職責分離: Controller 只處理 HTTP 請求與回應,Service 專注於業務,Repository 專注於資料庫。

  • 高可測試性: 測試 OrderService 時,只需注入一個模擬的 OrderRepositoryInterface,不需實際存取資料庫。

  • 高可重用性: OrderServicecreateNewOrder 方法可以被任何地方重複使用,例如後台管理系統或排程任務。


優缺點分析

優點

  • 模組解耦: Controller 不知道資料怎麼存取,Service 不知道資料庫細節,替換實作更容易。

  • 可測試性高: Service 可注入 Mock Repository,單元測試不需連真實 DB。

  • 維護性佳: 職責清楚,程式碼結構直觀,新人上手快。

  • 擴展性強: 業務邏輯集中在 Service,Repository 可共用,避免重複 SQL。

缺點

  • 結構複雜度增加: 小專案可能顯得過度設計。

  • 抽象過度: 如果業務邏輯簡單,Service/Repository 可能只是「轉手呼叫」。

  • 學習成本: 新手需要理解 IoC Container 與 DI,Debug 時要追蹤依賴鏈。

MVC 與分層架構比較表

面向MVC分層架構
Controller呼叫 Model,可能含業務邏輯只做流量控制,呼叫 Service
Model同時處理資料存取與邏輯僅作為 ORM 實體,資料存取交給 Repository
業務邏輯分散在 Controller/Model集中在 Service
測試性難以單元測試容易 Mock,測試友好
適用場景小型專案、CRUD 為主中大型專案、多人協作、業務複雜

結論

MVC:適合小型專案,開發快速,但隨著專案成長容易出現 Fat Controller / Fat Model 問題。

分層架構 + DI:適合中大型專案,能提升可維護性、可測試性與擴展性,但需要更多設計與學習成本。

簡單來說,MVC 偏向快速開發,而分層架構偏向長期維護與擴展。


👉 好的架構設計是為了應對複雜度。這個電商訂單案例很好地展示了分層架構如何讓你的程式碼更具彈性。在準備 高階 Backend/DevOps 面試 時,如果你能結合實際案例闡述,將能更有力地展現你的架構設計能力與工程判斷力。

您覺得這個加入案例的版本如何?是否更完整地表達了您的想法?

沒有留言:

張貼留言

熱門文章