Giới thiệu

Trong suốt những dự án mà tôi được nhận, tôi đã làm việc một loạt dự án phần mềm cũ với các lỗi và các vấn đề khác nhau.

Tất nhiên, chất lượng phần mềm kém (không có unit test, không áp dụng clean code...) thường là vấn đề lớn, nhưng cũng có những vấn đề từ việc quyết định xây dựng kiến trúc của dự án ngay từ thời điểm ban đầu, hoặc thậm chí từ những sơ khai nhất của hệ thống doanh nghiệp. Những vấn đề này theo quan điểm của tôi là nguyên nhân gây nhiều lỗi nhất cho các dự án.

Thực tế mà nói, cải thiện code thì khá dễ dàng, đặc biệt hiện tại các good pratice đã được các team sử dụng. Nhưng thay đổi core của hệ thống thì lại khác rất khó để thay đổi vì có các ràng buộc đã được áp đặt ngay từ lifecycle của nó.

Tôi sẽ nói về một số loại về quyết định thiết kế kiến trúc (architectural decisions - ADs) mà tôi đã gặp phải và đó là gánh nặng cho các team đang bảo trì các hệ thống này.

 

#1 Chia sẻ database của bạn cho toàn bộ công ty

 

Đây có lẽ là một trong những vấn đề mà tôi thường thấy nhất. Khi mà một số ứng dụng cần sử dụng các data chung, tại sao chúng ta không đơn giản việc chia sẻ database? Sau tất cả, những sự lặp lại là điều tồi tệ trong phát triển phần mềm là đúng phải không? Venkat Subramaniam đã nói về nó theo cách không thể quên được: "Một database giống như một cái bàn chải đánh răng, bạn đừng nên chia sẻ nó". Vậy có gì sai trong việc chia sẻ database? Rất nhiều điều trong thực tế ...

Điều đầu tiên mà tôi có thể nghĩ một cách chắc chắn là liên kết trong mô hình dữ liệu (data model). Tưởng tựng rằng có 2 ứng dụng A và B đang kết nối với ô tô. Ứng dụng A được phát triển bởi team sửa chữa, và vì vậy họ cần rất lưu trữ rất nhiều dữ liệu kỹ thuật về cơ học, về lỗi, về lịch sử can thiệp trên xe... Ứng dụng B được sử dụng để xử lý các appointments (cuộc hẹn) cho team kỹ thuật vì vậy họ chỉ cần những thông tin cơ bản về chiếc xe để có thể xác định được nó. Trong trường hợp này, sử dụng cấu trúc dữ liệu chung cho cả hai ứng dụng là không có ý nghĩa: chúng sử dụng rất nhiều dữ liệu khác nhau, vì thế chúng cần được sử dụng các cấu trúc dữ liệu cho riêng mình. Điều này thực hiện dễ dàng hơn khi một chiếc xe có thể dễ dàng được xác định, vì thế không cần phải chia sẻ một tham chiếu chung cho chúng.

Vấn đề thứ hai cũng đến từ việc liên kết trong mô hình dữ liệu. Tưởng tượng rằng B muốn thay đổi định danh của chiếc xe vì nó có ý nghĩa hơn nhiều theo vùng miền địa lý. Trong trường hợp này, A cũng phải update để xử lý cột tên mới... Vì vậy, để tránh xung đột team A, các developer của B phải bắt đầu sao chép thông tin vào một cột khác vì họ không thể thay đổi những tên đã tồn tại... Tất nhiên, A phải nói rằng họ sẽ có kết hoạch để thay đổi chúng trong tương lai để tránh việc 2 cột trong database chứa các dữ liệu trùng nhau, nhưng chúng ta đều biết rằng điều này sẽ không bao giờ đạt được...

Mọi thứ trở nên xấu hơn khi các ứng dụng không chỉ đọc dữ liệu từ một nguồn, nhưng họ cũng sửa đổi chúng! Trong trường hợp này, ai là người sở hữu dữ liệu? Ai là người đáng tin cậy? Làm thế nào để đảm bảo tính toàn vẹn của dữ liệu? Điều này thực sự khó khi nhiều phần của các ứng dụng sử dụng chung database và khi sửa database chung này nó sẽ ảnh hưởng đến các phần khác của ứng dụng đó.

