Tản mạn về Repository Design Pattern ( trong laravel )

A long time ago, in a company far far away ...

A bored, lazy developer has been tasked with creating something that-could-be-call-a-study-report, and submit it to THE-GREATEST-KNOWLEDGE-SHARING-SITE-EVER ( built by the said company, by the way ).Fail to do so, and he will have to face the wrath of an immensely powerful being, capable of reducing his happy-go-lucky life to a pitiful state in mere seconds. In other words, my wife would be extremely angry if lost 2-days worth of salary simply because of my laziness. And thus, this article is born. Welcome to the 5th part of 'Programmer's Extremely Neglected but Interesting Subjects'. Today, our topic will be 'Repository Design Pattern.

Na na na na na na na na na na na na na na na na na na ... BATMAN!

Dependency Inversion Principle , Inversion of Controls and Dependency Injection

Hơi lòng vòng loằng ngoằng một chút, nhưng để nắm rõ hơn Repository Design Pattern, trước tiên ta cần được trang bị một chút kiến thức cơ bản về các khái niệm trên. Đi từ gốc đến ngọn trước :

Dependency Inversion Principle

Đúng như tên gọi, đây chỉ đơn giản là một quy tắc về thiết kế cấu trúc phần mềm, ngắn gọn thì nó chỉ có 2 quy tắc sau :

  • Module ở level cao hơn không phụ thuộc vào module ở level thấp hơn.

  • Class trừu tượng ( Abstractions ) không phụ thuộc vào class cụ thể ( Details ) mà phải là ngược lại ( Details depend upon abstractions ).

Để hiểu rõ 2 quy tắc trên, ta cùng tưởng tượng một ví dụ đơn giản sau. Giả sử ứng dụng của ta đang có một yêu cầu đặt ra là khi xảy ra lỗi thì ghi lại log. Hãy coi như framework chưa hề có bộ ErrorHandler built-in. Về cơ bản, ta có thể làm như sau

class LogWriter
{
	public function write() {
    	echo 'A wild Critical Bug appears !';
    }
}

class EventListener
{
	public function listen($event) {
    	$writer = new LogWriter();
    	$writer->write();
    }
}

Đại loại như thế. Nếu chỉ thế này thì code của ta vẫn tạm ổn, có thể tạm thời chưa nhìn ngay ra vấn đề gì. Nhưng giả sử, giờ ta muốn, với 1 số lỗi thì ghi vào log, 1 số lỗi thì gửi email đến cho Admin chẳng hạn. Trong tình huống đó, ta phải làm thế nào. Nếu vẫn đi theo phong cách kia, có lẽ code của ta sẽ có dạng

class LogWriter
{
	public function fire() {
    	echo 'A wild Critical Bug appears !';
    }
}

class Mailer
{
	pu	blic function fire() {
    	// Let's pretend this line of code will send email
    	echo "A wild Critical Bug appears ! Here's an email for you.";
    }
}

class EventListener
{
	public function listen($event) {
    	// yet another shitty example
        if ($event == 'log') {
    		$writer = new LogWriter();
    		$writer->fire();
        } elseif ($event == 'email') {
        	$mailer = new Mailer();
            $mailer->fire();
        }
    }
}

Đến đây có lẽ vấn đề của ta đã hiện ra rõ hơn. Nếu cứ viết theo phong cách này, dù trong trường hợp ta chỉ muốn gửi email thôi, nhưng đoạn code Listener của ta vẫn phải phụ thuộc vào class LogWriter, thiếu cái này sẽ sinh ra lỗi ngay. Và nếu giả sử ta muốn có thêm xử lí khác nữa, gửi SMS, hay lưu vào database, ... hay gì gì đó nữa thì sao ? Rõ ràng hướng tiếp cận này ữngcó vẻ không ổn. Đến đây ta hãy thử áp dụng từng nguyên tắc của Dependency Inversion Principle vào xem tác dụng của nó ra sao nào ? Và thực hiện điều đó cũng đồng nghĩa với việc ta đã chạm đến khái niệm thứ 2 Inversion of Control

Inversion of Control

Về mặt khái niệm mà nói, Inversion of Control có thể nói là cách thức cụ thể để ta áp dụng quy tắc được nêu ra trong Dependency Inversion Principle vào trong code của mình. Dependency Inversion Principle đặt ra quy tắc phụ thuộc lẫn nhau giữa các module trong một phần mềm, còn Inversion of Control là kĩ thuật cụ thể, để ta viết sao cho các module thuộc level trên phụ thuộc vào những abstract class thay vì phụ thuộc trực tiếp vào các module ở dưới. Các module này lần lượt sẽ là các thực thể hóa của các abstract class. Và tất nhiên là viết như thế không thì chỉ có con nhà người ta đọc xong mới hiểu nổi, còn người bình thường chúng ta thì phải thử áp nó vào ví dụ trên kia, xem có dễ hiểu hơn không nào.

