logoMột hệ thống khó hiểu thì cũng khó thay đổi
Giằng co giữa REP-CCP-CRP

Sau khi nắm bắt được các nguyên lý thành phần (component principles) về cách làm thế nào để tạo ra được những thành phần có tính cohesion cao trong hệ thống đảm bảo:

  • Nguyên lý CCP: Tạo ra được những thành phần có tính module hóa, cohesion cao.
  • Nguyên lý CRP: Tối ưu những thành phần để tái sử dụng trong hệ thống.
  • Nguyên lý REP: Giúp đóng gói và phát hành các thành phần để có thể tái sử dụng tại nhiều nơi.

Ta thấy rằng chúng có những sự mâu thuẫn với nhau, tạo nên sự giằng co giữa chúng. Ta cần xem khi ta quyết định kiến trúc hệ thống phần mềm của ta ưu tiên đi theo nguyên lý nào thì kết quả ta nhận được sẽ là những gì. Tùy theo giai đoạn phát triển của dự án mà ta sẽ cần điều chỉnh kiến trúc của mình sao cho phù hợp.

REP

Trong mục REP, ta đã có nhận xét rằng nếu ta không cần tái sử dụng lại một đơn vị phần mềm thì ta không cần đóng gói và phát hành nó, mà thay vào đó, ta sử dụng code trực tiếp ở những thành phần trực tiếp triển khai như các web service, batch job,... tức là phần adapter của ứng dụng (theo kiến trúc Hexagonal).

Vì vậy, ta sẽ luôn dùng REP cho việc tái sử dụng, hay trong kiến trúc module hóa mà ta đang hướng tới.

CCP

Ưu tiên CCP, tức là gom những class thay đổi vì cùng lý do và cùng thời điểm lại với nhau sẽ giúp cho ta tránh được tình trạng có quá nhiều bản phát hành khi triển khai một tính năng đơn giản.

Vì sao lại thế? Nếu ta chỉ đơn thuần gom các class lại với nhau theo những module không có tính cohesion cao, vậy những thứ trong đó sẽ được kích hoạt thay đổi vì những lý do rất khác nhau. Điều này dẫn tới chỉ một tính năng đơn giản nhưng cũng có nhiều thành phần bị thay đổi để đáp ứng.

Khi tập trung vào CCP, ta đang tập trung vào khả năng bảo trì của hệ thống. Ta mong muốn những thay đổi nếu có của hệ thống sẽ chỉ tập trung vào một số lượng hữu hạn các thành phần thay vì trải dài ra rất nhiều.

CCP khiến cho thành phần trở nên lớn hơn.

CRP

Nhưng nếu chỉ tập trung vào CCP, các thành phần dù có tính cohesion cao nhưng sau này có thể phát sinh những tình huống mà người dùng của ta sẽ chỉ cần những phần nhỏ trong đó. Nếu ta cứ bám theo CCP mà bỏ qua CRP, người dùng sẽ thấy mình đang sử dụng một thư viện ôm đồm quá nhiều thứ hơn là mình muốn. Đó sẽ là rủi ro cho họ. 

Cụ thể hơn bạn có thể xem trong mục bàn về CRP.

Khi sử dụng CCP mà việc tái sử dụng trong hệ thống là cao, các thành phần sử dụng thành phần dùng chung sẽ có những bản phát hành thường xuyên.

Lúc này, ta cần giảm CCP xuống và ưu tiên CRP. Khi đó, các thành phần sẽ có kích thước giảm xuống, tức CRP có xu hướng tạo ra những thành phần nhỏ hơn. CRP giúp cho ta tránh phát hành những phiên bản không cần thiết.

REP + CCP

Kết hợp REP và CCP có thể được diễn đạt thế này: Gom những class lại vào những thành phần có thể tái sử dụng sao cho các thành phần này có tính cohesion cao.

Nhưng vì, như trong CRP đã mô tả, CCP có xu hướng tạo ra những thành phần lớn nên không tối ưu cho khả năng tái sử dụng. Chính vì thế, các thành phần sử dụng (using components) sử dụng các thành phần dùng chung này (used components) sẽ phải đối mặt với khả năng phải phát hành liên tục các phiên bản không cần thiết.

Hình 1: Component X được sử dụng tại 3 nơi: Component A, Component B, và Component C

Như trong hình 1, Component X đảm bảo nguyên lý CCP được sử dụng tại ba nơi: Component A, Component B, và Component C. Nhưng 3 vị khách hàng này lại chỉ sử dụng những phần khác nhau trong X. Khi thay đổi trong X tới từ A (nghĩa là ta thay đổi X để đáp ứng yêu cầu của A) thì dù B và C không liên quan nhưng cũng phải biên dịch, phát hành những phiên bản mới của chúng.

REP + CRP

Nếu ta ưu tiên REP và CRP, nghĩa là ta đã bóc ra nhiều thành phần nhỏ để tối ưu tái sử dụng và giảm ưu tiên cohesion xuống. Điều này có thể dẫn tới chỉ một yêu cầu mới đến, ta sẽ cần phải thay đổi rất nhiều thành phần khác nhau, khiến nhiều team phải tham gia, và do đó, ta sẽ cần phải thận trọng đáng kể vì lúc này đòi hỏi sự phối hợp chặt chẽ giữa các team.

Hình 2: Component X được phân rã thành 3 component nhỏ hơn X1, X2, X3 để tối ưu việc tái sử dụng

Như trong Hình 2 cho thấy, ta đã phân rã X thành 3 thành phần nhỏ hơn, tối ưu hơn cho việc tái sử dụng: X1, X2, và X3. Nhưng vì nghiệp vụ trong cả 3 thành phần này mang tính liên quan chặt chẽ với nhau (tính cohesion cao) nên có những yêu cầu mới khiến cho ta phải cập nhật cả 3 thành phần này. Điều này kéo theo nhiều thành phần khác cũng phải phát hành theo.

CCP + CRP

Bỏ qua REP, tức là như tôi đã đề cập, ta không đóng gói và phát hành các thư viện dùng chung. Code sẽ nằm ở những thành phần triển khai trực tiếp như web service, batch job.

Theo công thức này thì ta tạo ra những thành phần có tính cohesion cao, sau đó tái sử dụng bằng cách copy/paste???

Kết luận

Từ những phân tích trên, ta có biểu đồ Tension Diagram sau:

Hình 3: Tension Diagram giữa REP - CCP - CRP

Một hiểu lầm tai hại nếu ta nghĩ ta chỉ được lựa chọn hoặc hai nguyên lý này và bỏ qua nguyên lý còn lại. Ta có thể chọn cả ba nguyên lý và quan trọng là ta đặt trọng tâm của thiết kế tại thời điểm đó ở đâu.

Như trong ví dụ trên, tôi có thể lựa chọn điểm nằm giữa của (REP + CCP) và (REP + CRP) bằng cách chọn ưu tiên (REP + CCP) hơn. Kết quả là, tôi không loại trừ hẳn (REP + CRP) mà thay vào đó, tôi tìm ra những thành phần mang tính dùng lại cao và bóc nó ra thành một thành phần mới. Chỗ còn lại vẫn đảm bảo tính cohesion cao. Như hình dưới đây:

Hình 4: Lựa chọn điểm hợp lý giữa ba nguyên lý

Như hình trên, tôi chấp nhận có những yêu cầu khiến cả X1 và X2 thay đổi, và do đó khiến cho các client của chúng phải tái triển khai. Nhưng ngược lại, có những yêu cầu sẽ khiến tôi chỉ cần tác động vào số lượng thành phần nhỏ hơn.

Kết luận (lại)

Nó phụ thuộc vào kỹ năng, trạng thái của đội phát triển tại thời điểm đó. Tuy nhiên, ta cần nhận thức rằng, đội phát triển của chúng ta sẽ luôn thay đổi theo thời gian. Ví dụ, giai đoạn đầu của dự án thì CCP là nguyên lý quan trọng hơn nhiều so với REP bởi vì ở giai đoạn đó thì khả năng phát triển dự án quan trọng hơn nhiều so với việc làm thế nào để tái sử dụng các thành phần.

Các dự án có xu hướng khởi đầu ở bên phải của biểu đồ, ở đó khả năng tái sử dụng bị hi sinh. Khi dự án trưởng thành hơn và các dự án khác phát triển dựa trên nó, thì nó sẽ chuyển dịch dần sang bên trái. Điều này có nghĩa là cấu trúc thành phần của một dự án có thể biến đổi theo thời gian và sự trưởng thành của nó. Khi đó, dự án sẽ có nhiều việc cần làm xoay quanh phát triển và tái sử dụng hơn là xoay quanh những thứ mà dự án đã thực sự làm được.

Một điểm mà bạn cần ghi nhớ: KIẾN TRÚC CỦA MỘT HỆ THỐNG PHẦN MỀM LUÔN THAY ĐỔI THEO THỜI GIAN. Nếu kiến trúc đó vẫn giữ vững theo những bản cập nhật, một là những yêu cầu của khách hàng khá "hiền lành", hai là bạn đang khá khổ sở khi bảo trì một hệ thống kiến trúc như vậy.

P/s: Tôi có một liên tưởng như sau để dễ nhớ hơn về 3 nguyên lý thành phần này, ý tưởng này thể hiện trong hình sau:

Hình 5: Vai trò tưởng tượng của 3 nguyên lý thành phần REP, CCP, và CRP

Kiến trúc của thành phần thay đổi theo yêu cầu. Vậy Requirements sẽ là đầu vào cho "nhà máy quản lý vòng đời của components" (Component Lifecycle Factory).

Bên trong nhà máy này, CCP và CRP tranh đấu, giằng co với nhau để tối ưu giữa khả năng bảo trì và khả năng tái sử dụng.

Sau khi có kết quả, REP sẽ đóng gói và phát hành các thành phần.

 

Tham khảo:

Chính sách trong hệ thống phần mềm.

Reuse/Release Equivalence Principle - REP.

Common Closure Principle - CCP.

Common Reuse Principle - CRP.

Single Responsibility Principle - SRP.

Open Closed Principle - OCP.

Bình luận
Gửi bình luận
Bình luận