Design Pattern là kỹ thuật lập trình cung cấp cho chúng ta các mẫu thiết kế để áp dụng vào các trường hợp cụ thể để giải quyết các bài toán dễ dàng hơn. Các mẫu thiết kế này không phụ thuộc vào ngôn ngữ lập trình, vấn đề là bạn hiểu nguyên lý và áp dụng nó vào code mà thôi. Repository Pattern là một mẫu thiết kế trong design pattern, và trong bài viết này mình sẽ giới thiệu mẫu thiết kế này với các bạn trong Laravel Framework.

Repository Pattern hoạt động như thế nào?

Repository Pattern
  • Repository Pattern là lớp trung gian giữa tầng Business LogicData Access, giúp cho việc truy cập dữ liệu chặt chẽ và bảo mật hơn.
  • Repository đóng vai trò là một lớp kết nối giữa tầng BusinessModel của ứng dụng.
  • Các phần truy xuất, giao tiếp với database năm rải rác ở trong code, khi bạn muốn thực hiện một thao tác lên database thì phải tìm trong code cũng như tìm các thuộc tính trong bảng để xử lý. Điều này gây lãng phí thời gian và công sức rất nhiều, vì thế với Repository design pattern, thì việc thay đổi ở code sẽ không ảnh hưởng quá nhiều công sức chúng ra chỉnh sửa.
  • Những lý do ta nên sử dụng mẫu Repository Pattern:
    • Thay đổi quyền truy cập dữ liệu cũng như xử lý dữ liệu chỉ ở một nơi nhất định.
    • Việc chịu trách nhiệm cho việc mapping các bảng vào object cùng ở một nơi nhất định.
    • Tăng tính bảo mật và rõ ràng cho code.
    • Rất dễ dàng để thay thế một Repository với một implementation giả cho việc testing, vì vậy bạn không cần chuẩn bị một cơ sở dữ liệu có sẵn.

Các ví dụ cụ thể

Giả sự bây giờ chúng ta sẽ có một bảng là posts (với các cột cơ bản là title, content, is_published) và model như sau:

Và bây giờ chúng ta sẽ có các trường hợp cụ thể về cách thiết lặp các Controller, ở mỗi ví dụ mình sẽ chỉ ra 4 action đơn giản đó là:

  • index để lấy tất cả các post
  • show để lấy một bài post bất kỳ
  • store để tạo bài viết mới
  • update để cập nhật bài viết
  • destroy để xóa bài viết

Controller không áp dung Repository Pattern

Với trường hợp không áp dụng Repository Pattern, chúng ta phải viết code điều hướng controller và xử lý lấy dữ liệu từ Database chung một chỗ, điều này rất khó để kiểm soát và có thể phải viết đi viết lại code nhiều lần (ở User Pannel, ở Admin Pannel chẳng hạn). Chúng sẽ như thế này:

Ở ví dụ trên, ta thấy mỗi lần lấy một bài viết bất kỳ ta đều dựa trên Model, bây giờ lỡ như khách hàng yêu cầu chỉ lấy bài Post mà được public thôi thì sao? ta phải sửa tất cả các Action trên !?

Controller áp dung Repository Pattern

Để giải quyết ví dụ trên, ta sẽ áp dụng mẫu Repository vào, cụ thể ta sẽ tạo class trung gian giữa controller và model để giải quyết việc giao tiếp cơ sở dữ liệu tại một nơi. Ờ mà khoan, suy nghĩ chút… giả sử chúng ta làm 1 class Repository rồi mà dùng Eloquent đột ngột khách dở chứng đòi chuyển sang Redis hay MongoDB thì sao?! Bây giờ phải sửa toàn bộ repository sao, rồi mỗi lần đòi chuyển ta ta phải ngồi mò và viết lại tất cả sao.

