Architecture, Tản mạn, Uncategorized

Em muốn trở thành kiến trúc sư

Mình muốn kể cho mọi người nghe một câu chuyện là cuộc đối thoại ngắn giữa một anh chàng Developer trẻ và 1 anh Architect. Cậu ta đến gặp anh kiến trúc sư và tâm sự là muốn trở thành 1 kiến trúc sư như anh.

contact-6e28521451ea082bbad6fb829abaf4b8

Sau này em cũng muốn trở thành 1 Software Architect như anh.

Good, đó là 1 hướng đi tốt cho em đó.

Em muốn lead team và sẽ ra những quyết định quan trọng trong việc lựa chọn database, framework, web-server và tất cả mọi thứ khác.

Nghe em nói, anh không nghĩ là em muốn trở thành 1 Software Architect.

Sao không anh, em đã nói là em muốn trở thành một người sẽ đưa ra những quyết định quan trọng trong việc sử dụng các công nghệ cho dự án.

OK, là đưa những quyết định quan trọng. Nhưng những cái em nói như database, framework, web-server… nó không có quan trọng và cũng không liên quan.

Nghĩa là sao anh? Database không quan trọng hả? Chúng ta phải chi rất nhiều tiền cho database đó.

Có thể là rất nhiều. Nhưng mà việc quyết định có xài hay xài database gì không phải là một quyết định quan trọng.

Sao anh nói vậy? DB là trái tim của hệ thống, là nơi lưu trữ toàn bộ dữ liệu, không có DB thì lấy đâu ra system!

OK, nghe anh nói. DB đơn thuần chỉ là 1 I/O device. Nó là 1 công cụ quan trọng trong việc lưu trữ, sắp xếp, truy vấn và report nhưng mà những cái đó chỉ là thứ phụ trợ cho kiến trúc của hệ thống của em thôi

Phụ trợ? Đùa hả anh?

Đúng vậy, anh nói là phụ trợ. Những nghiệp vụ của system em có thể sử dụng một vài công cụ như lữu trữ, truy vấn; nhưng mà những đó không phải là bản chất của các nghiệp vụ. Trong 1 số ngữ cảnh, em có thể phải thay thế một database này bằng một database khác, nhưng nghiệp vụ của hệ thống thì vẫn không thay đổi.

Uhm… nhưng mà khi thay đổi như vậy, ví dụ như từ MySQL sang Oracle hoặc chuyển sang dùng file lưu trữ thì em phải code lại toàn bộ rồi.

Đó, đó chính là vấn đề.

Ý anh là sao?

Vấn đề chính là việc em cho rằng các business rules phải phụ thuộc vào những tools như DB hay Framework. Nhưng không. Hay nói chính xác hơn là một kiến trúc tốt mà em muốn xây dựng sẽ không thể có chuyện đó.

Từ từ.. Em nghĩ không ra. Làm sao mà… mà có thể build các business rules mà không dùng đến database hay framework được anh?

Anh không nói là sẽ không có DB hay framework. Anh chỉ nói là không nên phụ thuộc vào chúng. Business rules không cần phải biết cụ thể là em xài DB gì, MVC hay MVVM gì sất.

Sao được anh? Làm sao để viết code mà không biết mình dùng DB gì? Rồi làm sao access để lưu trữ, truy vấn này nọ?

Đảo ngược sự phụ thuộc. Nghĩa là khi đó, DB của em sẽ phụ thuộc ngược lại business rules, chứ không phải business rule của em cần DB.

Anh nói cứ như không nói vậy.

Gượng đã. Anh đang nói chuyện nghiêm túc với em với tư cách là 1 Software Architecture. Anh đang nói đến Dependency Inversion Principle. Những thứ low level phải phụ thuộc vào những cái high level hơn, không được ngược lại.

Trời, khó hiểu quá. Những thứ high level như anh nói, em giả dụ như là các business rules, gọi xuống những cái low level ở dưới, là database. Vậy không phải là những cái high level đang phụ thuộc vào những thứ low level sao, cũng giống như caller thì phải phụ thuộc vào callee vậy. Ai cũng hiểu, ai cũng biết.

Đúng, tại thời điểm runtime (lúc run chương trình). Nhưng mà tại thời điểm compile time (lúc gõ code á), ta cần phải đảo ngược sự phụ thuộc. Source code trong business rules không nên, và không cần biết gì về source code access xuống DB.

Thôi anh. Gọi một thứ mà không cần biết nó là gì sao?

Tất nhiên là được chứ em. Lập trình hướng đối tượng.

