Clean Architecture: Khi Nào Cần và Áp Dụng Ra Sao? Từ Android Dev tới Software Developer.

Su Ra GaSu Ra Ga
13 min read

-------- Tản mạn mở đầu -------- Trong buổi họp offline WeCommitX100 tại tuần thứ 2 của tháng 2, một câu hỏi từ người em đã khơi gợi trong mình nhiều suy nghĩ: "Chủ đề nào về Android sẽ vừa hữu ích, vừa gần gũi với anh em dev?". Mình tự hỏi, làm sao để chia sẻ kiến thức không chỉ mình hiểu, mà còn giúp mn trong giới lập trình ứng dụng đều có thể áp dụng được? Vậy là mình quyết định đặt bút viết về Clean Architecture.

Bài viết này có thể giúp bạn đưa ra quyết định sáng suốt về lựa chọn kiến trúc, một "nước cờ chiến lược" phù hợp cho từng dự án thực tế, từng mô hình công ty khác nhau.** Giống như việc bạn chọn chiến lược "đánh" cho từng trận đấu, không có một kế hoạch nào là hoàn hảo cho mọi tình huống. Kiến trúc phần mềm cũng vậy.

Author: Khuất Linh

Reading Time: 20 minutes

Time: Feb 19, 2025

Finish Date: Feb 28,2025 - 11:00 PM

Mục tiêu:

  1. Hiểu rõ bản chất và cấu trúc của Clean Architecture.

  2. Nhận diện những tình huống thực tế khi Clean Architecture thực sự phát huy sức mạnh.

1. Mở đầu: Tại sao lại sinh ra thuật ngữ Clean Architecture khi code application

Clean Architecture không phải là một khái niệm mới mẻ, nó xuất hiện ở khắp mọi nơi trong giới phát triển ứng dụng. Khi bắt đầu một dự án, câu hỏi đầu tiên thường là: "Ứng dụng này có dễ dàng mở rộng, bảo trì về sau không? Nên chọn kiến trúc nào để 'đánh' cho bài toán này?".

Giống như cách để học giỏi một môn học, một chuyên ngành, mình cần hệ thống hóa kiến thức đồ sộ. Tương tự, để xây dựng ứng dụng phức tạp, bạn cần kiến trúc để "hệ thống hóa" code, giúp dự án tiến xa mà không bị "ngợp" trong mớ hỗn độn.

Oki nếu đi một bài toán cụ thể bạn có thể đi rất sâu, nhưng nếu muốn Hiểu về kiến trúc, thì cần chọn cái nhìn toàn cảnh. Giống như đánh nước cờ quyết định nền móng vậy, tất nhiên về sau vẫn có thể thay đổi tuỳ vào tình huống.

Trong thực tế nhiều lúc mình sẽ tập trung xây móng hoặc vẽ bản thiết kế kiến trúc chi tiết trước khi bắt tay vào code. Nhưng đôi khi, thực tế lại phũ phàng. Dự án hóa ra không cần "bản vẽ" cầu kỳ đến vậy. Logic nghiệp vụ, UI, database... thời gian và tính chất của nó không cần tiến xa nên mình đã... tất cả "tống" vào một vài file ( đối với phần quản lý usecases). Mọi thứ nhanh chóng, tiện lợi... cho đến khi dự án lớn dần, mình nhận ra việc refactor code lại nhiều khi là một ác mộng.

https://www.blogger.com/video.g?token=AD6v5dyElC5Srf4zaDrhuJCxOBJ7vJr6u2N--PG_Caaxt_E-udgAoW9hq94L4uIerr9_b9dCK6GlLelk0MeP4YSl44mLN0DnEbmjh2zfA7V2ua1T1iQDJ26a3HCojZdyYmSdUgLyXhcZ - something like this video

