- Giới thiệu về Repository Pattern trong Laravel
- 1. Khi nào nên áp dụng Repository Pattern?
- 2. Kiến trúc Repository cơ bản
- 3. Sử dụng Repository trong Controller
- 4. Lợi ích thực tiễn và so sánh
- 5. Các best practices khi triển khai Repository
- 6. Một ví dụ thực tế: Xây dựng API người dùng với Repository và Cache
- Kết luận
Giới thiệu về Repository Pattern trong Laravel
Trong các dự án Laravel ngày càng phức tạp, việc quản lý truy cập dữ liệu một cách có cấu trúc và tách biệt khỏi logic nghiệp vụ là một nhu cầu thiết yếu. Repository Pattern cung cấp một lớp trừu tượng giữa Eloquent Models và các thành phần khác của ứng dụng (Controller, Service, Job...). Điều này giúp:
- Giảm sự phụ thuộc trực tiếp vào ORM, dễ dàng thay đổi nguồn dữ liệu (MySQL, MongoDB, API).
- Tăng tính testability bằng cách mock repository trong unit test.
- Đảm bảo single responsibility principle – mỗi lớp chỉ chịu trách nhiệm một phần.
Bài viết sẽ đi sâu vào cách thiết kế, triển khai và tối ưu Repository Pattern trong Laravel, đồng thời so sánh với cách tiếp cận truyền thống sử dụng trực tiếp Model.
1. Khi nào nên áp dụng Repository Pattern?
Mặc dù Laravel đã cung cấp Eloquent ORM mạnh mẽ, nhưng trong các trường hợp sau, việc đưa Repository vào kiến trúc sẽ mang lại lợi ích rõ rệt:
- Dự án có nhiều nguồn dữ liệu: Khi cần truy vấn đồng thời từ MySQL và một API bên ngoài.
- Yêu cầu test unit cao: Khi muốn viết test cho service layer mà không phụ thuộc vào database.
- Quy mô dự án lớn: Khi có nhiều nhóm phát triển, việc chuẩn hoá cách truy cập dữ liệu giúp giảm xung đột.
- Thay đổi công nghệ: Khi muốn chuyển từ MySQL sang PostgreSQL hoặc từ Eloquent sang Query Builder mà không ảnh hưởng tới các lớp sử dụng.
2. Kiến trúc Repository cơ bản
2.1. Định nghĩa Interface
Interface mô tả các phương thức mà Repository phải triển khai. Đây là hợp đồng (contract) giữa tầng service và tầng dữ liệu.
<?php
namespace App\Repositories\Contracts;
interface UserRepositoryInterface
{
public function all(array $columns = ['*']);
public function find(int $id, array $columns = ['*']);
public function findByEmail(string $email);
public function create(array $data);
public function update(int $id, array $data);
public function delete(int $id);
}
?>2.2. Triển khai Repository bằng Eloquent
Repository thực tế sẽ thực hiện các phương thức đã khai báo, sử dụng Eloquent Model hoặc Query Builder tùy nhu cầu.
<?php
namespace App\Repositories\Eloquent;
use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;
class UserRepository implements UserRepositoryInterface
{
protected $model;
public function __construct(User $model)
{
$this->model = $model;
}
public function all(array $columns = ['*'])
{
return $this->model->all($columns);
}
public function find(int $id, array $columns = ['*'])
{
return $this->model->find($id, $columns);
}
public function findByEmail(string $email)
{
return $this->model->where('email', $email)->first();
}
public function create(array $data)
{
return $this->model->create($data);
}
public function update(int $id, array $data)
{
$user = $this->model->findOrFail($id);
$user->update($data);
return $user;
}
public function delete(int $id)
{
return $this->model->destroy($id);
}
}
?>2.3. Đăng ký binding trong Service Provider
Laravel cho phép binding interface tới implementation trong App\Providers\AppServiceProvider hoặc tạo một RepositoryServiceProvider riêng.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Repositories\Contracts\UserRepositoryInterface;
use App\Repositories\Eloquent\UserRepository;
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(UserRepositoryInterface::class, function ($app) {
return new UserRepository($app->make('App\Models\User'));
});
}
}
?>Đừng quên thêm RepositoryServiceProvider::class vào mảng providers trong config/app.php.
3. Sử dụng Repository trong Controller
Với dependency injection, controller không còn phụ thuộc vào Model trực tiếp mà chỉ làm việc với interface.
<?php
namespace App\Http\Controllers;
use App\Repositories\Contracts\UserRepositoryInterface;
use Illuminate\Http\Request;
class UserController extends Controller
{
protected $userRepo;
public function __construct(UserRepositoryInterface $userRepo)
{
$this->userRepo = $userRepo;
}
public function index()
{
$users = $this->userRepo->all();
return view('users.index', compact('users'));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:6',
]);
$data['password'] = bcrypt($data['password']);
$this->userRepo->create($data);
return redirect()->route('users.index')->with('success', 'User created successfully');
}
}
?>4. Lợi ích thực tiễn và so sánh
4.1. Tính mở rộng
Khi cần chuyển sang MongoDB hoặc một API client, chỉ cần tạo một lớp Repository mới implement UserRepositoryInterface và thay đổi binding trong Service Provider. Các controller và service không cần thay đổi.
4.2. Testability
Trong unit test, chúng ta có thể mock interface để kiểm tra logic nghiệp vụ mà không thực thi query thực tế.
<?php
use Tests\TestCase;
use Mockery;
use App\Repositories\Contracts\UserRepositoryInterface;
use App\Http\Controllers\UserController;
class UserControllerTest extends TestCase
{
public function testIndexReturnsViewWithUsers()
{
$mockRepo = Mockery::mock(UserRepositoryInterface::class);
$mockRepo->shouldReceive('all')->once()->andReturn(collect([['id'=>1,'name'=>'John']]));
$controller = new UserController($mockRepo);
$response = $controller->index();
$this->assertEquals('users.index', $response->getName());
$this->assertArrayHasKey('users', $response->getData());
}
}
?>4.3. So sánh với việc gọi Model trực tiếp
| Tiêu chí | Sử dụng Model trực tiếp | Repository Pattern |
|---|---|---|
| Độ phụ thuộc | Controller phụ thuộc vào Model | Controller phụ thuộc vào Interface |
| Thay đổi nguồn dữ liệu | Phải sửa mọi nơi | Chỉ thay đổi binding |
| Test unit | Khó mock, cần DB | Dễ mock, không cần DB |
| Độ phức tạp ban đầu | Thấp | Vừa (cần tạo Interface, Provider) |
5. Các best practices khi triển khai Repository
- Giữ Interface ngắn gọn: Chỉ khai báo các phương thức thực sự cần thiết cho nghiệp vụ.
- Không lạm dụng Repository: Đối với các truy vấn đơn giản (ví dụ
User::find($id)) không nhất thiết phải tạo Repository. - Sử dụng Criteria/Specification pattern nếu cần lọc phức tạp, giúp tách logic query ra khỏi Repository.
- Cache kết quả trong Repository khi dữ liệu ít thay đổi, giảm tải DB.
- Document các phương thức để các developer khác hiểu rõ mục đích và dữ liệu trả về.
6. Một ví dụ thực tế: Xây dựng API người dùng với Repository và Cache
Giả sử chúng ta cần một endpoint trả về danh sách người dùng, hỗ trợ phân trang và cache trong 5 phút.
6.1. Thêm phương thức vào Interface
<?php
namespace App\Repositories\Contracts;
interface UserRepositoryInterface
{
// ... các phương thức đã có
public function paginate(int $perPage = 15);
}
?>6.2. Triển khai trong Repository
<?php
namespace App\Repositories\Eloquent;
use Illuminate\Support\Facades\Cache;
class UserRepository implements UserRepositoryInterface
{
// ... các phương thức cũ
public function paginate(int $perPage = 15)
{
return Cache::remember('users_page_' . request('page', 1), 300, function () use ($perPage) {
return $this->model->orderBy('created_at', 'desc')->paginate($perPage);
});
}
}
?>6.3. Controller sử dụng
<?php
namespace App\Http\Controllers\Api;
use App\Repositories\Contracts\UserRepositoryInterface;
use App\Http\Controllers\Controller;
class UserApiController extends Controller
{
protected $userRepo;
public function __construct(UserRepositoryInterface $userRepo)
{
$this->userRepo = $userRepo;
}
public function index()
{
$users = $this->userRepo->paginate(20);
return response()->json($users);
}
}
?>Kết quả trả về sẽ là một JSON chứa dữ liệu người dùng, meta pagination và được cache trong 5 phút, giảm đáng kể số lượng truy vấn tới DB.
Kết luận
Repository Pattern không chỉ là một mẫu thiết kế mà còn là một công cụ mạnh mẽ giúp Laravel trở nên modular, testable và độc lập với nguồn dữ liệu. Khi áp dụng đúng cách, bạn sẽ giảm thiểu rủi ro khi thay đổi công nghệ, tăng tốc độ phát triển và duy trì dự án lâu dài. Đối với những ai muốn nâng cao kiến thức Laravel và thực hành các kiến trúc hiện đại, Tham khảo khóa học "Lập trình web PHP & MySQL với Laravel Framework" tại đây.