Lập trình hướng đối tượng là việc mô phỏng các đối tượng ngoài thế giới thực thành các class gồm có data và function, rồi các đối tượng tương tác với nhau thông qua việc gọi hàm lẫn nhau.

Vậy thôi sao

Ai cũng biết mà, quá rõ ràng.

OK, sure. Tuy nhiên, bằng cách sử dụng các nguyên lý của hướng đối tượng, em có thể thực sự gọi 1 cái gì đó mà chẳng cần quan tâm nó là gì.

OK. Anh nói đi?

Em biết trong thiết kế hướng đối tượng, các objects sẽ send messages cho nhau?

Vâng, em biết điều đó.

Và em biết 1 sender có thể gửi message đến receiver mà không cần biết kiểu (dữ liệu) của receiver?

Điều đó phụ thuộc vào ngôn ngữ. Trong Java, sender có thể chỉ cần biết kiểu cha (base) của receiver. Trong Ruby, sender biết rằng receiver có thể xử lý message được gửi đến là được.

Chính xác. Trong cả 2 trường hợp thì sender đều không biết chính xác kiểu của receiver.

Đúng vậy anh.

Vì vậy, sender có thể gọi 1 hàm của receiver mà không cần biết chính xác kiểu của receiver đó.

Yeah, đúng vậy, em hiểu điều đó. Nhưng sender vẫn phải phụ thuộc vào receiver.

Như anh đã nói, đúng, trong lúc chạy (runtime). Còn lúc viết mã thì không. Source code của sender không hề biết và cũng không phụ thuộc vào source code của receiver. Thực tế thì source code của receiver lại phụ thuộc vào source code của sender thì đúng hơn.

Lý nào như vậy! Sender vẫn phụ thuộc vào class mà nó call.

Có lẽ tốt hơn là nên có vài dòng code cho dễ hiểu. Dùng Java đi. Đầu tiên sẽ là package sender:

package sender;

public class Sender {
	private Receiver receiver;

	public Sender(Receiver r) {
		receiver = r;
	}

	public void doSomething() {
		receiver.receiveThis();
	}

	public interface Receiver {
		void receiveThis();
	}
}

Tiếp theo là package receiver:

package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
	public void receiveThis() {
		//do something interesting.
	}
}

Em thấy là receiver package đang phụ thuộc vào sender package, ở đây là class SpecificReceiver. Còn sender package hoàn toàn không biết gì về receiver package.

Yeah, nhưng mà có gì đó sai sai. Anh khai báo interface của receiver trong class của sender.

Em nhận ra rồi đó, nhưng không biết đã hiểu chưa.

Hiểu gì anh?

Đó là các nguyên lý trong kiến trúc phần mềm. Sender sở hữu interface mà receiver phải implement

Nghĩa là em phải dùng nested-class…

Đó chỉ là 1 cách. Còn nhiều cách khác nữa.

OK, cái này em hiểu. Quay lại với vấn đề ban đầu về DB đi anh, em vẫn chưa hiểu.

OK, như em muốn. Lần này sẽ là source code của business rules.

package businessRules;

import entities.Something;

public class BusinessRule {
	private BusinessRuleGateway gateway;

	public BusinessRule(BusinessRuleGateway gateway) {
		this.gateway = gateway;
	}

	public void execute(String id) {
		gateway.startTransaction();
		Something thing = gateway.getSomething(id);
		thing.makeChanges();
		gateway.saveSomething(thing);
		gateway.endTransaction();
	}
}

Nghiệp vụ gì có chút xíu vậy anh?

Example thôi em trai. Ngoài thực tế có thể có cả trăm class.

OK, Gateway là gì anh?

Nó cung cấp tất cả các phương thức truy cập xuống DB mà business rule cần. Đây là source code của nó:

package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
Something getSomething(String id);
void startTransaction();
void saveSomething(Something thing);
void endTransaction();
}

<em>Chú ý là nó nằm trong businessRules package nhé.</em>

OK, vậy còn Something class?

<em>Nó đại diện cho 1 business object. Để nó trong package có tên entities.</em>


package entities;

public class Something {
	public void makeChanges() {
		//...
	}
}

Và sau cùng là implementation của BusinessRuleGateway. Đó là class thực sự biết và làm việc với DB thực sự. Ở đây anh dùng MySQL:

package database;

import businessRules.BusinessRuleGateway;
import entities.Something;

public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
	public Something getSomething(String id) {
		// use MySql to get a thing.
	}

	public void startTransaction() {
		// start MySql transaction
	}

	public void saveSomething(Something thing) {
		// save thing in MySql
	}

	public void endTransaction() {
		// end MySql transaction
	}
}

Nhắc lại lần nữa là business rule gọi database lúc runtime; nhưng tại thời điểm compile, package database lại phụ thuộc vào businessrules package.