Với một dự án mobile thương mại cá nhân hay các dự án nhỏ về giáo dục mà mình có toàn quyền thiết kế, mình đã mất nửa tuần "ngâm cứu" kiến trúc "tối ưu". Nhưng deadline dí sát nút, mình nhận ra: "Impossble to care about formal when it comes to time like this.". Thế là mình đã code như điên, mọi business logic nhồi hết vào một file. Tầm đó gọi là không còn gì để mất nữa, đó là một ngày hè của năm 2024. Điều đó giống như đọc bản vẽ một đằng và làm một nẻo vậy =))

Và mình thấy trong những dự án công ty nó cũng gặp tình trạng vậy, mà backend developer cũng "vật lộn" với code monolithic, frontend developer "khóc ròng" với component chồng chất logic trong đó. Mình đã từng tham gia một dự án mà logic "nằm" hết trong một file. Đọc code mà "toát mồ hôi hột" vì điều kiện lồng điều kiện, nhân lên mười mấy tầng, và những file code lên tới 1k-2k dòng do function viết trong đó. Điều đó khiến việc đọc hiểu và maintain code trở thành "cực hình". Nhưng vì deadline, vì tính chất dự án, refactor code là điều xa xỉ. Cảm giác "bó tay", "dập khuôn" theo kiến trúc hiện tại thực sự rất nản.

Vậy thì, câu hỏi đặt ra là: "Khi nào Clean Architecture thực sự cần thiết? Nó là gì và tại sao lại sinh ra thuật ngữ này?" Liệu nó có phải là "cây đũa thần" cho mọi dự án, hay chỉ là một "lý thuyết suông" xa vời thực tế? Và quan trọng nhất, làm sao để áp dụng nó một cách thật sự linh hoạt?

Để trả lời những câu hỏi này thì mình cần tìm hiểu sâu về nó cũng như những bài toán thực tế mình đã gặp, khi nào mình nên áp dụng nó.

2. Clean Architecture - Giải pháp khi hệ thống mở rộng và khi mình không làm chủ được code nhiều nữa.

Thật vậy, khi codebase phình to, team mở rộng, việc "lạc lối" trong code là điều khó tránh khỏi. Tìm một class, một function thôi cũng "đau đầu". Lúc này, Clean Architecture xuất hiện như một giải pháp "tối ưu", giúp mình "hệ thống hóa" code, dễ dàng quản lý, tìm kiếm và "giảm tải" sự phức tạp.

 Image: FE có thể là UI của Mobile hay Web,   BE cũng không bị ảnh hưởng hah :D

Image: FE có thể là UI của Mobile hay Web,

BE cũng không bị ảnh hưởng hah :D


Nguyên lý cốt lõi của Clean Architecture là sự phân tách trách nhiệm (Separation of Concerns), nhằm tạo ra các lớp (layers) độc lập, mỗi lớp đảm nhận một vai trò và chức năng riêng biệt.

Nhiệm vụ chủ yếu của Clean Architecture chính là tách biệt logic nghiệp vụ (business logic) ra khỏi các yếu tố bên ngoài như giao diện người dùng (UI), cơ sở dữ liệu (Database), và frameworks. Mục tiêu là giải phóng phần logic nghiệp vụ, giúp nó độc lập, dễ kiểm thử

(viết Unitest mà không cần phụ thuộc UI hay DBS), giúp nó dễ maintain hơn ( do dễ dàng khoanh vùng) và dễ mở rộng hơn (vì có sẵn khuôn mẫu process làm việc, mình chỉ cần áp dụng).

Có vẻ nghe vẫn trừu tượng, ok mình sẽ giải thích sâu hơn tí phần này bằng hình ảnh hah.

