Clean Architecture là gì? Cách tiếp cận và sử dụng Clean Architecture dễ dàng nhất.
Last updated
Last updated
Clean Architecture thì thực chất là một phong cách lập trình thôi, và tác giả là Robert C. Martin. Mục đích của nó nhằm tới giải quyết những vấn đề mà anh em lập trình viên đang gặp phải đó là: mở rộng có dễ không, bảo trị dễ không, khả năng kiểm thử tốt không. Thì đó là những vấn đề nhức nhối nhất của giới lập trình viên chúng ta. Nhưng hiện nay có rất nhiều cách để giải quyết vấn đề này, thì clean architecture là một trong những cách đó.
Bên cạnh đó, trong clean architecture có 1 phần vô cùng quan trọng là dependency. Tức là sẽ cố gắng làm giảm sự phụ thuộc giữa các module với nhau.
Để triển khai clean architecture trong lĩnh vực phát triển phần mềm đòi hỏi có nhiều vấn đề giải quyết và cần đầu tư nhiều thời gian vào nó. Vậy sau đây là một số quy tắt bất di bất dịch mà bạn cần tuân thủ khi triển khai clean architecture.
Thì ở đây, ông Robert muốn nhấn mạnh mỗi lớp đều có một nhiệm vụ cụ thể và rõ ràng. Các module có phụ thuộc thì chỉ phụ thuộc vào mức độ trừu tượng, không được phép phụ thuộc vào chi tiết của lớp phụ buộc.
Để làm rõ hơn ví dụ này, tôi có ví dụ cụ thể về một ứng dụng quản lý công việc (Task Management Application)
Trong package Entity
Thì ở lớp này có nhiệm vụ là quản lý các thông tin cơ bản của 1 task.
Trong package UseCase
Nhiệm vụ: Tạo mới một object công việc và save xuống database.
Phần này chỉ phụ thuộc vào các thực thể và giao diện, nó sẽ không phụ thuộc vào chi tiết của repository, tức là không cần biết reposiory làm với CSDL gì, nghiệp vụ gì,...
Hệ thống sẽ không phụ thuộc vào bất kỳ class hay framework nào. Mức độ phụ thuộc ở đây chỉ là ở mức trừu tưởng. Các module mà ta sử dụng để phụ thuộc thì không được phép sử dụng trực tiếp các thành phần bên trong đó. Hãy để tôi ví dụ giữ việc phục thuộc ở mức độ trừu tượng và không trừu tượng nhé.
Đây là một ví dụ được sử dụng EF trong .NET. Ở đây bạn có thể thấy phần TaskService phụ thuộc vào ApplicationDbContext. Phần context này thì có thành phần Task.
Thì ở đây, nếu mà trong ApplicationDbContext có sự thay đổi gì đó như là đổi tên biến Tasks thành Task đi thì có phải bạn phải đi rename hết các nơi mà gọi tới biến này đúng không? Thì đó là sự phụ thuộc đó, hay thậm chí trong này có đổi type hoặc logic đi chăng nữa thì có phải bạn phải sửa hết các nơi mà sử dụng nó không? Thì đó là vấn đề mà giảm sự phụ thuộc để dễ phát triển, tiếp theo đây tôi sẽ tối ưu phần này để bạn hình dung rõ hơn nhé.
Bạn có thể thấy, tôi đã sử dụng phần interface rồi đó. Và đó là giao diện, bạn sẽ không cần biết sẽ lữu xuống database như thế nào, chỉ cần bạn gọi function là được. Còn việc lưu dữ liệu như thế nào thì kệ nó, đã có 1 class impl lo rồi, bạn đâu cần phải đi add rồi savechange như ở trên.
Rồi tôi nói tiếp về sự thay đổi, tôi giả sử hiện tại tôi dùng SQL Server để lưu Task, nhưng không tương lai tôi muốn đổi sang dùng NoSQL thì sao, không có gì xảy ra ở đây cả, vì đây là interface. Điều mà tôi quan tâm là lớp impl của Repository đó thực thi theo kiểu NoSQL thôi. Dẫu sao thì chỗ TaskService cũng chẳng thay đổi gì.
Như đã nói và để ví dụ ở trên, thì ở đây chắc tôi cũng nói đơn giản thôi. Nếu mà ta đưa ra giải pháp interface để giảm sự phụ thuộc thì việc implement và thực thi công việc gì thì là việc của lớp đó thôi.
Như tôi ví dụ ở trên, việc impl dùng SQL hay NoSQL thì là do phần lớp impl thôi. Còn việc ở service thì không ảnh hưởng gì, thậm chí tôi muốn thêm phần lưu dạng JSON thì cũng được luôn, giả sử tôi có 3 lớp như sau:
Dù có cỡ nào đi nữa thì các module phụ thuộc qua interface thì thay đổi bên trong là điều dễ dạng và dễ dàng triển khai theo kiểu mock interface.
Có thể đây là một nguyên tắc vô cùng hay, như tôi đã giải thích ở trên thì cũng giống như việc không phụ thuộc vào bạn làm việc với CSDL nào ở mục 3. Phần này cũng vậy, bất kể bạn làm việc với Controller hay Console thì nó không phải là vấn đề. Vì việc bạn cần chú ý là dùng interface trong use case và sử dụng DI vào Controller hay Console mà chỗ bạn cần gọi đến.
Thì lúc này trong service chỉ cần biết Interface là được. Ví dụ ở trên là dùng 1 interface TaskUseCase có hàm Add(Task task); thì lúc này ở Controller chỉ cần gọi hàm này là được, còn việc lưu dữ liệu, gửi email, đánh dấu ... nôm na là bussiness thì do lớp triển khai đảm nhiệm, ở Controller không cần quan tâm đến.
Trên đây là một vài sơ bộ về clean architecture mà tôi muốn gửi đến bạn. Hy vọng rằng nó sẽ có ích với bạn và có thể mở mang tầm mắt nhiều hơn, và giúp bạn hiểu rõ hơn về Clean Architecture.