0
user-people-family-house-home
>

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

本篇介紹如何在 Laravel 中導入 Repository Pattern 與 Service Layer,將商業邏輯...

Posted by Roy on 2026-02-26 18:00:20
1 目前 1 人正在閱讀
|
| Views

本篇介紹如何在 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,無法單元測試。

三層架構設計

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

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

Step 1:建立 Repository Interface

# app/Repositories/Contracts/UserRepositoryInterface.php
<?php

namespace App\Repositories\Contracts;

use App\Models\User;

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
    public function findByEmail(string $email): ?User;
    public function create(array $data): User;
    public function update(int $id, array $data): User;
    public function delete(int $id): bool;
}

Step 2:建立 Eloquent 實作

# app/Repositories/EloquentUserRepository.php
<?php

namespace App\Repositories;

use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;

class EloquentUserRepository implements UserRepositoryInterface
{
    public function findById(int $id): ?User
    {
        return User::find($id);
    }

    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }

    public function create(array $data): User
    {
        return User::create($data);
    }

    public function update(int $id, array $data): User
    {
        $user = User::findOrFail($id);
        $user->update($data);
        return $user->fresh();
    }

    public function delete(int $id): bool
    {
        return User::destroy($id) > 0;
    }
}

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
<?php

namespace App\Services;

use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;
use App\Jobs\SendWelcomeEmail;
use Illuminate\Support\Facades\Cache;

class UserService
{
    public function __construct(
        private UserRepositoryInterface $userRepository,
    ) {}

    public function register(array $data): User
    {
        // 商業邏輯:檢查 Email 是否重複
        if ($this->userRepository->findByEmail($data['email'])) {
            throw new \Exception('Email 已存在');
        }

        $user = $this->userRepository->create($data);

        // 派發歡迎信 Job
        SendWelcomeEmail::dispatch($user);

        // 清除快取
        Cache::forget('users:list');

        return $user;
    }

    public function updateProfile(int $id, array $data): User
    {
        return $this->userRepository->update($id, $data);
    }
}

Step 5:Controller 只注入 Service

# app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\Services\UserService;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function __construct(
        private UserService $userService,
    ) {}

    public function store(Request $request)
    {
        $user = $this->userService->register($request->validated());

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

單元測試的好處

導入分層後,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

留言區

請先登入才能發表留言