Chia sẻ kiến thức lập trình

S.O.L.I.D – Dependency Inversion

Đừng phụ thuộc quá chặt

Ngày nay, với sự ra đời của các framework, việc tổ chức code của chúng ta cũng đơn giản hơn. Thông thường chúng ta sẽ có các tầng controller, service và repository, đây là cách tổ chức tương đối mềm dẻo. Tuy nhiên trước khi có các framework như hiện nay thì cách code thông thường là tầng controller sẽ phụ thuộc trực tiếp đến tầng repository dẫn đến khi tầng repository có update cũng sẽ dẫn đến những lỗi bất ngờ cho tầng controller. Vậy nên ngày nay chúng ta thường tuân thủ nguyên tắc dependency inversion thông qua các framework với 2 điểm đáng chú ý:

  1. Các lớp ở tầng cao không nên phụ thuộc trực tiếp vào các lớp ở tầng thấp mà phụ thuộc vào các lớp abstract hoặc interface
  2. Các lớp abstract cũng không nên phụ chứa quá nhiều chi tiết cụ thể của việc cài đặt. Ví dụ các lớp repository không nên viết cụ thể cho MySQL hay Oracle hay MSSQL.

Bài toán thực tế

Hãy nói chúng ta đang cần xây dựng 1 hệ thống quản lý sách, và chúng ta chưa biết liệu rằng sẽ dùng database MySQL hay Oracle hay MSSQL do cần tính toán về chi phí. Và chúng ta cũng chưa rõ là có cần sử dụng thêm cache để đảm bảo hiệu năng hay không.

Vậy nên cách tổ chức lớp như hình ở trên có lẽ chưa phù hợp khi tầng controller đang phụ thuộc quá chặt vào các tầng thấp nhất, và các tầng thấp cũng đang phụ thuộc vào quá chặt vào loại database và loại cache cụ thể.

Giải pháp

  1. Để tầng controller hoạt động ổn định thì chúng ta sẽ không để nó phụ thuộc trực tiếp vào các lớp ở tầng thấp nhất, chúng ta sẽ thêm 1 tầng service để xử lý nghiệp vụ, từ đó controller sẽ thực hiện đúng chức năng của nó là nhận yêu cầu, kiểm trả yêu cầu và trả về kết quả.
  2. Ở tầng repository chúng ta sẽ không sử dụng cài đặt cụ thể cho MySQL mà sẽ sử dụng các framework hỗ trợ truy xuất cơ sở dữ liệu, ví dụ ezydata-jpa.
  3. Cache do chưa biết có sử dụng không nên tạm thời chúng ta để lại, nếu cần thì sau này sẽ xử lý thêm ở tầng service.
  4. Để loại bỏ việc phụ thuộc chặt giữa các lớp với nhau chúng ta sẽ sử dụng các framework hộ trợ dependency injection, ví dụ ezyfox-bean

Sơ đồ tổ chức các tầng sẽ thay đổi thành thế này:

BookRepository sẽ ở dạng interface thế này:

@EzyRepository
public interface BookRepository extends EzyDatabaseRepository<Long, Book> {

    Book findByNameAndAuthorId(String name, Long authorId);
}

Nghĩa là nó không phụ thuộc vào một database cụ thể nào cả, và khi cần chỉ định kết nối đến database nào chúng ta sẽ chỉ cần thông qua cấu hình datasource kiểu thế này thôi:

datasource:
  jdbcUrl: jdbc:mysql://root:12345678@localhost:3306/test
  driverClassName: com.mysql.cj.jdbc.Drive

Lớp BookService sẽ thế này:

@EzySingleton
@AllArgsConstructor
public class BookService {
    private final BookRepository bookRepository;
}

Lớp BookController sẽ thế này:

@AllArgsConstructor
@Controller("/api/v1")
public class BookController {
    private final BookService bookService;

    @DoPost("/book/add")
    public BookResponse addBook(@RequestBody AddBookRequest request) {
}

Ví dụ đầy đủ bạn có thể xem tham khảo ezydata-jpa example.

Tổng kết

Mặc dù đây là nguyên tắc tương đối khó áp dụng, tuyên nhiên với sự hỗ trỡ từ các framework hiện đại như ngày nay mọi việc lại trở nên tương đối dễ dàng, và nó gần như là trong suốt với lập trình viên, những người hàng ngày vẫn đang sử dụng nguyên tắc này 1 cách tự nhiên. Vậy nên bạn cũng đừng quá lăn tăn về nó nhé, có thể bạn cũng đang áp dụng rất thành thạo rồi.

Share: