Nguyên tắc thiết kế về Component Cohesion trong kiến trúc phần mềm (Principles of Component Cohesion in Software Architectures)
Bài viết đến từ anh Nguyễn Ngọc Hải – Quản lý cao cấp Kiến trúc Giải pháp
Enterprise Architect team @Techcombank
Tổng quan
Trong quy trình phát triển phần mềm, các nguyên tắc về thiết kế component là không thể thiếu để có một hệ thống được tổ chức quy củ, dễ mở rộng và dễ quản lý. Components trong bài dùng để chỉ đến các thành phần nhỏ nhất, thuộc về một hệ thống, có thể triển khai được một cách tương đối độc lập với các component khác, có thể là micro-service, hoặc library, package, v.v..
Các nguyên tắc thiết kế dưới đây giúp hướng dẫn developer và kiến trúc sư đặt class nào vào component nào (component cohesion), và những component đó liên quan gì đến nhau (component coupling).
Việc có các nguyên tắc thiết kế đặc biệt quan trở nên quan trọng khi các hệ thống phần mềm phát triển ngày một lớn và phức tạp. Lúc này chỉ riêng việc quyết định đặt class nào vào component nào cũng có ảnh hưởng lớn đến cả hệ thống, nhất là tính chính xác, dễ sử dụng, dễ vận hành của hệ thống phần mềm.
Những nguyên tắc này thường đã và đang được bắt buộc sử dụng thông qua các framework thường thấy, hoặc các quy trình phát triển, release thông thường. Nhưng nó cũng rất hữu ích khi ta nắm được tại sao các framework được đóng gói và release như vậy, và liên hệ lại với những nguyên tắc hết sức cơ bản như sau.
Ở đây ta thảo luận 3 nguyên tắc về component cohesion dưới đây.
REP: The Reuse/Release Equivalence Principle – Việc dùng/dùng lại tương ứng với việc phát hành (release).
Về cơ bản, REP nghe có vẻ hiển nhiên. Ta chỉ sử dụng, hoặc sử dụng lại, những component được ghi lại qua một quy trình phát hành và có phiên bản cụ thể. Vì thực tế nếu không thực hiện quy trình phát hành, nâng số version, không có cách nào để biết được các component đang dùng lại này có tương thích với nhau hay không. Và việc phát hành component cùng tài liệu cũng thể hiện cho developer những tính năng nào sắp sửa được phát hành, có những thay đổi gì.
Vì vậy, ta sẽ không định nghĩa class của mình là “có thể dùng lại được”, khi nó chưa được đánh phiên bản, và chưa được phát hành chính thức. Đồng nghĩa với việc khái niệm “dùng/dùng lại” được gắn với việc phát hành mỗi component.
Ví dụ với trong hệ thống ngân hàng, khi mỗi team đang trong quá trình phát triển ứng dụng trên hệ thống quản lý thẻ, thì hiển nhiên các hệ thống khác cũng sẽ không dùng đến ứng dụng đó, cho đến khi team thẻ release ứng dụng đó trên các môi trường, cùng với đánh số version mới, và có chi tiết chức năng thay đổi.
Thông thường, developer sẽ được thông báo cụ thể mỗi khi có phiên bản mới và quyết định rằng họ sẽ dùng phiên bản mới đó, hay tiếp tục dùng phiên bản cũ của component đó. Vì vậy quy trình release phải bao gồm quy trình thông báo và các tài liệu chi tiết để người dùng (thường là developer) quyết định có nên tích hợp phiên bản mới hay không.
CCP: The Common Closure Principle – Các class thay đổi cùng nhau thì nên được gộp chung với nhau.
Nguyên tắc này hướng dẫn khi nào thì đặt một class nào đó vào một component. Một cách ngắn gọn, các class, mà có cùng một mục đích, hoặc một “lý do để thay đổi”, lý do đó thường là thay đổi tính năng kỹ thuật, hoặc tính năng về kinh doanh, thì nên được gom chung vào một component. Lợi ích là mỗi khi có một thay đổi xảy ra (tính năng mới, thay đổi tính năng cũ, v.v.), nó sẽ xảy ra ở một số ít component thôi, giúp cho việc phát triển thuận tiện, developer cũng chỉ cần hiểu biết về 1 số ít component.
Ví dụ đơn giản trong một hệ thống nhiều microservice, thì những class liên quan đến tài khoản sẽ nằm trong microservice về quản lý tài khoản, thay vì mỗi microservice tự định nghĩa 1 số class về tài khoản riêng. Như vậy nếu có sự thay đổi logic của việc xử lý tài khoản (có thể là cách trả về lỗi, cách đồng bộ dữ liệu tài khoản, v,v..), thì chỉ cần thay đổi 1 chỗ ở microservice đó thôi.
Đây là một nguyên tắc rất quan trọng, vì khi ta thay đổi 1 tính năng, ta muốn những cái thay đổi đó xảy ra chỉ 1 component, thay vì thay đổi ở nhiều component khác nhau. Bằng cách này, thay đổi được cô lập ở 1 component duy nhất, và ta chỉ cần triển khai 1 component bị thay đổi đó mà thôi. Những component, mà không phụ thuộc đến component bị thay đổi, thì cũng không cần kiểm thử lại và triển khai lại nữa, làm cho việc vận hành dễ dàng hơn.
Trên thực tế, điều này hiếm khi xảy ra vì thường một thay đổi về tính năng sẽ ảnh hưởng nhiều component. Nhưng nguyên tắc này nói rằng, bằng kinh nghiệm, ta nên đặt các class thường thay đổi cùng nhau vào chung một component.
CRP: The Common Reuse Principle – Các class được gọi cùng nhau thì nên được gộp chung với nhau.
CRP nói rằng ta không nên ép các hệ thống, hoặc component phụ thuộc vào những component mà chúng không sử dụng đến. Tức là các component không nên lớn quá mức tối thiểu cần thiết. Ta phải luôn nhớ rằng các class trong 1 component sẽ được dùng cùng nhau. Điều này có nghĩa rằng ta muốn đảm bảo rằng class trong một component là những class chúng ta không thể chia tách ra được, rằng là những component khác luôn sử dụng tất cả các class đó cùng 1 lúc.
Ví dụ, trong microserservice về tài khoản có nhiều chức năng liên quan đến tài khoản (đặt tên là service A). Có một microserservice B nào đó liên quan đến giao dịch, cần sử dụng class của A.
Nếu trong trường hợp A có thay đổi liên quan đến cách đồng bộ tài khoản chẳng hạn, A sẽ cần phát hành và triển khai phiên bản mới. Điều này cũng dẫn đến B cần đánh giá thay đổi của A, thậm chí nếu phiên bản cũ của A không còn tương thích, B cũng cần được phát hành và triển khai phiên bản mới. Như vậy trong trường hợp này, ta có thể cân nhắc áp dụng CRP để tách component A làm 2 microservice hoặc 1 microservice, 1 library package chẳng hạn.
Như vậy, khi chúng ta có một sự thay đổi, thì component không cần phải triển khai, phát hành lại, chỉ vì có 1 class nó phụ thuộc vào, nhưng không dùng đến. Tránh việc phải triển khai, phát hành không cần thiết, vì component quá lớn, được sử dụng ở quá nhiều nơi.
Kết luận
Tóm lại, REP nói rằng ta các class gom vào component để phát hành, triển khai cho việc dùng và dùng lại, CCP tập trung vào cách gom class chung mục đích lại để dễ cho việc vận hành, và CRP nói rằng ta cần chia nhỏ các class và component ra để chỉ dùng những gì cần thiết mà thôi.
Ta có thể nhận thấy là 3 nguyên tắc này có vẻ mâu thuẫn với nhau. REP và CCP thường hướng dẫn làm cho component lớn hơn. CRP thì ngược lại, làm cho component nhỏ hơn. Khi đó nhiệm vụ của kiến trúc sư giỏi là đưa ra phương án phù hợp nhất với yêu cầu business và yêu cầu của hệ thống.
Tam giác dưới đây cho thấy hậu quả viết ở cạnh của tam giác, khi tập trung vào chỉ hai nguyên tắc mà bỏ rơi nguyên tắc còn lại (ở phía đối diện của cạnh đó). Ví dụ, khi ta tập trung vào ERP và CRP, với mỗi thay đổi nhỏ, sẽ có quá nhiều component bị ảnh hưởng. Ngược lại, nếu tập trung vào CCP và ERP, sẽ có quá nhiều release không cần thiết vì trên một component.
Là một kiến trúc sư phần mềm, ta cần cân nhắc và tìm ra điểm hợp lý trong tam giác 3 nguyên tắc đó, để đạt được thiết kế tối ưu nhất ở thời điểm hiện tại. Tuy nhiên, ta cũng cần hiểu rằng, theo thời gian, điểm tối ưu đó cũng sẽ thay đổi tuỳ vào sự thay đổi và hiện trạng của hệ thống. Ví dụ, giai đoạn đầu dự án, CCP sẽ quan trọng hơn REP vì ta cần phát triển nhanh, hơn là dùng lại các component sẵn.
Thường thấy các dự án sẽ bắt đầu từ bên tay phải của tam giác, lúc này ta hy sinh khả năng dùng lại, có ít component, mỗi component lớn. Khi dự án có mức độ trưởng thành, và các dự án khác bắt đầu dùng nó, kết nối đến nó, dự án sẽ di chuyển về bên trái, có nhiều component hơn, nhiều release hơn. Nhìn chung, cấu trúc và quy hoạch component sẽ thay đổi theo thời gian và độ phức tạp của dự án cụ thể.
- S System Scheduler: Turn On/Off cloud application automatically (Bộ lập lịch hệ thống: Tự động bật/tắt ứng dụng đám mây)
- P Project Manager – Người “nhạc trưởng” thúc đẩy tổ chức tiến lên phía trước
- T Triển khai Cloud tại Digital Banking: Đâu là yếu tố để đảm bảo chuyển đổi thành công?
- S SAGA Pattern trong kiến trúc ngân hàng lõi (Core Bank Architecture)
- L Leveraging ML models to Predict Customer Churn in Business Banking
- T Tầm quan trọng của việc làm rõ yêu cầu trong việc triển khai dự án công nghệ
- X Xây dựng hệ thống giám sát (Monitoring) tập trung cho workload trên Cloud
- N Nguyên tắc thiết kế về Component Cohesion trong kiến trúc phần mềm (Principles of Component Cohesion in Software Architectures)
- T Tận dụng ưu thế cơ sở vật chất tại Techcombank: Nền tảng Machine Learning on-premise mang lại khả năng phân tích dữ liệu mạnh mẽ
- I Infrastructure as code (IaC)