Ở các bài viết trước, chúng ta đã tìm hiểu về 4 kiểu kiến trúc xây dựng ứng dụng:

  1. Kiến trúc đa tầng
  2. Kiến trúc lục giác
  3. Kiến trúc củ hành
  4. Package by Feature

Hôm nay, Techmaster Team sẽ giới thiệu với các bạn loại kiến trúc thứ 5 với cái tên rất hấp dẫn: kiến trúc sạch.

Unclde Bob Martin - Tác giả Clean Architecture

#1 Kiến trúc sạch - clean architecture là gì?

Kiến trúc sạch được xây dựng dựa trên tư tưởng "độc lập" kết hợp với các nguyên lý thiết kế hướng đối tượng (một đại diện tiêu biểu là Dependency Inversion). Độc lập ở đây ám chỉ việc project không bị phụ thuộc vào framework và các công cụ sử dụng trong quá trình kiểm thử.

Kiến trúc sạch chia project thành 4 lớp - ta có thể biểu diễn mỗi lớp thành một vòng tròn như sau:

Entities: là khái niệm dùng để mô tả các Enterprise Business Rule, ta có thể liên hệ khái niệm này với Domain Entities trong DDD

Use case: chứa các rule liên quan trực tiếp tới ứng dụng cục bộ (application-specific business rules)

Interface Adapter: tập hợp các adapter phục vụ quá trình tương tác với các công nghệ

Framework & Drivers: chứa các công cụ về cơ sở dữ liệu và các framework, thông thường bạn sẽ không phải lập trình nhiều ở tầng này. Tuy nhiên cần chắc chắn về mức ưu tiên sử dụng các công cụ này trong project.

Kiến trúc sạch định nghĩa ra một luật giữa các lớp kể trên: code của các class thuộc lớp trong không được tham chiếu đến code của class lớp ngoài. Tất cả việc phụ thuộc giới hạn ở mức interface. Luật này làm ta liên tưởng tới kiến trúc củ hành.

Bên cạnh việc định nghĩa ra các lớp kèm theo luật phụ thuộc, kiến trúc sạch còn mang đến một số gợi ý cho lập trình viên khi implement các class.

Như sơ đồ trên, bạn thấy flow control từ Controller sang Presenter không đi trực tiếp mà đi qua 2 interface là Use Case Input Port và Use Case Output Port. Nhờ đó sự ràng buộc được giảm đi tối đa.

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

#2 Bản chất của Clean Architecture

Theo quan điểm của tác giả, Clean Architecture nổi trội hơn các kiến trúc khác ở 2 điểm:

  • Tuyệt đối tuân thủ Dependency Inversion
  • Định hướng theo Use Case (Use Case orientation)

Dependency Inversion

Ngay từ phần 1 ta đã thấy xuất hiện từ khóa Dependency Inversion. Vậy thì Clean Architecture có hàng với Kiến trúc củ hành - Onion Architecture? Đúng vậy.

Clean Architecture áp dụng Dependency Inversion ở mức kiến trúc. Bằng cách này, nó trực tiếp đưa ra mức ưu tiên đối với các object trong ứng dụng của bạn, đồng thời hướng việc phát triển ứng dụng đến đẳng cấp "không lệ thuộc" vào framework hoặc database.

Định hướng Use Case

Tương tự với những gì chúng ta học được từ Package by Feature, Clean Architecture ủng hộ việc chia tách code theo chiều dọc. Đồng nghĩa với việc các tầng sẽ được phân biệt tới tận mức class. Sự khác biện lớn nhất giữa 2 kiến trúc trên nằm ở chỗ, thay vì đưa ra khái niệm "feature" một cách mơ hồ, Clean Architecture tái định hướng công đoạn chia package theo hướng Use Case.

Một điều đáng chú ý là nếu xét trong hệ thống phức tạp, khi Entities nằm ở tầng khác, một Use Case có thể hợp vơí nhiều Entities để cùng hoạt động và để phân loại theo kiểu Entity được tạo ra bởi con người.