Trên thực tế có khá nhiều trường hợp éo le hơn như thế, mình có một giải pháp như thế này:

  • Tạo ra một thư mục Repositories trong thư mục app để quản lý các Repository.
  • Đầu tiên ta sẽ tạo một interface  mang tên RepositoryInterface chung cho các mẫu repository bắt buộc các repository theo một chuẩn chung;
  • Tiếp theo, ta sẽ xây dựng một abstract class tên là EloquentRepository cho driver Eloquent implements từ RepositoryInterface đưa ra các phương thức cơ bản bắt buộc Repository nào cũng phải có (sẽ có nhiều class driver khác nhau để lấy cơ sở dữ liệu như MongoDB, Redis, AWS, v.v..).
  • Bây giờ, tạo một mẫu Post\PostRepositoryInterface để định nghĩa các phương thức chỉ có trong Post Repository.
  • Tiếp tục, tạo một class Post Repository thuộc driver Eloquent Post\PostEloquentRepository kế thừa từ class EloquentRepositoryimplements từ Post\PostRepositoryInterface.
  • Bây giờ ta chỉ việc inject mẫu PostRepositoryInterface bind PostEloquentRepository trong AppServiceProvider là xong, bây giờ ta có thể gọi nó trong controller để xử dụng rồi (đây là kỹ thuật Dependency Injection, nếu bạn chưa biết hãy xem ví dụ bên dưới hoặc đọc tài liệu chính thức của Laravel tại https://laravel.com/docs/master/container).

Quy trình là như thế bây giờ ta sẽ thực hiện từ bước một. Ta xem qua cấu trúc file mà ta sẽ tạo trước đã:

Cấu trúc thư mục Repositories trong Laravel

Đầu tiên, ta sẽ tạo RepositoryInterface trước:

Tiếp theo là abstract class EloquentRepository

Sau khi có abstract class rồi, bây giờ mới tiến hành tạo interface cho Post, ngoài các phương thức bắt buộc phải có thì Post cần có thêm method findOnlyPublished và getAllPublished để lọc các bài đã đăng thôi (ví dụ vậy), nó sẽ như thế này:

Rồi, bây giờ ta tạo cái Repository Eloquent cho thằng Post thôi, nên nhớ nó sẽ kế thừa từ EloquentRepository và implements từ PostRepositorty :

Yeah, hoàn tất phần tạo repository cho Post rồi, bây giờ ta inject nó. Các bán sẽ mở tập tin /app/Providers/AppServiceProvider.php và thêm vào method register() như sau:

Ở trên, mỗi lần ta muốn đổi driver ví dụ từ Eloquent sang Redis, hay Mongo chỉ việc thay đổi \App\Repositories\Post\PostEloquentRepository::class tương ứng, quá đơn giản phải không nào, bây giờ công việc cuối cùng là Inject vào controller và sử dụng thôi:

Từ bây giờ bạn có thể lấy dữ liệu, cập nhật dữ liệu thông qua repository mà không cần biết bên trong nó làm gì, như thế sẽ khiến code của chúng ta vừa gọn ràng, vừa dễ quản lý, bảo trì dễ dàng và tăng tính bảo mật nữa.

Lời kết

Việc áp dụng các mẫu thiết kế Design Patterns sẽ giúp các bạn tiết kiệm thời gian đồng thời cũng tăng hiệu suất, chất lượng code. Như mình đã nói ở trên, các mẫu thiết kế này không ràng buộc bởi ngôn ngữ lập trình, chỉ cần nắm rõ “mẫu thiết kế” thì bạn có thể xây dựng ở bất cứ ngôn ngữ nào, mình chỉ lấy Laravel để dễ diễn đạt thôi.

Chúc các bạn thành công.

Tham khảo thêm:

2 COMMENTS

  1. Rất cảm ơn bạn bài viết rất hay ,rất mong sẽ có nhiều bài viết hơn ạ .
    p/s : trong quá trình theo dõi bài viết mình thấy hình như trong phần khởi tạo abstract class EloquentRepository phần setModel() bạn thiếu return thì phải bạn thử xem lại xem nhé .

LEAVE A REPLY

Please enter your comment!
Please enter your name here