Kiến trúc hệ thống trên Laravel – phần 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 :).

Đây sẽ là 1 series gồm 1 số phần như sau:

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

Hi các bạn, hi vọng với phần 1, mình đã viết đủ sức thuyết phục để các bạn tiếp tục đồng hành cùng mình trong series này ^^. Nhắc qua phần trước, hi vọng các bạn đã có chung 1 cách nhìn với mình: muốn hệ thống linh hoạt thì phải đặt trong khuôn khổ ^_^. Tức là các component trong hệ thống phải có quan hệ lỏng lẻo và có kết nối với nhau thông qua các chuẩn được quy định sẵn. Chuẩn kết nối ở đây là OOP interface và chúng ta sẽ làm rõ điều này trong khuôn khổ bài viết này.

Mình sẽ viết mang tính định hướng key note cho các bạn là chủ yếu, về chuyên sâu thì mong rằng cả nhà có thể search google để tự ngâm cứu nhé. Các bạn phải chủ động tự tìm hiểu thì mới thấm sâu được :D. Bài viết sẽ rất là dài vì mình muốn các bạn có thể theo dõi được liền mạch 1 mảng kiến thức luôn. Hi vọng sẽ vẫn có bạn follow được đến cuối bài :).

*) OOP

Với tư tưởng lấy thực tế để mô phỏng trong thế giới lập trình. Mình cũng xin phép lấy 1 ví dụ để mọi người có thể hiểu rõ hơn 1 chút về hướng đối tượng (OOP) trong lập trình.

Ok, ví dụ thì rất đơn giản

 

Bạn muốn gửi thư, bưu kiện thì bạn phải ra bưu cục ở Việt Nam gửi đúng ko? Lúc đó bạn sẽ ký với 
bưu cục 1 cái biên lai / hợp đồng gì đó và bưu cục sẽ nhận tiền + thư/bưu kiện của bạn và điều hành 
nội bộ phía trong để gửi tới đúng địa chỉ. Nói riêng ở trong bưu cục sẽ phân chia làm các cấp, 
từ cấp trung ương tới cấp tỉnh, rồi cấp huyện, làng xã ... 
Bưu cục phía trên sẽ ép 1 số điều để bắt bưu cục cấp dưới phải làm theo: ví dụ như quy trình 
tuyển dụng, quy trình hoạt động. Ông không theo thì tôi cho out :))

 

Mô tả điều này vào trong thế giới lập trình thì ta thấy khá dễ dàng rằng nhưng đối tượng / thực thể trong hệ thống sẽ được tạo thành những class ^^ (ví dụ như bạn -> với hệ thống sẽ là class Customerbưu cục sẽ là class PostOfficethư sẽ là class Mailbưu kiện sẽ là class Package). Bạn sẽ chỉ được nhận những thông tin mà bưu cục cho phép bạn biết, đó là những thông tin được public. Ngoài ra những thông tin nội bộ trong bưu cục thì bạn sẽ không được biết, đó là những thông tin private. Nhưng trong nội bộ các bưu cục thì sẽ có những thông tin / điều khoản mà bưu cục cha áp xuống bưu cục con -> đây là tính kế thừa và những thông tin này gọi là thông tin protected. Bạn thấy đấy, việc áp các access modifier (public / private / protected) là điều hoàn toàn tự nhiên tùy theo mô tả của bài toàn.

Nói đến OOP là phải nói đến 4 tính chất cơ bản (nhưng lại cực kỳ quan trọng) làm lên tiếng vang của OOP:

  • Tính đóng gói (encapsulation)
  • Tính trừu tượng (abstraction)
  • Tính đa hình (polymorphism)
  • Tính kế thừa (inheritance)

Trong đó tính đa hình theo bản thân mình là tính chất quan trọng nhất của OOP :). Bạn cũng có thể thấy ở ví dụ phía trên có biểu diễn tính đa hình rồi đấy. Đố bạn nào chỉ ra cho mình được :)).

 

Khi coi bưu cục là 1 đối tượng thì bưu cục ở đây được biến hóa cực kỳ huyền ảo, có thể là 
bưu cục trung ương (văn phòng đẹp, hoành tráng, chỉ tiếp các sếp lớn, ko tiếp các thường dần >.<), 
có thể là bưu cục tỉnh (văn phòng vẫn đẹp, hướng tới việc tiếp các doánh nghiệp là chủ yếu), 
hay là bưu cục làng, xã (văn phòng chưa được đẹp lắm vì chỉ cần tiếp người dân thôi mà).

 

–> Đa hình chưa cả nhà :D.