OK, OK, em hiểu rồi. Anh đang sử dụng tính đa hình (polymorphism) để che giấu implementation của database từ business rule. Nhưng anh vẫn phải có 1 interface cung cấp tất cả những gì mà các business rule cần.

Không phải tất cả. Chúng ta không cần phải cung cấp tất cả các việc access, query xuống DB đến các business rules. Thay vào đó, business rule sẽ tạo ra các interface phục vụ đúng chỉ cho những mục đích nó cần. Các implementation của các interface đó sẽ thực hiện việc query hoặc insert xuống DB tương ứng.

Ah.. nhưng giả sử nếu tất cả các business rule đều cần tất cả các giao tiếp xuống DB thì anh phải bỏ hết các functions đó vào gateway interface.

Gượng đã, em vẫn chưa hiểu được vấn đề.

Sao anh? Em nghĩ em đã thông suốt rồi.

Mỗi business rule sẽ định nghĩa interface chỉ cho việc access xuống data mà nó cần.

Đợi đã, nghĩa là sao anh? Anh thông cảm, em não cá vàng 😦

Đó gọi là Interface Segregation Principle. Mỗi business rule class sẽ sử dụng 1 số câu lệnh nhất định để truy cập vào DB. Và nó chỉ cần định nghĩa ra một interface giúp nó làm chuyện đó, không phải cho tất cả.

Nhưng mà như vậy sẽ có hàng tá tá các interfaces, và 1 tá tá các implementation class nữa.

Good, não em bắt đầu thông lại rồi đó.

Nhưng mà làm vậy chi cho rối rắm, tốn thời gian.

Ý em là muốn làm đơn giản, rõ ràng và tiết kiệm thời gian?

Thôi mà. Mấy việc đó chỉ làm cho code phình lên thôi.

Xét khía cạnh ngược lại, đây là những quyết định kiến trúc quan trọng cho phép em trì hoãn các quyết định không liên quan.

Anh nói vậy có nghĩa là gì?

Lúc đầu em nói em muốn trở thành 1 Software Architect? Em muốn đưa ra những quyết định thực sự quan trọng?

Vâng, đó là cái em muốn.

Và những quyết định mà em muốn đưa ra là database, webserver và các framework?

Vâng, và anh lại nói nó không phải là những cái quan trọng. Anh nói nó chẳng liên quan.

Đúng vậy. Chúng không quan trọng, cũng không liên quan. Những quyết định sống còn mà một Software Architect cần đưa là là có thể giúp em không phải suy nghĩ đến việc lựa chọn DB nào, webserver là gì và framework chi quá sớm.

Nhưng mà anh phải suy nghĩ về mấy cái đó trước chứ!

Không, không nên em. Thực sự thì, em cần phải làm mọi cách để làm thế nào để có thể trì hoãn các quyết định đó càng trễ càng tốt, bởi vì làm như vậy sẽ giúp em có thêm nhiều thông tin hơn.

Thai Vu là 1 kiến trúc sư, anh đã quyết định sử dụng database ngay ban đầu, và sau đó nhận ra rằng chỉ cần lưu files là đủ.

Vinh Rau là 1 kiến trúc sư, anh ngay từ ban đầu suy nghĩ rằng sẽ build ứng dụng trên web-server, nhưng sau đó mới nhận ra rằng, cái team thực sự chỉ cần là 1 socket interface đơn giản.

FapTV là team mà các achitect của dự án đã sớm lựa chọn framework cho dự án, và sau đó nhận ra rằng framework cung cấp nhiều cái mà họ không cần và lại ko đáp ứng hết cái họ cần rồi tự đặt mình vào thế khó.

Ribi là team mà các kiến trúc sư đã xây dựng lên kiến trúc bằng cách nào đó cho phép họ trì hoãn các quyết định đến khi họ thực sự có đủ thông tin để đưa ra lựa chọn cuối cùng.

Các kiến trúc sư trong team DauPhongTV là người biết cách tách kiến trúc của họ ra khỏi các device IO chậm chạp, tốn resouce và các framework để có thể tạo ra các bộ test case nhanh và nhẹ.

Và họ chỉ quan tâm đến các vấn đề thực sự, trì hoãn những cái không quan trọng khác.

Thiệt tình, không thể hiểu nổi anh nói cái gì.

Có lẽ em sẽ cần vài năm nữa hoặc hơn… nếu em vẫn chưa thực sự đặt chân vào công việc của 1 kiến trúc sư thực thụ, cậu bé à.

Phỏng theo câu chuyện “The Little Architecture” của Robert C. Martin (Uncle Bob)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s