Repository Design Pattern và ứng dụng của nó trong Laravel

Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.
Repository Design Pattern và ứng dụng của nó trong Laravel
Repository Design Pattern và ứng dụng của nó trong Laravel

Repository Design Pattern là gì?

Đây là một mẫu thiết kế nâng cao mà các bạn mới tiếp xúc lập trình có lẽ cũng không để ý về nó lắm. Đối với các bạn đã có kinh nghiệm thực tập hay làm việc ở các công ti - chắc hẳn cũng đã được nghe các mentor của mình nói về nó. 

Repository Design Pattern (mình sẽ tạm viết tắt nó thành RD) là một trong những mẫu thiết kế được sử dụng nhiều nhất trong hầu hết các ngôn ngữ lập trình, các framework... như .NET, Java, PHP..., trải dài từ websites, services, applications,... hay kể cả mobile apps. 

Repository Design Pattern

RD là một lớp trung gian giữa Business Logic (BL) và Data Source (DB), các đối tượng trong lớp trung gian này được gọi là Repository. Giao tiếp giữa BL và DB sẽ được thực hiện thông qua các Interface.

Chúng đem lại sự chuẩn hóa (standardized) cho output và tách biệt hoàn toàn việc xử lí business logic và data access logic, giúp cho BL hoàn toàn không cần quan tâm tới công việc của DB (và ngược lại). Việc chia để trị này hướng tới mục tiêu: ai làm việc nấy, điều đó cũng khiến code của bạn sáng sủa hơn, rõ ràng hơn, và dễ maintenance hơn.

Nói một ví dụ thực tế - trong một nhà máy may mặc, mỗi công nhân đa số đều được chia theo nhóm, và mỗi nhóm chỉ làm một phần nhỏ trong khâu sản xuất. Có nhóm thì chịu trách nhiệm may cổ áo, nhóm may tay áo, nhóm may thân áo, nhóm ráp các bộ phận này lại với nhau, nhóm chịu trách nhiệm hấp ủi... Một nhóm chỉ tập trung vào một công việc cụ thể chắc chắn sẽ nhanh và ít tạo ra sản phẩm lỗi hơn so với một người làm từ đầu đến cuối đúng không nào? ^^

 

Lợi ích của Repository Design Pattern

  • Code dễ phát triển và maintenance khi làm việc theo nhóm.
  • Giảm thiểu thay đổi code khi có thay đổi về cấu trúc dữ liệu, DB hoặc BL.
  • BL và DB có thể test độc lập
  • Chuẩn hóa đầu ra dữ liệu
  • Giảm thiểu trùng lặp code (DRY - Don't Repeat Yourself)

 

Cũng có lợi bất cập hại

  • Viết nhiều, viết mệt, cái gì cũng phải nghĩ đến tách rời và đem xuống Repository và tái sử dụng =))
  • Dự án nhỏ, mì ăn liền thì không cần xài cũng được
  • Với việc thế giới đang chuyển dần sang microservice thì việc áp dụng RD cho mỗi mắt nhỏ trong microservice khá là dư thừa và tốn nhiều chi phí phát triển

 

Repository Design Pattern và Laravel

Nãy giờ nói lan man quá, toàn là kiến thức khô khan =)), giờ mình xin được phép tiếp tục phần chính của bài viết.

Trong Laravel, Repository là "cây cầu dừa" nối giữa Model và Controller, đây cũng là nơi tập trung xử lí các logic truy vấn dữ liệu.

Các truy vấn này trước đây được thực hiện trực tiếp ở Controller bây giờ sẽ được đưa vào Repository, lúc này Controller sẽ tương tác với DB thông qua Repository thay vì gọi trực tiếp Model. Việc thực hiện truy vấn như thế nào sẽ được Repository giấu kín bên trong (và Controller bản thân nó cũng chẳng cần quan tâm, cứ trả đúng - đủ dữ liệu về cho nó là được rồi).

Điều này cũng giống như bạn ra ngân hàng rút tiền vậy. Bạn chỉ có thể gởi yêu cầu tới nhân viên ngân hàng, sau đó nhân viên ngân hàng kiểm tra và lấy tiền đưa cho bạn. Bạn thử tự xông vào lấy tiền xem sao, vô tù bóc lịch là có nha =))

Ủa rồi phần xử lí BL đâu rồi?

