Con muốn trở thành một Software Architect

6682

Bài viết được sự cho phép của tác giả Tino Phạm

Một developer trẻ nói với Chú của mình về việc cậu ta muốn trở thành một Software Architecture trong tương lai. Sau đây là cuộc nói chuyện giữa hai chú cháu.

Con muốn sau này trở thành một Software Architecture (kiến trúc sư phần mềm).

Con có định hướng cho mục tiêu nghề nghiệp vậy là tốt lắm đó.

  10 Công cụ Go-To Tech dành riêng cho các Software Developer
  Biến Git và GitHub trở thành công cụ đắc lực cho Software Engineer

Con muốn được dẫn đầu một nhóm và đưa ra các quyết định quan trọng như chọn loại Database nào, Framework nào, Web API thế nào và dùng những công nghệ nào cho dự án.

Khá đó chứ. Nhưng nghe có vẻ như con không phải muốn trở thành Software Architecture nữa.

Tất nhiên là con muốn chứ. Con muốn trở thành một người quyết định tất cả những thứ quan trọng.

Chú biết vậy, nhưng mà con đang liệt kê những thứ không quan trọng. Chính xác hơn là những thứ con liệt kê nó không liên quan đến quyết định của một Software Architecture.

Ý của Chú là sao? Database mà không quan trọng sao? Chú có biết phải chi bao nhiêu tiền cho chúng không?

Có lẽ là rất nhiều, nhưng mà lựa chọn database không phải là một trong những quyết định quan trọng.

Sao Chú nói vậy? Database là trái tim của toàn hệ thống. Nó là nơi dữ liệu được lưu trữ, sắp xếp, đánh index và được truy cập. Không có nó thì sẽ không có hệ thống.

Database đơn thuần chỉ là một thiết bị IO. Nó chỉ là một công cụ hữu ích để sắp xếp, truy vấn và báo cáo nhưng đó chỉ là những phụ trợ cho kiến trúc hệ thống.

Phụ trợ hả? Chú giởn với con đúng không.

Chú nói thật, nó là phụ trợ thôi. Những nghiệp vụ (Business logics) của hệ thống có thể được sử dụng một trong các công cụ đó, nhưng những công cụ đó không phải là bản chất của các nghiệp vụ. Nếu cần con có thể thay thế Database này thành Database khác hoặc chuyển sang dùng Framework khác, nhưng các nghiệp vụ trong hệ thống của con vẫn như vậy.

Ý của Chú là sao?

Vấn đề của con là tin rằng các nghiệp vụ của phần mềm phụ thuộc vào các công cụ của Database. Sẽ không có chuyện đó hoặc có ít thôi nếu con cung cấp kiến trúc phần mềm tốt.

Chú nói nghe khó hiểu quá. Làm sao để xây dựng các nghiệp vụ mà lại không dùng đến các công cụ mà chúng phải dùng chứ.

Chú không có nói chúng ta không dùng các công cụ của Database, Chú nói chúng ta không nên phụ thuộc vào các công cụ để phát triển dự án. Các quy tắt nghiệp vụ sẽ không cần biết loại Database nào sẽ được dùng.

Vậy làm sao để có được các nghiệp vụ sử dụng các công cụ mà lại không biết về các công cụ?

Con hãy đảo ngược sự phụ thuộc này lại. Nghĩa là con có database phụ thuộc vào các nghiệp vụ, bây giờ con làm cho các nghiệp vụ không còn phụ thuộc vào database.

Vô lý quá vậy Chú.

Bình tĩnh, Chú đang nói với tư cách là một Software Architecture. Trong thiết kế phần mềm có một nguyên lý gọi là Dependency Inversion Principle. Nguyên lý này nói là những thứ low level sẽ phụ thuộc vào những thứ high level hơn, không được ngược lại.

Khó hiểu quá! những thứ high level ( Con đoán ý Chú là các nghiệp vụ – business logics) gọi xuống những thứ low level ( Con đón ở đây là database nè). Thế thì những thứ high level phụ thuộc vào low level cũng giống như callers thì phụ thuộc vào callees. Ai cũng hiểu vậy hết chơn hết chội (ai cũng hiểu vậy cả).