interface Handler
{
	public function fire();
}

class LogWriter implements Handler
{
	public function fire() {
    	echo 'A wild Critical Bug appear.';
    }
}

class Mailer implements Handler
{
	public function fire(){
    	echo 'Santa-sama, please deliver this email for me.';
    }
}

class EventListener
{
	private $ryan;

    public function __construct(Handler $handler) {
    	$this->ryan = $handler;
    }
	// In case of error, call this function.
	public function listen() {
    	// Don't you dare criticize my code
        $this->ryan->fire();
    }
}

Bỏ qua những chỗ chưa ngon, thì code này giờ nhìn ngon phết nhờ. Listener của ta bây giờ không phụ thuộc vào class handler cụ thể nào nữa. Nếu muốn thêm nghiệp vụ, ta có thể thoải mái tạo thêm các class mới, implements Handler, code cũ của ta không mảy may ảnh hưởng chút nào. And that, ladies and gentlemen, is Inversion of Control in action. Giờ ta có thể cùng nhau qua khái niệm cuối cùng, xin giới thiệu :

Dependency Injection

Thật ra , đọc qua Inversion of Control thì ta cũng gần như nắm được khái niệm này rồi. Nếu như Dependency Inversion Principal là quy tắc chung, Inversion of Control là kĩ thuật cụ thể, thì Dependency Injection là thao tác thực hiện của kĩ thuật cụ thể đó. Nói dễ hiểu hơn, Dependency Injection là cách ta chỉ ra trong module ở level cao xem, trong trường hợp cụ thể này, thì nó cần gọi đến class cụ thể ( Details ) nào. Có mấy loại Dependency Injection như sau

Constructor Injection

Trong phần này, mọi khái niệm thật sự là đều rất gần với nghĩa đen của nó. Đúng như tên gọi, Constructor Injection là cách ta đưa (inject) class cụ thể cần được gọi tới (dependencies) vào trong hàm construct của class gọi. Như ví dụ phía bên trên chính là ta đã thực hiện Constructor Injection.

Setter Injection

Nghe nhạc hiệu đoán được ngay chương trình, theo cách này thì ta sẽ inject dependencies by using a setter method, thật sự là éo biết phải nói câu này trong tiếng Việt ra sao nữa (orz)

class EventListener
{
	private $ryan;

    public function setRyan(Handler $handler) {
    	$this->ryan = $handler;
    }
	// In case of error, call this function.
	public function listen() {
    	// Don't you dare criticize my code
        $this->ryan->fire();
    }
}
Property Injection

Như trên luôn, chắc nghe cái tên là cũng phải biết loại DI này nó hoạt động ra sao, nhưng thôi cứ làm cái ví dụ cho nó xôm.

class EventListener
{
	public $ryan;

	// In case of error, call this function.
	public function listen() {
    	// Don't you dare criticize my code
        $this->ryan->fire();
    }
}

Vì đây không phải là phần chính của bài viết, nên thôi không viết thêm chi tiết nữa. Bài tập nho nhỏ dành cho người đọc, thử nghĩ xem trong 3 loại Dependency Injection trên kia, trong tình huống nào thì sử dụng phương pháp cụ thể nào, lợi và hại trong từng tình huống? Thế nhé.

Repository Design Pattern

Vâng, tôi cảm thấy vô cùng đau xót khi phải nói điều này với những ai đã kiên nhẫn đọc đến tận đây, nhưng toàn bộ phần phía trên chỉ là background knowledge thôi ạ. Tiêu đề bài viết là Repository Design Pattern, và đến đây thì tiết mục chính của bài viết mới bắt đầu.

Về định nghĩa thì nguyên văn nó là thế này

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.

Tóm lại, mục đích của nó là vẫn nhằm hướng tới một ước mơ tươi đẹp từ ngàn đời nay của giới developer, đó là sự tách biệt hoàn toàn giữa data access logic và business logic, để thằng làm business logic ( tạm gọi là thằng A ) hoàn toàn không phải quan tâm tới công việc của thằng làm data access logic ( đôi khi vẫn chính là thằng A kia luôn ). Túm lại là nó như cái hình dưới đây ( đi chôm về ) miêu tả repository_pattern.png

Cụ thể thì .... hẹn gặp lại các bạn vào tháng sau, khi cơm áo gạo tiền 2 ngày lương thúc giục. Bài viết đến đây có lẽ đã đủ dài, dừng lại thôi để tháng sau còn có cái mà chém. Không biết có ai đọc không, nhưng nếu bạn lỡ theo dõi đến tận đoạn này, xong ngơ ngác nhìn lại cái tiêu đề bài viết, thì ... xin lỗi vì quả title lừa tình . Đúng là ở đời chả tin được bố con thằng nào sất 😐