Không phải mình code cùi nên bỏ trực tiếp phần xử lí BL vào trong Controller như vậy đâu nha các bạn =)) 

Trên thực tế, một số thao tác get dữ liệu đơn giản sẽ được gọi trực tiếp ở Controller thông qua Repository.

Đối với các business phức tạp sẽ có thêm một tầng Service ở giữa nữa. Có nghĩa là lúc này, Controller chỉ có trách nhiệm điều hướng xử lí logic xuống Service, và Service mới là nơi thực hiện các BL và cập nhật xuống DB

Phần Service này mình sẽ nói rõ thêm với các bạn ở một bài viết khác, dù sao bài viết này cũng chỉ nói về RD thôi mà đúng không ^^

 

Triển khai Repository Design Pattern đơn giản cho Laravel

Khách hàng của chúng ta cần xây dựng một mạng xã hội cho phép các publishers chia sẻ các albums ảnh và kiếm tiền donate cũng như sự nổi tiếng.

Trước tiên chúng ta sẽ xây dựng một Model.

// app/Album.php namespace App;

use Illuminate\Database\Eloquent\Model;

class Album extends Model
{
    protected $guarded = [
        'id',
        'created_at',
        'updated_at',
    ];
}

Kế tiếp là Controller

// app/Http/Controllers/AlbumController.php

namespace App\Http\Controllers;

use App\Album;

class AlbumController extends Controller
{
    /**
     * Nội dung trang Albums List
     */
    public function index()
    {
        $albums = Album::all();

        return $albums;
    }

    /**
     * Nội dung trang Albums Details
     */
    public function show($id)
    {
        $album = Album::findOrFail($id);

        return $album;
    }
}

Trong Controller, Album được gọi trực tiếp để truy vấn dữ liệu. Mọi chuyện đều êm đẹp cho tới khi khách hàng muốn thay đổi cách truy vấn dữ liệu: các Album sẽ được sắp xếp theo độ tương tác, số lượng views, hoặc trang Album Details được truy vấn bằng hash_id thay vì id... Chắc chắn chúng ta sẽ cần phải cập nhật lại Controller để truy vấn dữ liệu cho phù hợp với requirements của khách hàng.

Điều này hết sức nguy hiểm và củ chuối. Bạn thử tưởng tượng không chỉ có mỗi AlbumController thực hiện các thao tác như thế này, mà rất nhiều Controller khác cũng thực hiện điều tương tự. Việc update code nhiều chỗ như vậy sẽ làm tăng khả năng bỏ sót hoặc thao tác sai lầm.

Và đây là lúc Repository lên sàn =))

Chúng ta sẽ tạo một Repository như sau

// app/Repositories/Eloquent/AlbumRepository.php

namespace App\Repositories\Eloquent;

use App\Album;

class AlbumRepository
{
    public function all()
    {
        return Album::orderBy('views_count', 'desc')->all();
    }

    public function find($id)
    {
        return Album::firstOrFail(['hash_id' => $id]);
    }
}

Cập nhật lại nội dung Controller

// app/Http/AlbumController.php

namespace App\Http\Controllers;

use App\Album;
use App\Repositories\Eloquent\AlbumRepository;

class AlbumController extends Controller
{
    protected $albumRepository;

    public function __construct(AlbumRepository $albumRepository)
    {
        $this->albumRepository = $albumRepository;
    }

    public function index()
    {
        $albums = $this->albumRepository->all();

        return $albums;
    }

    public function show($id)
    {
        $album = $this->albumRepository->find($id);

        return $album;
    }
}

Vậy là từ giờ trở đi, bạn cần thêm logic gì cứ chui vào Repository mà sửa, rõ ràng - sạch sẽ - khô thoáng - dễ hiểu phải không nào ^^

 

Câu chuyện vẫn chưa tới hồi kết

Vào một ngày nọ, khách hàng của chúng ta nghe phong phanh đâu đó bảo rằng dữ liệu của website mình hầu như người ta chỉ có xem là chính, không cần cập nhật gì nhiều cả. Kết thúc chương trình, ông khách hàng yêu cầu chúng ta đọc dữ liệu lên từ cache thay vì truy cập DB như hiện tại. 

Giờ chúng ta phải làm sao? Sửa lại các hàm trong AlbumRepository chăng?

Sai. Chúng ta sẽ tạo ra một repository khác chịu trách nhiệm xử lí caching cho AlbumRepository.

