Tìm hiểu về clean architecture

I, Mở đầu

  • Để tạo nên một ứng dụng hay to hơn là hệ thống, các developer thường sẽ tuân theo 1 architecture.
  • Chúng ta có thể đã làm quen với các architecture áp dụng ở một app riêng lẻ như MVC, MVP và MVVM.
  • Hôm nay, mình sẽ giới thiệu với mọi người 1 architecture khác là clean architecture.

II, Tác giả

  • Người tạo ra clean architecture là một kỹ sư phần mềm rất nổi tiếng là Robert C.Martin.
  • Robert C.Martin hay Uncle Bob là một kỹ sư phần mêm người Mỹ. Ông có tầm ảnh hưởng lớn trong việc phát triển và áp dụng các nguyên lý thiết kế phần mềm.
  • Không chỉ có clean architecture, Uncle Bob còn là tác giả của AgileSOLID.

III, Clean architecture

  • Clean architecture là một kiến trúc (architecture) thiết kế phần mềm dựa trên dependency rule.
  • Clean architecture không bị giới hạn trong một ứng dụng mà nó còn có thể được áp dụng cho cả một hệ thống (tập các ứng dụng). Đây cũng là một trong những điểm vượt trội so với MVC, MVP hay MVVM.
  • Sơ đồ về clean architecture được khái quát như hình sau:
  • Dependency rule là gì ? Hãy cùng phân tích sơ đồ về clean architecture:
    • Mô hình sẽ chia thành nhiều vòng tròn đồng tâm. Mỗi vòng tròn tương ứng với 1 layer.
    • Vị trí của vòng tròn nói lên cấp (level) của chúng: vòng tròn càng xa tâm thì có cấp càng cao.
    • Dependency rule là quy tắc quy định rằng:
      • Điều 1: layer trong (level thấp hơn) không được chứa source code của layer ngoài (có level cao) hơn.
      • Điều 2: bất cứ thay đổi ở layer ngoài không làm ảnh hưởng tới layer trong.
  • Clean architecture thông thường chia thành 4 layer: entities, use cases, interface adapters và Frameworks and drivers.
  • Chúng ta có thể tham khảo cách implement của clean architecture thông qua CleanCodeCaseStudy của Uncle Bob trên Github.

1, Entities

  • Entities chứa enterprise wide business rules.
  • Điều này có nghĩa là entities chứa business logic của cả hệ thống (tập các app) chứ không bị bó buộc trong 1 app nào cả.
  • Nếu hệ thống chỉ có một app, các entity cũng chính là business object của ứng dụng đó.
  • Do entities có phạm vị hệ thống nên sự thay đổi hoạt động (không phải business logic) của một ứng dụng không làm ảnh hưởng tới entities. Ví dụ như các entity sẽ không bị thay đổi khi việc navigation giữa các màn hình hay UI bị thay đổi.
  • Do đó, entities có thể chạy độc lập mà không sợ bị lỗi.
  • Ví dụ 1: chúng ta muốn xây dựng hệ thống bao gồm các app liên quan tới audio. Chúng ta có thể tạo Audio entity object cho hệ thống.

2, Use cases

  • Use cases chứa application specific business rules.
  • Use thực hiện các use case của 1 ứng dụng dựa trên các entity object của entities.
  • Theo điều 1, use cases không chứa source code của interface adapters và frameworks and drivers.
  • Theo điều 2, use cases cũng không bị ảnh hưởng bởi sự thay đổi interface adapters và frameworks and drivers.
  • Use cases thể hiện business logic của ứng dụng. Do đó khi app requirement của ứng dụng thay đổi, code trong layer này cũng sẽ bị thay đổi.
  • Ví dụ 2: một trong những use case phổ biến là fetch data từ một api.

3, Interface adapters

  • Interface adapters là layer dùng để chuyển đổi dữ liệu của ở entites và use cases thành định dang thuận tiện nhất để làm việc với các tác vụ như database, networking…Do đó, interface adapters cũng có thể chứa các framwork có sẵn để làm việc.
  • Use cases, entities và các framework được sử dụng để chuyển đổi dữ liệu.
  • Ví dụ 3: ứng dụng quyết định làm việc với SQL database:
    • Entities và use cases không thể chứa source code về SQLite database.
    • Interface adapter tạo ra các đối tượng như table, SQL query…để làm việc thuận tiện nhất với yêu cầu của ứng dụng.
    • Ngoài interface adapters, không thành phần nào của clean architecture làm việc với SQL database.