Tại thời điểm runtime thì đúng (lúc chạy chương trình). Nhưng tại thời điểm compile time (lúc biên dịch code hay viết code) chúng ta muốn đảo ngược các phụ thuộc này. Source code của high level (các nghiệp vụ) sẽ không cần biết source code ở low level (code để kết nối, thao tác với database)

Sao được Chú! mình gọi một cái gì đó mà không cần biết đến nó sao được.

Được chứ. Cái đó gọi là lập trình hướng đối tượng (OOP).

Con biết lập trình hướng đối tượng nè. Lập trình hướng đối tượng là việc tạo các classes (hay objects) từ thế giới thực vào trong phần mềm, các đối tượng (objects) thì có các thuộc tính (properties) và các hành vi (behaviors). Các đối tượng tương tác với nhau bằng cách gọi các functions của nhau. OOP là một cách để tổ chức code trực quan hơn.

Con biết chỉ có vậy thôi hả?

Ai cũng biết vậy mà Chú. Nó đúng mà.

Con nói không sai. Tuy nhiên, việc sử dụng các nguyên lý của lập trình hướng đối tượng sẽ giúp cho con gọi một cái gì đó mà không cần biết tới nó.

Con đồng ý luôn, Vậy làm thế nào đây?

Con có biết trong lập trình hướng đối tượng, các đối tượng có thể gửi tin nhắn cho nhau không?

Dạ, con biết chứ.

Rồi, con có biết là sender (đối tượng gửi message) không cần biết gì về kiểu dữ liệu của receiver (đối tượng nhận message)?

Cái này còn tùy vào ngôn ngữ mình đang dùng. Trong Java thì sender ít nhất cũng biết được base type của receiver. Trong Ruby thì sender ít nhất cũng phải biết receiver có thể handler được message.

Đúng vậy, nhưng  cả hai trường hợp sender không hề biết chính xác kiểu dữ liệu của receiver.

Dạ, đúng như vậy.

Vì vậy sender có thể gọi một hàm bên trong receiver mà không cần biết chính xác kiểu dữ liệu của receiver đó.

Dạ, đúng. Con hiểu điều đó. Nhưng sender vẫn phụ thuộc và receiver.

Tại thời điểm chạy chương trình (runtime) thì đúng, nhưng lúc viết code (compile time) thì không. Source code của sender không hề biết cũng không hề 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. 

Không đời nào! con nghĩ là Sender vẫn phụ thuộc vào class mà nó đang gửi message.

Xem thêm tuyển dụng Software Developer hấp dẫn trên TopDev

Có lẽ chúng ta nên dùng vài dòng code để làm rõ vấn đề hơn. Chú sẽ viết cái này với Java. Đầu tiên 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 sẽ là package receiver:

package receiver;

import sender.Sender;

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

Con hãy để ý là package receiver đang phụ thuộc vào package sender. Cụ thể là class SpecificReceiver phụ thuộc vào Sender. Con cũng thấy là package Sender không hề biết bất cứ thứ gì trong package Receiver.

Dạ, nhưng Chú đã cheated code. Chú đã đặt interface của receiver bên trong class sender.

Con bắt đầu hiểu vấn đề rồi đó, khá nhanh đấy chứ.

Mà hiểu cái gì vậy Chú? ahihi

Đó là các nguyên lý của kiến trúc phần mềm. Senders sở hữu các interfaces mà receivers phải implement.

Chà..! vậy là con phải dùng các lớp lồng nhau (nested-classes).

Nested-classes chỉ là một trong những cách để đạt được điều đó. Còn nhiều cách khác nữa.

OK Chú. Bây giờ quay lại vấn đề với database mà Chú cháu mình đã nói lúc đầu đi. Chú giải quyết nó thế nào.

Con hãy xem code sau đây nhé. Đầu tiên là source code dành cho business rule.

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();
  }
}

Code cho nghiệp vụ ít quá vậy Chú.

Ví dụ đơn giản cho dễ hiểu thôi con trai. Con có thể thêm nhiều class mới, viết code implement cho nhiều nghiệp vụ khác nếu con muốn.

Dạ, vậy Gateway là gì thế Chú?

Nó cung cấp tất cả các phương thức (methods) để cho business rule truy cập xuống database. Code implemented của nó như sau:

package businessRules;

import entities.Something;

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

Con hãy chú ý là nó nằm trong package businessRules.