Trường hợp cuối cùng tôi đã thấy là khi 2 ứng dụng sử dụng chung cấu trúc dữ liệu để lưu trữ thông tin về 2 đối tượng nghiệp vụ, nhưng để hiểu được dữ liệu nào là của ứng dụng nào thật sự là khó. Trong trường hợp này, cả hai ứng dụng đều sử dụng bảng này để tạo một mô hình thị trường tài chính chỉ khác nhau về cấp độ của nó. Không có gì để chỉ rõ rằng có 2 loại data trong bảng này, vì thế chúng tôi phải nhìn vào bảng khác (sở hữu bởi ứng dụng thứ hai) để xác định dòng nào được tạo ra bởi ứng dụng nào... Mỗi developer mới khi phải làm việc ở trên bảng này chắc chắn sẽ gặp những lỗi tương tự như các dev cũ họ từng gặp phải kèm theo đó là sự sử dụng dữ liệu không hợp lệ và các rủi ro liên quan đến công ty.

 

#2 Xây dựng hệ thống xung quanh một phần mềm nghiệp vụ

 

Không phải tất cả các công ty đều có thể phát triển hệ thống để xử lý tất cả các usecase nghiệp vụ. Thực tế trong rất nhiều trường hợp điều này chỉ là sự sử dụng lại khi các usecase này được phổ biến đối với nhiều công ty khác vì vậy bạn có thể dễ dàng tìm thấy phần mềm trên thị trường đã viết sẵn để hỗ trợ các chức năng đó.

Vì thế mua các sản phẩm có sẵn trên thị trường thường là rẻ hơn là tự xây dựng nó. Nhưng tất nhiên là cái phần mềm bạn vừa mua không thể tích hợp với các phần mềm khác mà bạn đang sử dụng vì vậy bạn cần phải phát triển một ứng dụng để kết nối các phần mềm đó. Có lẽ bạn sẽ xây dựng các công cụ chuyên biệt để xử lý một phần nghiệp vụ, và khi cái phần mềm đắt tiền bạn đã mua đã có mô hình được xây dựng sẵn, bạn sẽ bị cám dỗ để sử dụng database của nó và cung cấp các thông tin của bạn cho database đó...

Một vài năm trôi qua, hàng tá nhà phát triển hoặc các team đã làm như vậy và đẵ mắc kẹt vào một số vấn đề: bạn không thể sử dụng các phần mềm khác khi nó bị đóng, bị xóa, hoặc sản phẩm không còn được hỗ trợ hoặc nếu sản phẩm mới cần các chức năng tốt hơn. Trong một số trường hợp, thậm chí bạn còn phụ thuộc vào đặc tả kỹ thuật của các phần mềm bên ngoài. Nếu họ muốn bán cho bạn một phần mềm có phiên bản mới để cung cấp cho bạn tính năng mà bạn thực sự cần, nhưng phiên bản này có các đặc điểm kỹ thuật khác như vậy bạn phải cập nhật các kỹ thuật của mình để phù hợp với tính năng của phiên bản đó. 

Tôi đã làm việc trên một sư án mà editor của phần mềm chúng tôi đang sử dụng không muốn phát triển thêm tính năng mới cho tất cả các khách hàng, bởi vì nó trở nên quá phức tạp để sửa đổi (mỗi khách hàng có một phiên bản cụ thể kèm theo các tính năng mà họ chỉ muốn). Vì vậy để sử dụng các tính năng của riêng mình chúng tôi sử dụng một bộ phát triển phần mềm (Software Development Kit - SDK). Tất nhiên khi sử dụng SDK, họ không cung cấp nhiều tài liệu về cách thực hện, chúng tôi cần dịch ngược để hiểu cấu trúc của họ vi chúng tôi không có mã nguồn và tài liệu...Tính năng đơn giản nhất sẽ mất nhiều ngày để thực hiện, và nó hầu như không thể kiểm chứng vì mọi thứ đều rất phức tạp, không ai giới thiệu về những kịch bản ngôn ngữ thế nên các vấn đề càng ngày càng chồng chất cho các team...

 

#3 Kết nối nhiều ứng dụng

 