4, Frameworks and drivers

  • Đây là layer ngoài cùng, nó là nơi chúng ta đi vào chi tiết hoá yêu cầu của ứng dụng như UI, application behavior, interaction…và cũng có thể bao gồm các Framework được xây dựng để phục vụ việc chi tiết hoá như UI framework…
  • Chúng ta chỉ viết code liên quan đến việc chi tiết hoá như UI design…
  • Ví dụ 4: đối với Android, trong frameworks and drivers:
    • Chúng ta sẽ import những UI dependency như material, ConstraintLayout… và sử dụng code trong interface adapter để tạo ra UI.
    • Ngoài ra, chúng ta cũng sẽ thực hiện các tác vụ như điều hướng giữa các screen, thực hiện tương tác với user, chỉnh setting cho app…

IV, Có phải bắt buộc là bốn layer ?

  • Clean architecture không bắt buộc chỉ có 4 layer. Tuỳ theo hệ thống, chúng ta có thể chia thành nhiều layer hơn.
  • Không có rule nào bắt buộc chỉ có 4 layer nhưng clean architecture phải tuyệt đối tuân thủ vào dependency rule.
  • Khi chúng ta đi vào trong từ layer level cao xuống các layer thấp hơn, mức độ trừu tượng hoá sẽ được tăng lên (mức độ phụ thuộc giảm đi) do đó chúng ta càng dễ tách biệt chúng để dễ dàng kiểm thử, maintain và thay đổi hơn.
  • Do đó, vòng tròn trong cùng là chung nhất. Chúng ta có thể chạy nó độc lập mà không sợ bị lỗi.

V, Crossing boundaries

  • Hình ảnh trên minh hoạ cho crossing boundaries, nó chỉ ra việc kết nối giữa use cases và interface adapters. Khi thực hiện use cases, phần lớn sẽ đều liên quan tới interface adapters. Trong suy nghĩ, chúng ta sẽ cần các implement của interface adapters để thực hiện các use case.
  • Nhưng theo điều 1, use cases không thể có source code của interface adapters. Vậy chúng ta phải làm như thế nào ?
  • Chúng ta giải quyết vấn đề này bằng dependency inversion principle.
  • Ví dụ 5: trong java language, chúng ta sẽ sử dụng interface và quan hệ kế thừa. Các use case sẽ sử dụng interface để trừu tượng hoá việc thực hiện của mình. Sau đó các component của interface adapters sẽ implement các interface đó.

VI, Data cross boundaries

  • Thông thường, data là những cấu trúc dữ liệu đơn giản nhất. Nó có thể là POJO object, basic structs…
  • Chúng ta không thể dùng chung data xuyên suốt ứng dụng bởi vì theo điều 2, các layer cấp cao hơn không làm thay đổi layer thấp hơn. Nếu chúng ta chỉ sử dụng chung data thì layer level có thể làm thay đổi data đó.
  • Điều quan trọng ở đây là hãy tách biệt và đơn giản hoá data ở các layer.
  • Ví dụ 6: Ở entities, chúng ta có Movie là movie data của hệ thống. Ở interface adapters, chúng ta chỉ muốn lưu một số property của movie vào database. Để tách biệt với entities, trong interface adapters, chúng ta phải tạo ra 1 MovieRow object để làm việc với database.

VII, Tổng kết

  • Việc tuân thủ theo clean architecture là không khó, nó giúp chúng ta giảm đi nhiều rủi ro có thể gặp phải.
  • Với việc tách biệt thành các layer và tuân theo dependecy rule, hệ thống của chúng ta sẽ dễ dàng test, maintain và thay đổi.
  • Khi một thành phần như giao diện, database work…bị lỗi thời, chúng ta có thể dễ dàng thay thế nó với effort bỏ ra là ít nhất.