Dạ, con thấy rồi. Còn lớp Something là gì vậy Chú?

Nó là  1 object của business. Chú đặt nó trong package tên là entities.

package entities;

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

Cuối cùng là viết code implement cho BusinessRuleGateway. Đây là class biết về database thực sự đang dùng. Trong ví dụ này, Chú sẽ 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
  }
}

Một lần nữa, con hãy để ý là các business rules (nghiệp vụ) gọi database lúc runtime (chạy chương trình), nhưng lúc compile time (lúc viết code) thì database package lại phụ thuộc vào businessRules package.

Dạ dạ, con nghĩ là con đã hiểu. Chú dùng đa hình (polymorphism) để ẩn đi implementation của database package từ businessRules package. Nhưng Chú vẫn phải có một interface để cung cấp tất cả các thứ trong database mà business rule cần.

Không phải  tất cả. Chúng ta không cung cấp tất cả các thứ trong database cho business rule. Thay vào đó, chúng ta tạo các interface trong business rule chỉ cung cấp những thứ liên quan đến database mà nó cần. Các implemenations của các interfaces này có thể gọi đến các chức năng cụ thể như: insert, delete, update trên database tương ứng mà nó đang dùng.

Dạ! nhưng mà nếu như business rules cần tất cả các chức năng liên quan đến database, như vậy thì Chú phải đặt tất cả các methods đó vào trong gateway interface.

Haizz, hình như con vẫn chưa hiểu vấn đề.

Hiểu gì nữa chứ? Con hiểu quá rõ vấn đề rồi mà.

Mỗi business rule định nghĩa 1 interface để thao tác với database mà nó cần.

Chờ đã, Chú nói cái gì con chưa rõ lắm?

Cái này gọi là Interface Segregation Principle. Mỗi class business rule sẽ chỉ dùng một số chức năng để thao tác với database. Và do đó, mỗi business rule sẽ cung cấp một interface và expose một số methods để thao tác với database.

Nhưng như vậy thì Chú phải có rất nhiều interfaces và phải có rất nhiều class để implement cho các interfaces đó.

À, đúng rồi. Chú thấy giờ con đang bắt đầu hiểu rồi đó.

Nhưng code sẽ nhiều hơn, tốn thời gian hơn? Tại sao mình phải làm vậy chứ?

Thật ra, đó là cách giúp con viết code rõ ràng và tiết kiệm thời gian.

Thôi mà Chú. Điều đó làm code phình to lên và viết nhiều code hơn thì có.

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

Chú nói vậy nghĩa là sao?

Nà, có phải lúc đầu con nói là con muốn trở thành một Software Architecture? Con muốn đưa ra những quyết định quan trọng phải không?

Dạ, đó là những gì con muốn.

Trong số các quyết định con đưa ra là về: database, web api và các frameworks.

Dạ đúng rồi, nhưng Chú lại nói là những quyết định đó không quan trọng. Chúng là những thứ không liên quan.

Chính xác, những quyết định này không quan trọng cũng không liên quan. Những quyết định quan trọng mà một Software Architecture đưa ra là những quyết định giúp con KHÔNG đưa ra quyết định về database nào, web api thế nào, dùng framework nào quá sớm.

Nhưng Chú cũng phải đưa ra những quyết định này trước khi làm!

Không, không nên đâu con. Thực sự thì, con 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 con có thêm nhiều thông tin hơn.

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

Điều tệ hại là nếu 1 kiến trúc sư, anh ta 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, tất cả các teams thực sự chỉ cần là 1 socket interface đơn giản.

Điều tệ hại là nếu 1 kiến trúc sư, anh ta và team của mình đã 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ó.

May mắn 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.

May mắn là team mà các kiến trúc sư 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ẹ.

May mắn là team mà các kiến trúc sư chỉ quan tâm đến các vấn đề thực sự quan trọng, trì hoãn những cái không quan trọng khác.

Thua luôn, con hết hiểu nổi Chú đang nói cái gì luôn rồi.

Có lẽ con sẽ cần vài năm nữa hoặc hơn… nếu con 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é à.

Bài viết dựa theo mẫu chuyện A Little Architecture của Uncle Bob.

Bài viết gốc được đăng tải tại thangphampt.wordpress.com

Có thể bạn quan tâm:

Xem thêm việc làm IT hấp dẫn trên TopDev