Đa hình là nguồn cội, gốc gác để sinh ra interface và chúng ta sẽ tìm hiểu ở phía dưới đây

*) Interface

Vậy inteface là cái chi mà mình cứ bảo nó là chuẩn kết nối :).

 

Hãy quay lại ví dụ trên, bạn kết nối với bưu cục bằng hợp đồng (có thể là biên lai với 
khách hàng nhỏ lẻ) đúng không nào? Bạn với bưu cục ký kết với nhau, tôi đưa anh $, 
anh chuyển cho tôi cái thư / bưu kiện đến đúng địa chỉ là ok, 
còn chuyển bằng cách nào là chuyện của ông, tôi không quan tâm ^^.

 

Đấy, cái hợp đồng chính là interface đó, nó chính là phương thức kết nối bạn với bưu cục

 

Bạn vẫn chưa tin, để tôi lấy 1 ví dụ khác cho bạn nhé ^^. Bạn có 1 cái điện thoại, bạn có 1 cái sạc, 
làm thể nào để bạn kết nối cái điện thoại với cái sạc. Thông qua chuẩn bạn ơi. 
Với iPhone hiện tại là chuẩn lighting, với Android thì là micro usb, hay usb-type C.

 

Chính xác chưa nào, interface chính là chuẩn kết nối. Và do có interface nên 2 đối tượng có thể kết nối được với nhau mà không cần biết tới nhau quá nhiều tới mức không phụ thuộc vào nhau :)) -> điều này sẽ tạo nên sự linh hoạt của hệ thống.

 

Thế tại sao bạn lại ràng buộc việc gửi thư / bưu kiện của bạn với bưu cục Việt Nam.

 

Có phải bạn đang hạn chế sự linh hoạt của việc gửi thư :)). Cái ông bưu cục vừa xấu lại vừa khó tính trong khi đó hiện tại có rất nhiều công ty tư nhân coi bạn là thượng đế được mở ra để làm việc này. Tuyệt với biết mấy nếu bạn có thể ký 1 cái hợp đồng chung và được quyền lựa chọn công ty nào được thực hiện việc vận chuyển cho bạn. Có cạnh tranh mới có phát triển đúng không nào ^_^.

Bạn nhìn thấy rồi chứ, linh hoạt là ở đây, cùng 1 việc gửi thư, bạn có thể lựa chọn vô vàn biến thể của việc vận chuyển. Cuộc đời vẫn đẹp sao các bạn nhỉ ^^

*) Dependency Injection và IoC

Chắc chúng ta thông nhất với nhau rằng việc làm 1 cái hợp đồng chung, 1 cái chuẩn kết nối, 1 cái interface giữa các component với nhau là việc bắt buộc để hệ thống được linh hoạt hơn rồi đúng không nhỉ? Vậy khi đã có hợp đồng chung rồi thì việc ký kết diễn ra như thế nào, và làm cách nào chúng ta có thể tùy chọn được biến thể của component trong hệ thống. Đây là lúc Dependency Injection và IoC tỏa sáng ^^.

Bạn muốn ký hợp đồng chung cho toàn bộ class, method nào sử dụng cũng được -> hãy inject interface vào hàm khởi tạo __construct()

Bạn muốn ký hợp đồng chung cho 1 method cụ thể trong class -> hãy inject interface thành parameter của method đó.

Việc còn lại chỉ là định nghĩa cái interface đó sẽ được biến thể nào của component xử lý -> bạn cần IoC ở đây. Và với Laravel, nơi tốt nhất để đặt IoC là ở Service Provider.

Hi vọng với cách diễn giải đời thường thế này, bạn cũng đã hiểu phần nào vai trò của các thành phần tham gia trong hệ thống tới thời điểm này

*) Ok, lý thuyết suông thì sẽ không thuyết phục được lòng người, chúng ta đi vào 1 ví dụ nho nhỏ để hiểu rõ hơn nhé :).

Chúng ta sẽ giả lập lại phần gửi mail, nhưng không phải là thư tay mà là email trong Laravel nhé :D.

Bạn có hay sử dụng Mail facade của Laravel không? Chắc đoạn code dưới đây sẽ quen thuộc với bạn lắm nhỉ?

 

class UserController extends Controller
{
   /**
   * Send an e-mail reminder to the user.
   *
   * @param Request $request
   * @param int $id
   * @return Response
   */
   public function sendEmailReminder(Request $request, $id)
   {
       $user = User::findOrFail($id);

       Mail::send('emails.reminder', ['user' => $user], function ($m) use ($user) {
           $m->from('hello@app.com', 'Your Application');

           $m->to($user->email, $user->name)->subject('Your Reminder!');
       });
   }
}

 

