Tôi nghĩ rằng ứng dụng Spring không nên cấu trúc source code theo kiểu package theo layer. Theo tôi, nên package theo chức năng sẽ chuẩn hơn.
Đầu tiên, tôi sẽ tóm lược mỗi kiểu một cách ngắn gọn như dưới đây.
“Package theo layer” (hay còn gọi là “Chia folder theo loại” với anh em không lập trình)
Kiểu này thì nhóm source code theo các package/ thư mục dựa trên lớp kiến trúc của nó, ví dụ:
“Package theo feature” (hay còn gọi là “Chia folder theo chức năng” với anh em không lập trình)
Hướng tiếp cận này thì khác kiểu trên ở điểm là nhóm tất cả những file liên quan đến 1 chức năng vào 1 chỗ, ví dụ:
Xu hướng
Tôi quan tâm tới vấn đề này cũng đã lâu lắm rồi. Khi mà google thử cụm package by feature vs package by layer là đủ để thấy lượng người theo phe ủng hộ việc structure source “theo chức năng” đang tăng mạnh ra sao. Tôi tất nhiên cũng nằm trong nhóm này
Nhưng không chỉ lập trình viên backend mới ủng hộ nó. Angular (1 trong những Front-end framework nổi tiếng hỗ trợ Single Page Application) thậm chí còn đưa ra cách cấu trúc source này trong Coding standard của họ.
Cấu trúc source dự án dùng framework Spring
Không như vô số những bài viết so sánh về ưu và nhược của từng hướng tiếp cận, tôi sẽ chỉ tập trung vào trường hợp sử dụng của dự án dùng framework Spring.
Cấu trúc truyền thống của các ứng dụng Spring CRUD (nếu không sử dụng Spring Data REST) được chia thành 3 lớp: web / service / persistence. Đa phần dự án Java/Spring mà tôi từng gặp đều follow theo kiến trúc này.
Coupling
Package theo layer khả năng cao bắt nguồn từ thế kỷ trước, khi mà kiến trúc theo layer được sử dụng như cơ chế tạo liên kết yếu (decoupling) điển hình. Thực tế khi tôi muốn kiến trúc dự án “decoupling” thì mọi người thường đưa ra câu trả lời là package theo layer. TÔI PHẢN ĐỐI. Theo tôi, package theo layer là một trong những lý do chính yếu gây ra liên kết chặt (tight coupling).
Khi mà các bạn viết modifier cho class trong 1 dự án theo kiến trúc package theo layer, từ khóa nào được nghĩ đến trước tiên? Tôi cá đó là public. Liệu rằng public modifier giúp decoupling hay không? Tôi đoán không ai trả lời có cho câu hỏi này cả.
Tại sao anh em dev lại sử dụng public access modifier tràn lan như vậy? Đó chính xác là do source code dự án được kiến trúc theo kiểu layer. Repository class cần phải để public, bởi vì nó cần được truy xuất từ service package. Và service package thì cũng cần được để public bởi vì nó cũng cần được truy xuất từ web package. Khi mà tất cả đều public, thì thật sự rất khó để duy trì kỷ luật, dễ dàng dẫn đến 1 đống source rối loạn và cực khó maintain sau này.
Khi mà package theo feature, nếu bỏ access modifier (package-private) thì class UserRepository vẫn sẽ có thể sử dụng được bởi UserService, bởi vì chúng nằm chung package với nhau. Trong trường hợp chúng ta quyết định chỉ UserController sử dụng UserService, thì chỉ cần để access modifier là package-private (không có access modifer), và 2 lớp kia vẫn tương tác với nhau bình thường, vì chúng nằm chung trong 1 package. Khi đó, đa phần các class sẽ chỉ cần để modifier là package-private. Như vậy, các lập trình viên sẽ cần đưa ra lý do thật sự chính đáng khi muốn để 1 class là public.
Scaling
Và tiếp theo, điều gì sẽ xảy ra khi mà project có trên 10 class trong mỗi loại layer web/service/persistence? Lập trình viên bắt đầu có xu hướng nhóm các lớp vào những sub-package. Nhưng họ phân loại chúng kiểu gì? Từ kinh nghiệm của tôi, chúng lại đa phần được phân theo chức năng (feature). Do vậy, chúng ta lại thường thấy kiểu structure như sau ở các dự án lớn hơn:
Nhìn thật sự rất gai mắt.
Chuẩn bị cho tương lai
Như các “chuyên gia” nhận định, không nên áp dụng kiến trúc Microservices cho 1 dự án phát triển từ đầu. Cũng có vẻ đúng. Nhưng chẳng phải sẽ hợp lý hơn nếu bạn chuẩn bị ứng dụng monolith của mình sẵn sàng chia nhỏ ra làm nhiều project khi mà sản phẩm bắt đầu tăng trưởng sao?
Thử tưởng tượng, bạn cần tách 1 microservice ra khỏi project monolith của mình. Hoặc chia toàn bộ project ra làm các microservices. Tôi mong mọi người sẽ hiểu rằng, không có kiểu kiến trúc micro-service tách biệt theo layers. Tách biệt theo chức năng sẽ được sử dụng. Vậy thì cấu trúc project kiểu gì sẽ dễ tách biệt thành các microservices hơn? Cái mà có public class có thể sử dụng được bởi bất kỳ public class từ bất kỳ package nào á (package theo layer)? Hay là cái mà đã được chia thành những package độc lập và private (package theo feature)? Tôi tin rằng câu trả lời đã quá hiển nhiên rồi nhỉ.
Kết luận
Package theo feature đơn giản nhưng thật sự là 1 cơ chế rất mạnh mẽ cho việc decoupling. Nên lần tới, nếu có lập trình viên nào cuồng layer đưa ra lý do biện luận là cấu trúc source project theo layer để decoupling, thì mọi người làm ơn hãy sửa lại sự hiểu nhầm đó của họ nhé. Theo tôi “thói quen + định kiến” là thứ duy nhất giải thích tại sao package theo layer vẫn còn tồn tại đến ngày nay.