Series Repository Pattern | Bài 3: Giải cứu "Đầm lầy" Filter với Criteria Pattern
Chào anh em, tiếp nối series, hôm nay chúng ta sẽ bàn về một nỗi đau mà bất cứ Backend Engineer nào cũng từng trải qua: Xây dựng bộ lọc tìm kiếm (Filter/Search).
Khi project còn nhỏ, bạn có thể viết một hàm trong OrderRepository như thế này:
public function getOrders($status, $fromDate, $toDate, $userId, $keyword) { ... }
Nhưng khi yêu cầu nghiệp vụ tăng lên, hàm này sẽ phình to với hàng chục tham số if - else chằng chịt để kiểm tra xem tham số nào được truyền vào. Tệ hơn nữa, nếu ngày mai sếp yêu cầu lọc thêm theo "Phương thức thanh toán", bạn lại phải sửa Interface, sửa Repository, và sửa luôn cả chỗ gọi ở Controller. Tính Đóng/Mở (Open/Closed Principle trong SOLID) đã bị vi phạm nghiêm trọng!
Để giải quyết, chúng ta sẽ kết hợp Repository với Criteria Pattern (Mẫu tiêu chí).
1. Criteria Pattern là gì?
Thay vì truyền 1 đống tham số rác vào hàm của Repository, chúng ta sẽ đóng gói mỗi "điều kiện lọc" thành một "Class độc lập" (được gọi là một Criterion/Criteria).
Ví dụ:
- Lọc theo trạng thái -> Tạo class StatusCriteria.
- Lọc theo trạng thái -> Tạo class StatusCriteria.
Repository lúc này đóng vai trò như một cái thùng chứa. Bạn ném (push) bao nhiêu Criteria vào cái thùng đó thì nó sẽ tự động nối các câu query (WHERE) lại với nhau.
2. Các bước triển khai thực tế
Bước 1: Khai báo Bản hợp đồng Criteria
Tạo một interface chung cho tất cả các điều kiện lọc.
<?php
// app/Repositories/Criteria/CriteriaInterface.php
namespace App\Repositories\Criteria;
use Illuminate\Database\Eloquent\Builder;
interface CriteriaInterface
{
/**
* @param Builder $model
* @param RepositoryInterface $repository
* @return Builder
*/
public function apply($model, $repository);
}
Bước 2: Nâng cấp "Người cha" Base Repository
Chúng ta cần bổ sung cho BaseRepository (đã viết ở Bài 2) khả năng tiếp nhận và xử lý các Criteria này.
Bước 2: Nâng cấp "Người cha" Base Repository
Chúng ta cần bổ sung cho BaseRepository (đã viết ở Bài 2) khả năng tiếp nhận và xử lý các Criteria này.
Bước 3: Tạo các Criteria cụ thể
Giờ là lúc chúng ta bóc tách các điều kiện if - else lộn xộn thành các Class sạch sẽ.
Tiêu chí 1: Lọc Đơn hàng theo Trạng thái
<?php
// app/Repositories/Criteria/Order/StatusCriteria.php
namespace App\Repositories\Criteria\Order;
use App\Repositories\Criteria\CriteriaInterface;
class StatusCriteria implements CriteriaInterface
{
protected $status;
public function __construct($status)
{
$this->status = $status;
}
public function apply($model, $repository)
{
if (empty($this->status)) return $model;
return $model->where('status', $this->status);
}
}
Tiêu chí 2: Lọc Đơn hàng theo ngày tạo
<?php
// app/Repositories/Criteria/Order/DateRangeCriteria.php
namespace App\Repositories\Criteria\Order;
use App\Repositories\Criteria\CriteriaInterface;
class DateRangeCriteria implements CriteriaInterface
{
protected $fromDate;
protected $toDate;
public function __construct($fromDate, $toDate)
{
$this->fromDate = $fromDate;
$this->toDate = $toDate;
}
public function apply($model, $repository)
{
return $model->whereBetween('created_at', [$this->fromDate, $this->toDate]);
}
}
Bước 4: Tận hưởng sự "Thanh lịch" tại Controller
Hãy xem cách Controller của chúng ta gọi data khi có Criteria. Mọi thứ trở nên trong vắt như nước cất!
<?php
namespace App\Http\Controllers;
use App\Repositories\Contracts\OrderRepositoryInterface;
use App\Repositories\Criteria\Order\StatusCriteria;
use App\Repositories\Criteria\Order\DateRangeCriteria;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function __construct(protected OrderRepositoryInterface $orderRepo) {}
public function index(Request $request)
{
// 1. Nhận param từ URL (vd: ?status=completed&from=2026-01-01&to=2026-06-01)
$status = $request->get('status');
$from = $request->get('from');
$to = $request->get('to');
// 2. Ném (Push) các điều kiện lọc vào Repository
if ($status) {
$this->orderRepo->pushCriteria(new StatusCriteria($status));
}
if ($from && $to) {
$this->orderRepo->pushCriteria(new DateRangeCriteria($from, $to));
}
// 3. Thực thi và lấy kết quả
$orders = $this->orderRepo->all();
return response()->json($orders);
}
}
Tổng kết Series Repository Pattern
Nhờ sự kết hợp giữa Base Repository và Criteria Pattern, kiến trúc data của bạn giờ đây đã đạt cảnh giới tối thượng về khả năng mở rộng.
Ngày mai, nếu sếp yêu cầu thêm bộ lọc "Theo phương thức thanh toán", bạn không cần đụng vào Controller cũ, không cần sửa Base Repository. Bạn chỉ việc tạo thêm một file PaymentMethodCriteria.php và push nó vào Controller. Code cũ hoàn toàn không bị ảnh hưởng, zero bug phát sinh!
Hi vọng qua chuỗi 3 bài viết này, anh em đã bỏ túi được một vũ khí hạng nặng để thiết kế hệ thống Backend vững chãi hơn, sạch sẽ hơn và nhàn nhã hơn trong khâu bảo trì. Code sạch là code làm cho chúng ta ngủ ngon vào ban đêm!
All rights reserved