Roy-Try-Catch
← Back to list

【設計模式】Repository Pattern + Service Layer 在 Laravel 的實踐

Roy • Updated 2026-10-15 16:02:37

本篇介紹如何在 Laravel 中導入 Repository Pattern 與 Service Layer,將商業邏輯從 Controller 分離,提升可測試性與可維護性。

為什麼需要分層架構?

大多數 Laravel 專案初期都是 Fat Controller,所有邏輯堆在一起:

// 典型的 Fat Controller(難以維護、無法測試)
public function store(Request $request)
{
    $user = User::where('email', $request->email)->first();
    if ($user) {
        return response()->json(['message' => 'Email 已存在'], 422);
    }

    $user = User::create([...]);
    $user->roles()->attach(Role::where('name', 'member')->first());
    Mail::to($user)->send(new WelcomeMail($user));
    Cache::forget('users:list');
    Log::info('新用戶註冊', ['user_id' => $user->id]);

    return response()->json(['message' => '註冊成功']);
}

問題:Controller 同時負責驗證、資料庫操作、商業邏輯、快取、通知,一旦需求改變就要動到 Controller,無法單元測試。

三層架構設計

分層架構圖:Request 經 Controller 交給 Service,再透過 Repository Interface 由 Eloquent 實作存取資料庫。AppServiceProvider 綁定兩者,測試時以 Mock 取代。

Controller 管進出、Service 管邏輯、Repository 管資料庫。

層次 職責 不應該做的事
Controller 接收 Request、呼叫 Service、回傳 Response 直接操作 DB、撰寫商業邏輯
Service 商業邏輯、流程控制、跨 Repository 協調 直接操作 DB(透過 Repository)
Repository 所有資料庫查詢與寫入 撰寫商業邏輯

實作流程(以 User 模組為例)

Step 1:建立 Repository Interface

# app/Repositories/Contracts/UserRepositoryInterface.php

Step 2:建立 Eloquent 實作

# app/Repositories/EloquentUserRepository.php

Step 3:綁定 Interface → 實作

# app/Providers/AppServiceProvider.php
use App\Repositories\Contracts\UserRepositoryInterface;
use App\Repositories\EloquentUserRepository;

public function register(): void
{
    $this->app->bind(UserRepositoryInterface::class, EloquentUserRepository::class);
}

Step 4:建立 Service Layer

# app/Services/UserService.php

Step 5:Controller 只注入 Service

# app/Http/Controllers/UserController.php

單元測試的好處

導入分層後,Service 可以 Mock Repository,完全不需要碰真實資料庫:

class UserServiceTest extends TestCase
{
    public function test_register_throws_exception_when_email_exists()
    {
        // Mock Repository 回傳已存在的 User
        $mockRepo = Mockery::mock(UserRepositoryInterface::class);
        $mockRepo->shouldReceive('findByEmail')
            ->once()
            ->andReturn(new User());

        $service = new UserService($mockRepo);

        $this->expectException(\Exception::class);
        $service->register(['email' => '[email protected]']);
    }
}

目錄結構

app/
├── Http/
│ └── Controllers/
│ └── UserController.php # 只管 Request / Response
├── Services/
│ └── UserService.php # 商業邏輯
├── Repositories/
│ ├── Contracts/
│ │ └── UserRepositoryInterface.php
│ └── EloquentUserRepository.php # 資料庫操作
└── Providers/
 └── AppServiceProvider.php # Interface 綁定

什麼時候不需要用?

分層架構是有成本的,以下情境可以再斟酌:

  • 單純 CRUD 的小型專案:直接用 Controller + Model 就夠了
  • 原型開發、快速驗證:先求跑通,之後有需要再重構
  • 團隊規模小、沒有測試需求:分層反而增加溝通成本

分層的核心價值在於:商業邏輯複雜、需要單元測試、多人協作的中大型專案。

參考文獻:

https://laravel.com/docs/11.x/container

https://laravel.com/docs/11.x/providers

Comments

No comments yet.

請先登入