Những năm đầu thập niên 2000 và niềm vui của việc sử dụng Enterprise Java Beans (EJB) để xử lý các remote call giữa các ứng dụng trong các hệ thống thông tin của bạn. Tại thời điểm này, ý tưởng này có lẽ hay. Chia sẻ codebase của bạn với các team khác để tránh trùng lặp thì có vẻ ok. Đúng, mỗi team buộc phải phân phối các ứng dụng của họ tại cùng một thời điểm để chắc chắn rằng không có phụ thuộc broken binary. Nhưng giả sử trong một buổi tối thú vị, bạn ăn pizza với đồng nghiệp và phải chờ 2 tiếng đồng hồ để nhận pizza?

Tốt, trong thực tế đó không vui chút nào. Bạn không thể tái cấu trúc một đơn class trong codebase của bạn bởi vì có một ai đó trong công ty thích code của bạn và quyết định sử dụng nó trong ứng dụng của họ, điều đó không vui chút nào phải không.

Một khi bạn nhận ra sự lộn xộn mà những quyết định kiến trúc ban đầu gây ra, nỗ lực cần thiết là tách ứng dụng của bạn khỏi phần còn lại. Mất rất nhiều thời gian để chia nhỏ dự án của bạn thành các thành phần nhỏ nhưng bù lại phần thưởng cho những người tham gia dự án là rất lớn: dễ dàng để phát triển và testing, quy trình phân phối nhanh hơn vì không cần phải đồng bộ hóa với mọi người nữa, việc chia tách code của bạn sẽ dễ quản lý hơn, ít có những vấn đề phụ thuộc vào các ứng dụng khác trong classpath của bạn...Những thay đổi đáng giá này thực sự sẽ giúp cho team tiết kiệm sau này, và thực sự rất rẻ để thực hiện ngay ở thời điểm ban đầu của dự án.

 

#4 Xây dựng dự án của bạn từ dự án khác

 

Đây có lẽ là vấn đề bạn ít phải đối mặt nhất, nhưng điều này vẫn có thể xảy ra và đây là trường hợp xấu nhất, vì nó kết hợp các vấn đề trước đó. Trong thực tế là tôi đã phải đối mặt với vấn đề này trong những dự án đầu tiên trong sự nghiệp của tôi.

Khi tôi tiếp nhận dự án, Tôi được biết đó là dự án được viết lại và là sư kết hợp của hệ thống công ty và một dự án mới được bắt đầu 2 tháng. Vì vậy, khi tôi thấy một webapplication phức tạp với một module adminstration đầy đủ, một tính năng về business phức tạp đã sẵn sàng được triển khai và một framework cho phép phát triển các module khác, tôi đã rất ngạc nhiên. Tôi nhanh chóng biết rằng tất cả những thứ này hầu như không được phát triển bởi team: nó được tái sử dụng từ một framework được phát triển bởi một công ty khác để tránh phát triển lại từ đầu. Vấn đề là framework không được phân tách độc lập khỏi dự án mà nó phát triển. Vì vậy, team của chúng tôi chỉ có một kho để lưu trữ tất cả các mã nguồn của dự án công ty khác, bao gồm cả code business của họ, chẳng có điểm gì chung với dự án của công ty tôi. Thậm chí điều tồi tệ là chúng tôi được thừa hưởng từ database schema và data base của họ, mọi thứ từ họ...

Là một người mới trong team, rất khó để biết code nào liên quan đến framework, đến dự án hay là liên quan đến nghiệp vụ của công ty khác. Team muốn dọn dẹp các thứ lộn xộn kia nhưng các cố gắng không được đền đáp bởi vì có sự phụ thuộc giữa các phần của code (Tôi không thể nó về các module khi mà nó chỉ có một), và một điều hiển nhiên là không có auto test. Hơn thế nữa, chúng phải từ bỏ ý tưởng về việc sử dụng server application khác bởi vì đây là những code chỉ định trong hệ thống được công ty khác sử dụng mọi nơi, điều này dẫn đến chi phí lớn đối với team nhỏ của chúng tôi.

