+15

Kiến trúc hệ thống trên Laravel – phần 4

Các bài viết trong series

Kiến trúc hệ thống trên Laravel – phần 1 : Tại sao phải áp dụng architect vào trong Laravel Kiến trúc hệ thống trên Laravel – phần 2 : OOP, Interface, Dependency Injection, IoC Kiến trúc hệ thống trên Laravel – phần 3 : Phân tích sâu vào việc sử dụng interface Kiến trúc hệ thống trên Laravel – phần 4 : Design Pattern – Decorator Kiến trúc hệ thống trên Laravel – phần 5 : Design Pattern – Adapter Kiến trúc hệ thống trên Laravel – phần 6 : Design Pattern – Repository Kiến trúc hệ thống trên Laravel – phần 7 : Design Pattern – Factory Kiến trúc hệ thống trên Laravel – phần 8 : Advance component trong Laravel Kiến trúc hệ thống trên Laravel – phần 9 : Mô hình kiến trúc cụ thể Part 1 Kiến trúc hệ thống trên Laravel – phần 10 : Mô hình kiến trúc cụ thể Part 2

Xin chào các bạn. Mình vào nghề lập trình cũng đã lâu, cũng có 1 số hiểu biết coi như là nâng cao về framework Laravel. Nên hôm nay mình xin chia sẻ 1 chút về kiến trúc hệ thống của mình được xây dựng trên Laravel như thế nào. Mong rằng có thể giúp ích cho các bạn 😃.


Hi, xin lỗi mọi người, dạo này mình bận quá, thỉnh thoảng lại mới có thời gian rảnh rỗi mới viết được bài mới. Hôm nay mình sẽ đi vào 1 cái gọi là design pattern. Vì mình không có nhiều thời gian lắm nên mình sẽ tranh thủ viết nhanh chứ không kỹ như các bài trước.

OK, nào, chúng ta vào design pattern.

Nghe nó có vẻ oách vậy thôi chứ thực tế design pattern là các bước viết code (kiểu như làm mẫu ý, có từng bước phải làm thế này thế kia) để giải quyết những vấn đề mà lập trình viên hay gặp phải. Vậy học design pattern là hiểu vấn đề gặp phải là như thế nào rồi quay lại cách giải quyết ra sao. Hôm nay chúng ta sẽ đi vào mẫu decorator. Thực tế mình chọn mẫu này trước vì nó có liên quan tới bài trước.

1. Vấn đề gặp phải

Nhắc lại bài trước nhé.

Bạn cần gửi email

class EmailOnly implements Notify
{
    protected $mailer;
 
    public function __construct(MailAdapter $mailer)
    {
        $this->mailer = $mailer;
    }
 
    public function send($subject, $template, $data)
    {
        $this->mailer->send($subject, $template, $data);
    }
}

Bạn cần gửi email + sms

<?php

class MailAndSms implements Notify
{
    protected $mailer;
    protected $sms;

    public function __construct(MailAdapter $mailer,SmsAdapter $sms)
    {
        $this->mailer = $mailer;
        $this->sms = $sms;
    }

    public function send($subject, $template, $data)
    {
        $this->mailer->send($subject, $template, $data);
        $this->sms->send($subject, $template, $data);
    }
}

Thế bạn cần gửi email + slack thì làm sao -> tất nhiên phải viết 1 class mới và inject MailAdapter + SlackAdapter vào __construct() rồi.

Bạn cần gửi email + sms + slack

class MailAndSmsAndSlack implements Notify
{
    protected $mailer;
    protected $sms;
    protected $slack;
 
    public function __construct(MailAdapter $mailer,SmsAdapter $sms,SlackAdapter $slack)
    {
        $this->mailer = $mailer;
        $this->sms = $sms;
        $this->slack = $slack;
    }
 
    public function send($subject, $template, $data)
    {
        $this->mailer->send($subject, $template, $data);
        $this->sms->send($subject, $template, $data);
        $this->slack->send($subject, $template, $data);
    }
}

Bạn thấy vấn đề chưa, cứ mỗi lần add up thêm service thì bạn lại phải thêm 1 cái dependency để inject vào -> cứ với chiều hướng này thì bạn sẽ inject quá nhiều biến vào __construct. Ngoài ra khi bạn ghép các dịch vụ với nhau, bạn lại cần phải viết 1 class mới (ví dụ như MailAndSlack, SmsAndSlack …) -> cực kỳ khó maintain 😃.