#3 Triển khai

Uncle Bob là tác giả của kiến trúc này. Đáng buồn là quyển sách đề cập đến cách triển khai thực tế của nó vẫn chưa lưu hành chính thức (dự kiến tháng 7 năm nay). Tuy nhiên chúng ta có thể tham khảo Case Study về Clean Architecture trên GitHub của Uncle Bob.

Cách tổ chức package

Như các bạn thấy, Entities và Use Cases được chia thành 2 package khác biệt. socketserver, http và view có thể xem như một phần của nhóm Framework & Driver. Có một điểm hơi mù mờ là package gateways. Trong package này, ta thấy toàn bộ là các interface thuộc tầng Interface Adapter layer, nhưng xét về mặt khái niệm, các interface này lại thuộc về Use Case. Theo dự đoán của tác giả thì việc xếp các interface này vào package gateways để đảm bảo chúng có thể được sử dụng bởi các Use Case khác nhau (đó chỉ là dự đoán thôi nhé).

Khi theo dõi package usecases.codecastSummeries, ta có được cái nhìn chi tiết hơn về cấu trúc - thành phần của một Use Case package. Cụ thể, nó chứa các class liên quan tới quá trình thực thi của một use case trong thực tế: có view, có controller, presenter, boundaries, response models... Tại sao lại có quá nhiều class như vậy? Đó là "yêu cầu" tối thiểu để "sạch".

Phần implement của các class

Mở từng class ra một, bạn thấy tất cả chúng chỉ sử dụng duy nhất một annotation: @Override.

Quay lại với một "luật" của kiến trúc sạch là không phụ thuộc framework, kết hợp với biểu đồ ở mục 1, ta thấy framework nằm ở lớp ngoài cùng. Như vậy code ở lớp bên trong không được phép tham chiếu trực tiếp tới chúng.

Vậy thì làm sao tôi có thể làm việc cùng Spring framework?

Nếu bạn cần thì bạn có 3 lựa chọn XML config, @Configuration và @Bean. Nhớ nhé, không @Service, không @Autowired!

#4 Lợi ích

  • Mạch lạc - dễ xem (bản gốc ghi screaming với dụng ý là chỉ cần nhìn cấu trúc package cũng có thể hiểu được mục đích và cơ chế hoạt động của ứng dụng)
  • Linh hoạt - thể hiện ở khả năng độc lập, không phụ thuộc vào framework, database, application server.
  • Dễ kiểm thử - testable
  • Có khả năng kết hợp với các chiến thuật phát triển như DDD

#5 Hạn chế

  • Không thể sử dụng framework theo cách mỳ ăn liền- do luật dependencyu inversion.
  • Khó áp dụng
  • Indirect - quá nhiều interface?
  • Cồng kềnh - thể hiện ở việc có quá nhiều class so với các project cùng mục tiêu (tuy nhiên các class được thêm vào đều có chủ ý và đáp ứng đúng quy định khi triển khai kiến trúc)

#6 Áp dụng khi nào?

Chính tác giả bài viết này cũng chưa từng áp dụng Clean Architecture trong các context thực tế của các project mà anh ta đang tham gia. Để có được cái nhìn đầy đủ, chúng ta nên chờ đợi cuốn sách của Uncle Bob?

Nếu suy luận từ những hạn chế của Clean Architecture, chúng ta rút ra được một số điểm cần chú ý khi áp dụng Clean Architecture:

  • Trình độ của team? Khi khả năng team member có hạn và họ không thích kiểu kiến trúc này (có thể do thói quen hoặc do "lười" đổi mới) thì dĩ nhiên áp đặt lên đầu họ các tư tưởng ở mục #1 chỉ kéo hiệu suất của team đi xuống.
  • Cần duy trì và nâng cấp hệ thống kể cả khi người làm ra nó đã rời khỏi công ty?
  • Cần duy trì hệ thống ổn định, không phụ thuộc vào sự sinh-tử của framework?

Techmaster via TidyJava