Clean Architecture tổ chức hệ thống phần mềm thành các lớp đồng tâm, mỗi lớp có trách nhiệm cụ thể và tuân thủ nghiêm ngặt Nguyên tắc Phụ thuộc (Dependency Rule). Các lớp chính bao gồm:

  1. Phần Vòng tròn trong cùng ( Entities ): Nằm ở trung tâm của kiến trúc, đại diện cho logic nghiệp vụ cốt lõi và dữ liệu nghiệp vụ của hệ thống. Entities là các đối tượng (objects) hoặc cấu trúc dữ liệu (data structures) , nó rất ít thay đổi và cũng là phần quan trọng nhất.

    Ví dụ trong hệ thống quản lý bán hàng trực tuyến: User, Product, Order, OrderItem. Các Entities này chứa các thuộc tính dữ liệu (ví dụ: Userid, name, email, password) và các phương thức (methods) thể hiện quy tắc nghiệp vụ (ví dụ: User có phương thức isValidPassword(), Product có phương thức checkStockLevel()) sẽ được quản lí và phát triển trong RepositoryImpl.

  1. Vòng tròn thứ 2 - Use Cases (Domain/Interactors)): Lớp này sẽ quản lý logic của ứng dụng và sẽ phụ thuộc/gọi xuống tầng Entities thông qua interface của repo, lớp này mô tả các quy trình nghiệp vụ cụ thể mà người dùng có thể thực hiện với hệ thống. Use Cases điều phối các Entities để thực hiện các tác vụ nghiệp vụ và tuân thủ các quy tắc nghiệp vụ được định nghĩa trong Entities.

    Ví dụ trong hệ thống quản lý bán hàng trực tuyến: RegisterUserUseCase, LoginUserUseCase, ViewProductListUseCase, CreateOrderUseCase, ViewOrderHistoryUseCase, CancelOrderUseCase. Mỗi Use Case đại diện cho một tác vụ nghiệp vụ cụ thể (ví dụ: CreateOrderUseCase mô tả quy trình tạo đơn hàng, bao gồm các bước kiểm tra thông tin người dùng, kiểm tra tồn kho sản phẩm, tạo đối tượng đơn hàng và các mặt hàng trong đơn, tính toán tổng giá trị đơn hàng, v.v.) mình sẽ nhóm các usecase này lại dựa vào tính chất của object vậy, tức là object có bao nhiêu hành vi thì mình sẽ nhóm các usecases tương ứng vào package tượng trưng cho object đó.

  1. Vòng tròn 3 - Interface Adapters: Lớp này chịu trách nhiệm chuyển đổi dữ liệu giữa định dạng của Use Cases và định dạng phù hợp với các lớp bên ngoài (UI, Database, External Services), nó bao gồm các thành phần chính:

    Presenters/Controllers : Chuyển đổi dữ liệu từ Use Cases sang định dạng hiển thị trên UI (ví dụ: định dạng dữ liệu JSON, XML, hoặc các đối tượng View Model trong kiến trúc MVVM) và cũng xử lý request từ UI ngược lại xuống dữ liệu, trong android thì có kiến trúc MVVM (Model-View-ViewModel) hoặc MVP (Model-View-Presenter), ViewModels hoặc Presenters thường đảm nhận vai trò này.

Repositories (Interfaces/Gateways): Định nghĩa các interface trừu tượng hóa việc truy cập dữ liệu (giấu đi cách thức triển khai), phần này mình có thể khởi tạo ở đây để quản lý với mục đích khiến Usecases chỉ tương tác với các Repository Interfaces, điều này giúp không phụ thuộc vào cách thức và nơi lưu trữ dữ liệu cụ thể, giấu đi phần logic trong tầng Entities.

  1. Lớp Frameworks & Drivers là lớp ngoài cùng, chứa đựng các chi tiết công nghệ cụ thể và dễ thay đổi nhất trong hệ thống. Đây là nơi triển khai các frameworks UI (ví dụ: React, Angular, Jetpack Compose), web frameworks (ví dụ: Spring Boot, Django), hệ quản trị CSDL (ví dụ: PostgreSQL, MySQL, Room), Object-Relational Mapping (ORM) libraries, thư viện mạng (networking libraries), các API của bên thứ ba, và các drivers cho thiết bị ngoại vi.

    Ví dụ trong dự án Android: mình sẽ xử lý các Activities, Fragments, Composables (UI layer), Room persistence library (Database layer), Retrofit library (Network layer), Dependency Injection frameworks (Hilt, Dagger), và các Android System APIS.