Ở đây mình sẽ áp dụng một mẫu thiết kế khác, đó chính là Decorator Pattern. Mẫu thiết kế này giúp chúng ta thêm các tính năng mới mà không cần phải cập nhật lại các lớp hiện tại (lớp ở đây chính là AlbumRepository).

// app/Repositories/Cache/AlbumRepositoryCacheDecorator.php

namespace App\Repositories\Cache;

use App\Repositories\Eloquent\AlbumRepository;;

class AlbumRepositoryCacheDecorator
{
    protected $repository;

    public function __construct()
    {
        $this->repository = new AlbumRepository();
    }

    public function all()
    {
        /*If cache exists, get data from cache*/
        if ('has-cache') {
            return 'data-from-cache';
        }

        $albums = $this->repository->all();

        /*Logic to store cache*/

        return $albums;
    }

    public function find($id)
    {
        /*If cache exists, get data from cache*/
        if ('has-cache') {
            return 'data-from-cache';
        }

        $album = $this->repository->find($id);

        /*Logic to store cache*/

        return $album;
    }

    public function update($id, array $data)
    {
        $this->repository->update($id, $data);

        /*Logic to clear cache*/
    }
}

Sau đó chúng ta cần import AlbumRepositoryCacheDecorator thay vì AlbumRepository

// app/Http/AlbumController.php

namespace App\Http\Controllers;

use App\Repositories\Cache\AlbumRepositoryCacheDecorator;

class AlbumController extends Controller
{
    protected $albumRepository;

    public function __construct(AlbumRepositoryCacheDecorator $albumRepository)
    {
        $this->albumRepository = $albumRepository;
    }

    public function index()
    {
        $albums = $this->albumRepository->all();

        return $albums;
    }

    public function show($id)
    {
        $album = $this->albumRepository->find($id);

        return $album;
    }
}

Các bạn cần chú ý sự thay đổi ở đây: chúng ta đã thay đổi thứ được inject vào __construct

Củ chuối lắm các bạn à. Bởi AlbumRepository không chỉ được sử dụng ở AlbumController như ví dụ trên, nó còn có thể được sử dụng ở hàng tá chỗ khác, nếu như chúng ta cập nhật một cách thủ công như vậy sẽ có thể dẫn đến nhiều lỗi không muốn, và khiến code của chúng ta lặp đi lặp lại nhiều lần.

Với sự trợ giúp của Laravel Service Container, chúng ta có thể bind một interface tới một class nhất định. 

Đầu tiên chúng ta sẽ tạo ra một interface như sau

// app/Repositories/Contracts/AlbumRepositoryContract.php

namespace App\Repositories\Contracts;

interface AlbumRepositoryContract
{
    public function all();

    public function find($id);
}

Sau đó chúng ta cần chỉnh sửa nội dung cho hai lớp AlbumRepository và AlbumRepositoryCacheDecorator sao cho chúng implements AlbumRepositoryContract trên.

use App\Repositories\Contracts\AlbumRepositoryContract;

class AlbumRepository implements AlbumRepositoryContract {}

class AlbumRepositoryCacheDecorator implements AlbumRepositoryContract {}

Bước kế tiếp quan trọng nhất: chúng ta cần khai báo cho Laravel biết cách xử lí khi chúng ta gọi interface binding. Chúng ta sẽ cập nhật nội dung phương thức register bên trong tập tin app/Providers/AppServiceProvider.php.

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\Contracts\AlbumRepositoryContract;
use App\Repositories\Cache\AlbumRepositoryCacheDecorator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(AlbumRepositoryContract::class, AlbumRepositoryCacheDecorator::class);
    }
}

Việc cuối cùng chúng ta cần làm là cập nhật lại file AlbumController một chút

namespace App\Http\Controllers;

use App\Repositories\Contracts\AlbumRepositoryContract;

class AlbumController extends Controller
{
    protected $albumRepository;

    public function __construct(AlbumRepositoryContract $albumRepository)
    {
        $this->albumRepository = $albumRepository;
    }

    ...
}

Boom! Mọi thứ tới đây đã khá ổn rồi. Nếu như khách hàng có đổi ý, không muốn sử dụng cache nữa, chúng ta chỉ việc cập nhật lại AppServiceProvider. Khá là đơn giản đúng không nào ^^

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\Contracts\AlbumRepositoryContract;
use App\Repositories\Eloquent\AlbumRepository;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(AlbumRepositoryContract::class, AlbumRepository::class);
    }
}

Thậm chí sau này, nếu như khách hàng muốn lưu các albums ở một database khác, chúng ta cũng sẽ không cần cập nhật lại các logic nghiệp vụ ở AlbumController nữa, mà sẽ tạo ra các Repository liên quan và bind chúng lại ở AppServiceProvider.

 

Bài viết đến đây là kết thúc rồi. Mình hi vọng bài viết này sẽ giúp các bạn hiểu rõ hơn về RD cũng như ứng dụng nó vào Laravel.

Nếu bài viết có nội dung nào đó không chính xác, hoặc các bạn có câu hỏi muốn hỏi mình, hãy để lại comments bên dưới hoặc liên lạc với mình nhé.

Cám ơn các bạn đã theo dõi. Hẹn gặp lại các bạn trong các bài viết vọc vạch Laravel kế tiếp ^^

 

Comments

Mục lục

Bài viết nổi bật

PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Ở bài viết này mình sẽ hướng dẫn bạn bắt đầu xây dựng một ứng dụng HMVC với Laravel, và tận dụng sức mạnh của Composer khi quản lí modules.
Có khá nhiều bạn đã yêu cầu mình một bài viết về Repository Design Pattern. Vậy mục đích của nó là gì? Nó có thực sự cần thiết cho ứng dụng của bạn hay không? Những điểm mạnh, điểm yếu của nó là gì? Chúng ta cùng đi sâu tìm hiểu qua bài viết này nhé.
Trong quá trình làm việc với Laravel Eloquent ORM, chắc hẳn các bạn từng thực hiện khá nhiều tác vụ lặp đi lặp lại - mà bạn không hề biết Laravel đã hỗ trợ sẵn. Thông qua vài mẹo và thủ thuật nhỏ trong bài viết này, mình hi vọng sẽ giúp các bạn giảm bớt sự phức tạp khi viết code cũng như bớt nhàm chán khi thực hiện các tác vụ lặp đi lặp lại theo cách thông thường.
Như đã được thông báo vào năm 2018, React Native đã và đang được đội ngũ phát triển của Facebook nỗ lực tái kiến trúc nhằm giúp nền tảng này trở nên mạnh mẽ hơn và giải quyết một số vấn đề phổ biến mà các nhà phát triển đã gặp phải trong vài năm qua.

Related posts

Redux là một framework quản lý state dựa trên ý tưởng: tất cả các state của ứng dụng sẽ được lưu xuống một global state và sử dụng các reducer function để tương tác với chúng.
Styled-Components là một thư viện giúp bạn tổ chức và quản lý styles trong các dự án sử dụng React tuân theo đề xuất từ Facebook.
Decorators trong Typescript
1106
Các bạn dev nào từng làm Angular chắc sẽ khá quen thuộc với Decorators đúng không? Nhưng bạn thật sự đã hiểu rõ về nó hay chưa? Chúng ta hãy cùng nhau ôn bài một tí nhé ^_^
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Là developer, chúng ta luôn tìm cách để tiết kiệm thời gian cho các dự án của mình. Đó là lý do tại sao các thư viện được tạo ra để giảm bớt các khó khăn khi triển khai những thứ lặp đi lặp lại. Với các frontend framework như React, việc chia sẻ chức năng chung cho các dự án khác nhau trở nên dễ dàng hơn bao giờ hết.
Snappy là một thư viện PHP cho phép tạo các thumnails, snapshots và PDF từ URL hoặc trang HTML. Tác giả Barryvdh đã viết thư viện Laravel Snappy giúp việc tích hợp với Laravel dễ dàng hơn.
Bạn có thể sử dụng tính năng kiểm tra kiểu dữ liệu của Typescript để xác định các interface cho các components, nhằm chắc chắn rằng component của bạn chỉ nhận được các thông tin đầu vào chính xác.
Bài viết này cung cấp một cái nhìn tổng quan ngắn gọn về TypeScript, tập trung vào hệ thống kiểu dữ liệu của nó.
Hooks là một tính năng bổ sung mới từ React 16.8 trở đi. Chúng cho phép bạn sử dụng State và các tính năng khác của React mà không cần viết một class.
Giới thiệu series react
601
Một vài kiến thức cơ bản cũng như nâng cao mà mình sẽ tổng hợp lại trong quá trình làm việc cũng như self learning
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Flexbox toàn tập
1429
Bài viết này sẽ hướng dẫn cho bạn mọi thứ về flexbox, bao gồm tất cả các thuộc tính cho phần tử cha (flex container) và phần tử con (flex items).

Tin mới nhất

Redux là một framework quản lý state dựa trên ý tưởng: tất cả các state của ứng dụng sẽ được lưu xuống một global state và sử dụng các reducer function để tương tác với chúng.
Styled-Components là một thư viện giúp bạn tổ chức và quản lý styles trong các dự án sử dụng React tuân theo đề xuất từ Facebook.
Decorators trong Typescript
1106
Các bạn dev nào từng làm Angular chắc sẽ khá quen thuộc với Decorators đúng không? Nhưng bạn thật sự đã hiểu rõ về nó hay chưa? Chúng ta hãy cùng nhau ôn bài một tí nhé ^_^
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Là developer, chúng ta luôn tìm cách để tiết kiệm thời gian cho các dự án của mình. Đó là lý do tại sao các thư viện được tạo ra để giảm bớt các khó khăn khi triển khai những thứ lặp đi lặp lại. Với các frontend framework như React, việc chia sẻ chức năng chung cho các dự án khác nhau trở nên dễ dàng hơn bao giờ hết.
Snappy là một thư viện PHP cho phép tạo các thumnails, snapshots và PDF từ URL hoặc trang HTML. Tác giả Barryvdh đã viết thư viện Laravel Snappy giúp việc tích hợp với Laravel dễ dàng hơn.
IRAC (Issue - Rule - Analysis - Conclusion) là một phương pháp phổ biến và quen thuộc với sinh viên luật và dân luật nói chung. Cá nhân mình thấy phương pháp này khá hay và hoàn toàn có thể áp dụng vào bất cứ công việc hoặc ngành nghề nào.
Bạn có thể sử dụng tính năng kiểm tra kiểu dữ liệu của Typescript để xác định các interface cho các components, nhằm chắc chắn rằng component của bạn chỉ nhận được các thông tin đầu vào chính xác.
Bài viết này cung cấp một cái nhìn tổng quan ngắn gọn về TypeScript, tập trung vào hệ thống kiểu dữ liệu của nó.
Hooks là một tính năng bổ sung mới từ React 16.8 trở đi. Chúng cho phép bạn sử dụng State và các tính năng khác của React mà không cần viết một class.
Giới thiệu series react
601
Một vài kiến thức cơ bản cũng như nâng cao mà mình sẽ tổng hợp lại trong quá trình làm việc cũng như self learning
JWT Tokens là một cách thức lưu trữ thông tin xác thực hiệu quả, nhưng làm cách nào để chúng ta có thể giúp chúng an toàn hơn? Có 2 cách thường dùng để lưu trữ JWT Tokens là LocalStorage và Cookies. Bây giờ chúng ta sẽ bắt đầu "mổ xẻ" các ưu - nhược điểm của mỗi loại nhé.
Redux là một framework quản lý state dựa trên ý tưởng: tất cả các state của ứng dụng sẽ được lưu xuống một global state và sử dụng các reducer function để tương tác với chúng.
Styled-Components là một thư viện giúp bạn tổ chức và quản lý styles trong các dự án sử dụng React tuân theo đề xuất từ Facebook.
Decorators trong Typescript
1106
Các bạn dev nào từng làm Angular chắc sẽ khá quen thuộc với Decorators đúng không? Nhưng bạn thật sự đã hiểu rõ về nó hay chưa? Chúng ta hãy cùng nhau ôn bài một tí nhé ^_^
PHP là ngôn ngữ được sử dụng rộng rãi nhất trên thế giới trong lập trình web. Nó cũng bị ghét nhất. Nhưng tại sao nhiều developer lại ghét nó đến vậy? Hôm nay chúng ta hãy cùng tìm hiểu lý do xem chúng có thuyết phục không nhé ^_^
Là developer, chúng ta luôn tìm cách để tiết kiệm thời gian cho các dự án của mình. Đó là lý do tại sao các thư viện được tạo ra để giảm bớt các khó khăn khi triển khai những thứ lặp đi lặp lại. Với các frontend framework như React, việc chia sẻ chức năng chung cho các dự án khác nhau trở nên dễ dàng hơn bao giờ hết.