(Đây là ví dụ trên trang chủ của Laravel nhé, version 5.2 ^^).

Cuộc đời thật là tuyệt với vì Laravel hỗ trợ ta đến tận răng việc gửi email thế này. Nhưng, lại nhưng ^^, tại sao bạn lại giới hạn bản thân mình chỉ sử dụng tính năng gửi email của Laravel. Bạn có đang tự giới hạn sự linh hoạt của việc send email hay không? Nhỡ có việc mà Mail Facade của Laravel không xử lý được thì sao. Cuộc đời không chỉ màu hồng như bạn nghĩ đâu >_<. Nếu bạn muốn hệ thống linh hoạt hơn thì phải làm các bước sau :):

  1. Tạo một hợp đồng chung về việc gửi email
  2. Định nghĩa 1 biến thể sử dụng Mail Facade của Laravel để gửi
  3. Inject hợp đồng gửi email đó vào class / method mà ta muốn sử dụng
  4. Lựa chọn / khai báo biến thể ta muốn sử dụng ở hợp đồng chung này

Nếu bạn muốn sử dụng 1 biến thể khác thì làm thế nào:

  1. Tạo 1 biến thể mới
  2. Thay đổi phần khai báo biến thể sẽ handle hợp đồng chung này

Các bạn thấy đó, vì UserController chỉ có quan hệ với hợp đồng chung (chính là interface) mà cái hợp đồng chung này hoàn toàn được xử lý bởi nhiều cách khác nhau nên chúng ta hoàn toàn có thể thay thế các component xử lý hợp đồng chung lẫn nhau -> bạn đã nâng tầm tính linh hoạt của hệ thống lên một tầm cao mới rồi đấy ^^. (Coding to interface -> bạn có thể tìm hiểu thêm với keynote này)

Ok, vào phần code thôi:

1. Tạo hợp đồng chung:

 

interface Notify
{
    public function send($subject, $template, $data);
}

 

2. Định nghĩa biến thể:

 

use Mail;

class LaravelMailer implements Notify
{
	public function send($subject, $template, $data)
	{
		 Mail::send($template, ['user' => $data], function ($m) use ($data) {
                     $m->from('hello@app.com', 'Your Application');

                     $m->to($data->email, $data->name)->subject($subject);
                 });
	}
}

 

3. Inject hợp đồng chung vào class:

 

class UserController extends Controller
{
    private $notifier;

    public function __construct(Notify $notifier)
    {
        $this->notifier = $notifier;
    }
    /**
     * Send an e-mail reminder to the user.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendEmailReminder(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $this->notifier->send('Your Reminder!', 'emails.reminder', $user);
    }
}

 

Note: mình đã sử dụng dependency injection để inject vào trong hàm __construct() và sử dụng trong method sendEmailReminder()

Ở thời điểm này Laravel sẽ không biết Notify là cái chi chi. Vì hiện tại Notify là 1 interface, nó không định nghĩa body của method nên chẳng biết sẽ xử lý thế nào -> chúng ta cần định nghĩa biến thể nào sẽ xử lý interface này.

4. Binding inteface to concrete class (Khai báo biến thể xử lý hợp đồng chung nhá ^^).

 

class NotifyServiceProvider extends ServiceProvider
{
    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('Notify', function ($app) {
            return new LaravelMailer();
        });
    }
}

 

Xong, ở thời điểm này, tất cả hợp đồng chung Notify được inject trong toàn bộ hệ thống của mình sẽ được handle bởi LaravelMailer.

Về phần sử dụng 1 biến thế khác để xử lý Notify, mình sẽ không nêu ví dụ cụ thể ở đây, chỉ là các bạn làm theo bước 2 để tạo 1 biến thể mới và sửa lại code ở bước 4 để binding biến thể đó để xử lý Mailer :). Đơn giản quá nhỉ, giờ thay thế component có gì khó nữa đâu ^_^_^_^_^_^.

 

Phào, cuối cùng cũng đã xong. Mình cám ơn các bạn đã theo dõi đến tận bước cuối này. Hi vọng mình cũng đã bổ sung thêm 1 số kiến thức mới cho các bạn. Hẹn gặp lại các bạn ở phần tiếp theo.

P/S: Vì không liên quan lắm đến lý thuyết nhưng những item dưới đây sẽ giúp các bạn có cách code sáng sủa và đẹp hơn. Hi vọng các bạn có thời gian tìm hiểu:

  • Don’t repeat your self (DRY)
  • SOLID principles

 

Nguồn: http://blog.portalbeanzvn.com/kien-truc-he-thong-tren-laravel-phan-2/