Đi vào bài toán cụ thể, ví dụ với một hệ thống quản lý bán hàng nhỏ thì mình sẽ phân tách cấu trúc như sau:

  1. Entities: Tại đây mình sẽ định nghĩa các object cần thiết User, Product, Order, OrderItem, sau đó dựa vào các usecase từ domain mà mình sẽ viết các chức năng tương ứng trong repoImpl. Những file như mapper giữa kết nối mình cũng sẽ để ở đây

  2. Domain: Tại đây mình sẽ viết các usecase tương ứng với các hành vi của object đó: Register/Login/UserUseCase, LoginUserUseCase, ViewProductListUseCase, Create/view/cancelOrderUseCase, đồng thời, mình cũng sẽ viết các interface của repo để quản lý tại đây: UserRepository, ProductRepository, OrderRepository.

  3. Interface Adapters: Tại đây mình sẽ viết các Controller để handle các request từ UI và gọi tới các usecase tại đây. UserController, ProductController, OrderController, hay với Android thì sẽ chia theo từng màn hình mà có các viewmodel tương ứng rồi inject các Usecase tương ứng.

lâu không code hệ thống khác đâm ra hơi lú giữa các hệ thống vì bên android cách chia của nó hơi khác =)).

  1. Frameworks & Drivers: Tại đây mình sẽ viết UI, có thể là React/Angular, SpringBoot, Django, Node.js, hoặc với android là Jetpack Compose, Activity, Fragment,

Ngoài ra, còn rất nhiều ví dụ khác: Quản lý nội dung (CMS) hay Ngân hàng số ( Digital Banking Application) chẳng hạn. Mình chỉ thông qua một ví dụ nhỏ để mn thấy được các viên gạch cơ bản của nó, nhưng với thực tế thì tuỳ vào tính chất dự án, không phải lúc nào mình cũng đấm và nghĩ chi tiết được như vầy, CA chỉ là khuôn mẫu lý tưởng thôi, tuỳ vào những bài toán thực tế sẽ buộc bạn phải lựa chọn linh động.

3. Các tình huống thực tế với Clean Architecture và cách giải quyết

  • Có lẽ đây là tình huống phổ biến nhất với những dự án/ bài toán nhỏ:

Tình huống 1: Deadline gấp rút, nguồn lực hạn chế, ưu tiên tốc độ phát triển.

Ở đây, mình không có nhiều thời gian để lựa chọn kiến trúc, bắt buộc phải ra quyết định, vì vậy, clean Architecture chỉ ở mức cơ bản chia theo dạng package(ui,domain,data) sẽ là hợp lý không cần thiết phải cầu kỳ thêm. MVC/MVVM đơn giản hơn có thể phù hợp để tối ưu tốc độ trong trường hợp này.

Tình huống 2: Đội ngũ Phát triển Thiếu Kinh nghiệm về Clean Architecture

  • Trong tình huống này, bắt buộc phải có người phá băng để nhóm còn lại có thể học theo, mà việc này cần xây thành từng bước, có lộ trình, bắt đầu với module nhỏ, cần có thời gian để apply và có process rõ để mọi người có thể follow theo.

    → Tóm lại: dựa vào thời gian, độ phức tạp của bài toán ( về mặt thời gian và chi phí), kinh nghiệm của dev mà điều này có thể được cân nhắc, đây là yếu tố mình hay sử dụng hồi xưa khi phát triển ứng dụng 1 mình hay bàn luận với 1 nhóm nhỏ.