Tại mộ số điểm, chúng tôi muốn thêm một số tính năng hay cho frame work, nhưng chúng tôi đã được phản hồi rằng nó đã được có sẵn trong công ty khác. Vì thế chúng tôi phải ghép phiên bản hiện tại của chúng tôi với phiên bản hiện tại của công ty kia... Quản lý của team để tránh cơn ác mộng này bằng cách chỉ chọn những tính năng đơn giản nhưng nó vẫn thế rất nhiều phức tạp và tốn kém hơn những gì chúng tôi cần...

Chúng tôi đã hoàn thành dự án, nhưng chất lượng của nó thực sự là một nỗi đau. Ít nhất là 40% code và các content database dự án của chúng tôi là thừa thãi và vô dụng. Tôi hy vọng team sẽ có cơ hội để tách các khối mã độc lập khỏi các ràng buộc kể từ khi tôi rời đội.

 

#5 Tất cả các logic nghiệp vụ của bạn đều nằm trong một công cụ quản lý

 

Đặt một chút logic nghiệp vụ của bạn trong một hệ thống quản lý quy tắc là một điều phổ biến. Đây là một ví dụ hữu ích khi một số nghiệp vụ cần được cập nhật thường xuyên nhưng ứng dụng đơn tầng cần một khoảng thời gian để kiểm thử khiến bạn không thể điều chỉnh các quy tắc này.

Nhưng tôi đã phải đối mặt với một trường hợp đó là hầu hết tất cả logic nghiệp vụ được đặt trong một hệ thông quản lý mà các quy tắc này đôi khi được tạo ra bởi một file Excel. Hơn nữa các quy tắc này không được thay đổi thường xuyên. Với một dự án Java, sau tất cả được tạo ra từ các miêu tả kỹ thuật về các framework và read/write từ source và các target hệ thống và hoàn toàn không tham chiếu đến domain.

Kết quả là, tất cả các quy tắc được viết bằng ngôn ngữ cụ thể mà trong team không ai có thể hiểu rõ nó, rất khó để viết (IDE của chúng tôi không xử lý được việc này) và hầu như không thể debug và kiểm thử được. Khi có một quy tắc mới hoặc có yêu cầu thay đổi các quy tắc hiện tại hầu hết các developer trong team chỉ thường sao chép và dán lại các quy tắc cũ, dẫn đến toàn bộ file mới giống hệt file cũ trừ một số điểm.

 

Tổng kết

 

Trong cuốn "Clean Architecture" của Uncle Bob đã miêu tả, khi nghĩ về kiến trúc của một dự án, một số quyết định phải hoãn lại cho đến khi chúng tôi phải có sự lựa chọn chắc chắn trừ khi chúng tôi không tiếp tục thêm các giá trị vào dự án của chúng tôi (như việc chọn database để thay thế). Các quyết định khác phải được thực hiện sớm đừng chờ đợi cho đến khi nó trở thành rắc rối. May mắn thay, các loại quyết định kiến trúc này dễ dàng phát hiện được bởi vì chúng có thể được gọi là mùi kiến trúc: khi bạn nghĩ về nó chúng chỉ là những ý tưởng tồi những điều này có thể quay lại và ám ảnh bạn ở một thời điểm nào đó. Thật không may, khi bạn làm trên một phần mềm cũ, loại gánh nặng này thường được giấu kỹ trong các đoạn code khiến rất khó để loại bỏ thậm chí rất tốn kém.

Chúng ta không nên sợ hãi. Việc clean qua hàng năm thậm chí hàng chục thập kỷ không phải là nhiệm vụ dễ dàng gì, làm một phần mềm chuyên nghiệp chúng ta không để nó "tồi tệ" hơn. Phải tăng sự động lực của các lập trình viên, sự tin tưởng của người dùng vào sản phẩm của mình và tăng giá trị kinh doanh cho họ.

Tất nhiên, mỗi gánh nặng về kiến trúc mà tôi đã mô tả có thể giải quyết được nhiều cách, vì vậy không có "viên đạn bạc" nào có thể giải quyết hết từng vấn đề. Nhưng tôi chắc chắn rằng trong các team có thể đưa ra các phương án cuối cùng để giải quyết các gánh nặng đó. Vì vậy hãy đối mặt với nó và bắt đầu giải quyết các mớ hỗn độn này.!

Bài viết được dịch từ dev.to:architecture as a burden