Vũ khí - thứ không thể thiếu khi ra trận. Có người thích đao, có người thích kiếm, có người lại thích dùng thương. Việc lựa chọn vũ khí phụ thuộc vào đối thủ, cách đánh, địa hình… chung quy lại là chọn loại phù hợp nhất. Điều này quyết định 70-80% tỷ lệ thắng.

Code cũng vậy. Vũ khí là những công cụ, những design pattern được cộng đồng công nhận giá trị. Nhưng không phải lúc nào chúng cũng phù hợp với dự án. Quan điểm của tôi: chỉ có vũ khí phù hợp chứ không có vũ khí tốt nhất cho mọi cuộc chiến. Nhiều khi vũ khí đã phù hợp nhưng cách dùng sai thì vẫn dở. Còn nếu chọn sai từ đầu thì càng dở hơn.

Tôi làm việc nhiều với PHP - ngôn ngữ càng làm càng thấy dở, nhưng nó phù hợp. Hôm nay có bài viết này vì thời gian trước tôi nâng cấp một hệ thống để tăng tốc độ. Khi muốn áp dụng cache, tôi gặp vấn đề lớn: đã dùng Eloquent ORM ở quá nhiều nơi khiến việc nâng cấp trở nên cực kỳ khó khăn. Đặc biệt khi muốn sửa một thứ, tôi phải tìm kiếm ở hàng chục file khác nhau - điều này gọi là tight coupling. Lúc này tôi nghĩ ngay tới Repository pattern, thứ mà trước đây tôi đọc và thấy không cần thiết, nhưng giờ lại thấy quá phù hợp.

Repository có gì gườm gà? Câu trả lời là rất nhiều. Thay vì viết đơn giản:

// Cách viết trực tiếp với ORM
$activeUsers = User::where('status', 'active')
                   ->where('created_at', '>', '2024-01-01')
                   ->orderBy('name')
                   ->get();

Bạn phải viết cả một đống code như thế này:

// Tạo interface
interface UserRepositoryInterface 
{
    public function getActiveUsers($fromDate = null);
}

// Implement repository
class UserRepository implements UserRepositoryInterface
{
    public function getActiveUsers($fromDate = null)
    {
        return User::where('status', 'active')
                   ->when($fromDate, function($query, $fromDate) {
                       return $query->where('created_at', '>', $fromDate);
                   })
                   ->orderBy('name')
                   ->get();
    }
}

// Inject vào controller  
class UserController 
{
    private $userRepository;
    
    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }
    
    public function index()
    {
        $users = $this->userRepository->getActiveUsers('2024-01-01');
        // ...
    }
}

Nhìn thì có vẻ thừa thãi, nhưng khi hệ thống lớn lên cần nâng cấp thì “búa tạ” lại trở nên nhẹ nhàng.

Lấy ví dụ từ trường hợp của tôi, khi muốn thêm cache vào hệ thống, tôi phải đi tìm từng chỗ gọi model để sửa. Nếu có repository từ đầu, tôi chỉ cần sửa một chỗ duy nhất. Điều này giống như việc bạn có một cái búa tạ - nặng nề khi mang theo nhưng khi cần phá tường thì không gì hiệu quả bằng.

Repository giúp việc báo trì dễ hơn

Repository tạo ra một lớp trung gian giữa business logic và cách lấy dữ liệu. Ngày mai muốn chuyển từ MySQL sang MongoDB? (Phần này tôi lấy ví dụ thôi chứ với riêng tôi chưa từng đổi database kiểu này :D) Chỉ cần thay đổi implementation, business logic vẫn nguyên vẹn. Muốn cache dữ liệu? Tạo một wrapper quanh repository gốc. Muốn test? Mock interface dễ hơn nhiều so với mock cả ORM framework.

Nhưng cộng đồng developer lại hay “chửi” repository pattern. Lý do cũng dễ hiểu, các ORM framework như Eloquent, Entity Framework, Hibernate… đã đủ mạnh rồi, tại sao phải wrap thêm một lớp nữa? Nhiều khi developer implement sai, tạo ra những “God Repository” chứa hàng trăm method, làm code càng thêm rối rắm. Với những dự án nhỏ, repository đúng là over-engineering.

Quan điểm của tôi vẫn là: không có vũ khí tốt nhất cho mọi cuộc chiến. Repository giống như cây búa tạ, nặng nề nhưng hiệu quả khi đúng lúc. Dự án nhỏ, làm prototype thì đừng dùng. Nhưng nếu bạn muốn phần bảo trì dễ dàng hơn, hoặc hệ thống phức tạp cần maintain lâu dài thì repository sẽ giúp bạn rất nhiều.

Điều quan trọng nhất là đừng áp dụng mù quáng. Hãy đánh giá dự án của bạn trước khi quyết định có nên “vác búa tạ” hay không.

Repository giúp việc báo trì dễ hơn

Bình luận

Đang tải bình luận...