Tình huống 3: Dự án hiện tại đang không follow theo và có mong muốn tái cấu trúc dần.

  • Đối mặt với tình huống này, leader sẽ xác định các module hoặc tính năng quan trọng nhất của hệ thống trước, đầu tiên là vẫn giữ code cũ, tạo ra một modules hoạt động song song (tách ra với chức năng tương tự). Chia việc tái cấu trúc thành từng giai đoạn nhỏ tích hợp dần với quá trình phát triển (sprint), có lộ trình rõ ràng. Trong quá trình phát triển mới, cần phát triển ưu tiên Domain Layer trước (Usecase) sau đó là entities, như vậy vậy migrate sẽ dần được hình thành và ổn định đến lúc mà phần code cũ có thể không cần đến nữa.

4.Cách sử dụng Clean Architecture để có thể quen dần với những nguyên tắc của nó.

Kiến trúc này rất mạnh, nhưng nó không phải lúc nào cũng sử dụng được, mình cần có sự linh hoạt trong việc áp dụng để biến nó thành đáp án đúng cho bài toán mà nó có đất dùng.

  • Không Over-engineering: Dự án nhỏ, đơn giản có thể không cần Clean Architecture phức tạp.

  • Linh hoạt trong Số lượng Lớp, Cấu trúc Thư mục và modules: Điều chỉnh phù hợp với dự án, đảm bảo điều kiện phân tách rõ trách nhiệm và tuân thủ nguyên tắc phụ thuộc.

  • Có thể bắt đầu từ nhỏ và từng Bước: Áp dụng cho module quan trọng trước, mở rộng dần. quá trình này sẽ lặp dần theo thời gian và sự phát triển của dự án.

  • Refactoring và Tái cấu trúc Code: Sau khi phần logic ổn định thì với dự án có lẽ phần UI và domain sẽ chịu nhiều yêu cầu thay đổi, hoặc sau khi xong một event quan trọng ( trước đó tiến độ rất gấp), khi đó việc refactoring code là cần thiết để giúp code sau dễ maintain hơn.

  • Cần Tham khảo Best Practice khi học code tại các dự án để có thể có một góc nhìn về cách CA hoạt động trong dự án/ các mô hình dự án hiện tại.

Tóm lại, nó không phải là cái gì đó quá thần thánh, chỉ là điều cần làm khi mô hình code phát triển về lâu về dài và yêu cầu một hệ thống đáng tin cậy cao thôi, còn nhiều kiến trúc khác, CA chỉ là một trong các sự lựa chọn được tin cậy thôi.

Có lẽ ở bài viết đầu tiên, mình sẽ chọn nước cờ là nội dung cơ bản nên nó sẽ không có nhiều tính sáng tạo và đặc sắc lắm, vì mục tiêu trong sự nghiệp là tìm ra được hướng đi với giải pháp về hệ thống nên điều này chỉ là một phần kiến thức nhỏ và cơ bản. mình sẽ tiếp tục tổng hợp và phát triển cô đọng hơn trong các chủ đề sau, sẽ chủ yếu về Tech & LifeSkill.

Reference:

  1. Một người anh Senior cùng team đã dạy mình rất nhiều thứ, một trong số đó là cách thay đổi tầm nhìn và tư duy và Dev Application trong thực tiễn về nghề công nhân code =))

  2. How Clean Architecture in Android can help for an Efficient Estimation - ProAndroidDev

Nour El Islam SAIDI

  1. Understanding Clean Architecture

  2. Architecting Code Excellence: 20 Essential Tips for Crafting Clean and Maintainable Software

  3. Clean, Easy & New- How To Architect Your App: Part 3 — Network Calls - Britt Barak

  4. Do You Have to Learn Clean Architecture as a Beginner? - Philipp Lackner

  5. Clean Architecture diagram

1
Subscribe to my newsletter

Read articles from Su Ra Ga directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Su Ra Ga
Su Ra Ga

Một khi đã Phóng lao, sẽ không có đường lui, vì vậy, hãy chịu trách nhiệm cho lời nói của mình! 🔥