Kiến trúc hệ thống trên Laravel – phần 4
Bài đăng này đã không được cập nhật trong 8 năm
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
Bình luận
Cảm ơn ad đã chia sẻ bài viết rất hữu ích. Tuy nhiên ad verify giúp 1 chỗ code nhé: "return new SmsAddOn(new EmailOnly());", mình nghi ngờ là đoạn này sẽ không chạy được, vì ở đây mình manually tạo một instance của SmsAddOn, mà hàm constructor của class này có 2 arguments là Notify & SmsAdapter, nhưng dòng code ở trên mình chỉ truyền một đối số là instance của Notify interface thôi nên sẽ bị lỗi (ở đây là do mình thủ công dùng new SmsAddOn(...) nên IoC nó ko involve vào được).
Bạn có thể paste lại code bạn tạo install của SmsAddOn được không? Để rảnh mình code lại rồi đẩy lên github
À, mình đọc bài post của ad, đến đoạn đó thì mình nghi ngờ là nó ko chạy được nên trao đổi với ad xem thế nào, chứ mình ko gõ code để test nó
.
À uh, để IoC nó hoạt động được thì bạn phải inject nó vào trong controller, job ... https://laravel.com/docs/5.5/container#automatic-injection
hoặc bạn phải dùng app()->make() nếu sử dụng trong các chỗ khác nhé.
Nếu bạn tạo bằng tay như bình thường thì hệ thống IoC của Laravel ko biết mà tạo giúp bạn đâu
cảm ơn anh bài viết rất rất hay ạ
Thank bạn, series ra đời cũng lâu lắm rồi mà vẫn còn bạn đọc. Chứng tỏ nó cũng đã ít nhiều giúp ích cho các bạn nhỉ
chào ad, code trên ad đã test chưa, khi mình đọc thì bị ngợ ở class NotifyServiceProvider , những cái này hình như không đúng , mong ad xem lại $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 */ });
ad ơi, bài viết hay mà có chỗ này mình chưa hiểu ( return new SlackAddOn(new EmailOnly()); )
sao lại tạo 1 đối tượng SlackAddOn và khởi tạo đối tượng EmailOnly trong nó ntn ?? ad giải thích giúp mình nhé
17/06/2020 ^^