Vậy làm sao chỉ inject tối đa 2 biến vào __construct mà muốn add thêm bao nhiêu service nữa cũng được 😀

2. Cách giải quyết

Chúng ta sẽ giữ nguyên class EmailOnly với việc mặc định luôn luôn có việc gửi email ngay cả khi thêm các service khác vào

Và chúng ta viết lại hàm gửi SMS để có thêm thêm vào việc gửi Email

<?php

class SmsAddOn implements Notify
{
    protected $sms;
    protected $notifier

    public function __construct(Notify $notifier,SmsAdapter $sms)
    {
        $this->notifier = $notifier;
        $this->sms = $sms;
    }

    public function send($subject, $template, $data)
    {
        $this->notifier->send($subject, $template, $data);
        $this->sms->send($subject, $template, $data);
    }
}

Bạn hãy so sánh class SmsAddOn với class MailAndSms ở trên, gần như giống nhau hoàn toàn trừ việc mình sử dụng interface Notify để inject vào class SmsAddOn chứ không inject dịch vụ cụ thể MailAdapter nữa -> hiện tại mình đã chuyển sang coding to interface rồi đó 😀

Ok, tương tự với việc bạn muốn add thêm dịch vụ slack thì bạn viết thế nào.

class SlackAddOn implements Notify
{
    protected $slack;
    protected $notifier

    public function __construct(Notify $notifier,SlackAdapter $slack)
    {
        $this->notifier = $notifier;
        $this->slack = $slack;
    }

    public function send($subject, $template, $data)
    {
        $this->notifier->send($subject, $template, $data);
        $this->slack->send($subject, $template, $data);
    }
}

Thế muốn thêm dịch vụ mới, chẳng hạn như Skype chẳng hạn, thì làm thế nào. Giống hệt như trên thôi

class SkypeAddOn implements Notify
{
    protected $skype;
    protected $notifier

    public function __construct(Notify $notifier,SkypeAdapter $skype)
    {
        $this->notifier = $notifier;
        $this->skype = $skype;
    }

    public function send($subject, $template, $data)
    {
        $this->notifier->send($subject, $template, $data);
        $this->skype->send($subject, $template, $data);
    }
}

Ok, thế ở nếu muốn sử dụng Sms kèm theo theo Email thì chúng ta làm thế nào. Thay vì viết hàm EmailAndSms như trên rồi binding interface thì chúng ta chỉ cần sửa lại phần binding thôi

use Illuminate\Support\ServiceProvider;
 
class NotifyServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('MailAdapter', function ($app) {
            return new LaravelMailer();
        });
        $this->app->singleton('SmsAdapter', function ($app) {
            return new Sms();
        });
        $this->app->singleton('SlackAdapter', function ($app) {
            return new Slack();
        });
        $this->app->singleton('SkypeAdapter', function ($app) {
            return new Skype();
        });
        $this->app->singleton('Notify', function ($app) {
            // return new EmailOnly(); //Nếu muốn gửi email thôi thì uncomment dòng này và comment dòng dưới
            // return new MailAndSms(); // Làm theo cách cũ
            return new SmsAddOn(new EmailOnly()); // EmailAndSms
            /* return new SlackAddOn(new EmailOnly()); //EmailAndSlack */
            /* return new SkypeAddOn(new EmailOnly()); //EmailAndSkype */
            /* return new SlackAddOn(new SmsAddOn(new EmailOnly())); // EmailAndSmsAndSlack */
            /* return new SkypeAddOn(new SlackAddOn(new SmsAddOn(new EmailOnly()))); // EmailAndSmsAndSlackAndSkype */
        });
    }
}

Mình cũng có comment nếu add thêm các dịch vụ ở dưới thì chỉ cần thay đổi phần bindding là xong. Các bạn hãy đọc kỹ phần code ở trên nhé 😃.

Các bạn thấy đấy với cách viết này thì việc thêm dịch vụ mới chỉ là các bạn viết thêm class AddOn, rồi sau đó ghép các AddOn vào với nhau ở phần binding là xong -> bạn hoàn toàn không cần viết các biến thể cụ thể về việc ghép các dịch vụ với nhau (ví dụ như hoàn toàn không cần viết EmailAndSlack, EmailAndSkype, EmailAndSmsAndSlack …)

Rồi, bài này đến đây là hết. Toàn code là code đọc thích mắt nhỉ 😄. Xin cám ơn ^^.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí