Giải cứu dự án phần mềm thất bại: Đưa một ứng dụng di động bị đình trệ ra thị trường trong 90 ngày
Đầu năm 2024, khi một chuỗi bán lẻ tầm trung của Việt Nam tìm đến chúng tôi, các chỉ số nội bộ của họ kể một câu chuyện mà chúng tôi đã từng chứng kiến quá nhiều lần. Ứng dụng khách hàng thân thiết của họ, ban đầu được xác định là một dự án phát triển trong sáu tháng, đã bước sang tháng thứ mười bốn, vượt ngân sách khoảng 180 phần trăm, và vẫn chưa sẵn sàng phục vụ 40.000 thành viên đang hoạt động. Hai nhà cung cấp đã đến rồi đi. Một báo giá thứ ba vừa mới đáp xuống bàn của CEO, hứa hẹn thêm chín tháng làm việc nữa. Thay vào đó, họ gọi cho chúng tôi để thực hiện một cuộc audit kéo dài hai tuần. Chín mươi ngày sau, ứng dụng đã có mặt trên Apple App Store và Google Play, vận hành chiến dịch khách hàng thân thiết cuối năm giúp mở khóa hơn 12.000 lượt đăng ký mới trong tháng đầu tiên.
Đây là câu chuyện về cách chúng tôi đến được đích, điều gì thực sự đã xảy ra, và một cuộc giải cứu dự án phần mềm thất bại trông như thế nào khi bạn gạt bỏ hết chuyện chính trị giữa các nhà cung cấp.
Tổng quan dự án
Khách hàng là một thương hiệu bán lẻ chuyên biệt vận hành 34 cửa hàng tại ba tỉnh, với một chương trình khách hàng thân thiết đã trở nên quá phức tạp so với hạ tầng SMS-và-thẻ-nhựa ban đầu. Tầm nhìn thì đơn giản: một ứng dụng di động native cho iOS và Android, cho phép thành viên kiểm tra số dư điểm, đổi thưởng tại điểm bán hàng, nhận các ưu đãi được cá nhân hóa, và đặt lịch dịch vụ tại cửa hàng. Trên giấy tờ, đây là một dự án được hiểu rõ, với các thư viện đã trưởng thành và logic nghiệp vụ rành mạch.
Kế hoạch triển khai ban đầu là một hợp đồng sáu tháng với một studio tại Thành phố Hồ Chí Minh, sau đó là ba tháng bàn giao cho đội nội bộ. Sau mười bốn tháng, đội nội bộ vẫn chưa được tuyển, nhà cung cấp thứ hai đã xây lại backend hai lần, và bản thân ứng dụng vẫn crash trên Android 12 trong vòng hai phút khi vào luồng quét điểm thưởng. Giám đốc tài chính của khách hàng ước tính chi phí trực tiếp đã vượt 4,1 tỷ VND, chưa tính doanh thu bị mất từ chương trình khách hàng thân thiết đang mắc kẹt với các voucher in giấy.
Vấn đề mà khách hàng phải đối mặt
Cuộc audit hai tuần của chúng tôi làm lộ ra ba vấn đề riêng biệt, và đội ngũ lãnh đạo của khách hàng ngạc nhiên khi biết chỉ có một trong số đó thực sự mang tính kỹ thuật.
Vấn đề thứ nhất là scope drift được ngụy trang dưới hình thức tăng trưởng tính năng. Bản đặc tả 42 trang ban đầu đã phình to lên 118 trang, với các tính năng mới được ba bên liên quan khác nhau thêm vào mà không có một quy trình thay đổi chính thức. Đội mobile đang đuổi theo một mục tiêu di động, còn đội backend thì đang xây các endpoint cho những màn hình đã được thiết kế lại hai lần rồi.
Vấn đề thứ hai là một backend được thiết kế cho bản demo, chứ không phải cho sản xuất. Tầng API được xây trên một monolith PHP dùng chung, đồng thời cũng phục vụ trang thương mại điện tử của khách hàng. Các truy vấn số dư điểm đang lấy từ một bảng MySQL 2,1 triệu dòng mà không có index nào trên cột member ID. Các giao dịch đổi thưởng không idempotent, nghĩa là một tín hiệu 4G chập chờn tại điểm bán hàng có thể trừ điểm gấp đôi của một thành viên. Nhà cung cấp trước đó biết về các vấn đề này và đã đề xuất viết lại toàn bộ như là báo giá thứ ba.
Vấn đề thứ ba, và cũng gây thiệt hại nhất, là một đội ngũ không còn có thể nói chuyện được với chính mình. Đến tháng thứ mười hai, product owner của khách hàng, agency thiết kế và nhà cung cấp triển khai gần như chỉ giao tiếp với nhau qua một bảng tính chia sẻ mà không ai tin tưởng. Các cuộc họp đã trở thành phiên đổ lỗi thay vì là nơi ra quyết định.
Technical debt là có thật. Nhưng chính sự sụp đổ của niềm tin và giao tiếp mới là thứ thực sự đang cản trở việc ship sản phẩm.
Giải pháp mà chúng tôi đã đưa ra
Chúng tôi đề xuất một cuộc giải cứu 90 ngày với phạm vi cố định, chia thành ba giai đoạn rõ ràng, và neo các điều khoản thương mại vào việc ship chiến dịch khách hàng thân thiết trước mùa bán lẻ cao điểm của khách hàng vào tháng Mười Một.
Hai tuần đầu tiên là để cắt bỏ, đóng băng và tái thiết lập kế hoạch. Chúng tôi đàm phán một scope freeze cứng với lãnh đạo khách hàng: bản ra mắt sẽ ship 31 trong tổng số 118 tính năng của đặc tả, được chọn vì chúng bao phủ ba hành trình thành viên có giá trị cao nhất. Mọi thứ khác được đưa vào một backlog v1.1 có tài liệu, với chủ sở hữu được chỉ định. Chỉ riêng quyết định này đã cứu được khoảng sáu tuần thời gian kỹ thuật mà lẽ ra sẽ bị dùng để đuổi theo các trường hợp biên mà không ai yêu cầu.
Sáu tuần tiếp theo là để xây lại những phần rủi ro và giữ lại phần còn lại. Chúng tôi bảo tồn codebase React Native hiện có, bởi vì công việc của đội mobile thực ra là hợp lý và chỉ bị tích hợp kém. Sau đó chúng tôi xây lại hai thành phần bị hỏng về mặt cấu trúc. API khách hàng thân thiết được trích ra khỏi monolith PHP dùng chung để thành một service Node.js nhỏ với PostgreSQL database riêng, index đúng cho các truy vấn thành viên, và các giao dịch đổi thưởng idempotent được đánh key bằng một UUID do client sinh ra. Chúng tôi giới thiệu một event log nhẹ, để mọi giao dịch điểm đều có thể được tái dựng từ các event thô, nghĩa là đội tài chính cuối cùng cũng có thể audit số dư mà không cần mở support ticket.
Tháng cuối cùng là để ổn định, kiểm thử và bàn giao. Chúng tôi chạy hai vòng closed beta với 200 thành viên thật trên năm cửa hàng, sửa các crash khi chúng xuất hiện. Chúng tôi viết runbook cho đội nội bộ sắp tới của khách hàng, ghi tài liệu cho deployment pipeline, và huấn luyện hai kỹ sư của chính khách hàng để họ có thể sở hữu codebase sau khi bàn giao. Ứng dụng được ship lên cả hai store vào ngày thứ 87, sớm ba ngày so với deadline của chiến dịch.
Các điểm nhấn kỹ thuật giúp mọi thứ hoạt động
Ba lựa chọn kỹ thuật đã đảm nhận phần lớn công việc nặng.
Endpoint đổi thưởng idempotent đã loại bỏ được một lớp bug trừ điểm gấp đôi vốn đã làm tổn hại niềm tin của thành viên trong nhiều tháng. Mỗi request đổi thưởng từ mobile client mang theo một UUID; backend ghi UUID này vào một unique index và từ chối các bản trùng bằng cách trả về response gốc. Một thành viên với tín hiệu 4G kém giờ đây có thể nhấn Redeem ba lần và chỉ mất điểm một lần.
Event-sourced point ledger đã thay thế một trường balance có thể sửa đổi bằng một log append-only của các event điểm. Số dư trở thành derived state, được tính khi đọc và được cache. Việc đối soát, vốn là cơn ác mộng hàng tháng của đội tài chính, đã trở thành một câu SQL duy nhất. Đây không phải là một pattern mới lạ; đây là pattern đúng cho bất cứ thứ gì liên quan đến tiền hoặc giá trị tương đương tiền. Các nhà cung cấp trước đã bỏ qua nó vì mất nhiều thời gian để giải thích hơn là để xây.
Pipeline build tách rời cho iOS và Android đã cắt giảm chu kỳ release từ một tuần xuống dưới một giờ. Hai nền tảng không còn phải chờ nhau, và một test thất bại trên một nền tảng chỉ ngăn việc ship của nền tảng đó. Trên giấy tờ đây là một thay đổi nhỏ, nhưng lại là một chuyển biến đáng kể về tinh thần cho một đội đã ship trong tâm thế sợ hãi suốt cả năm.
Kết luận
Một dự án phần mềm bị đình trệ hiếm khi bị đình trệ chỉ vì một lý do duy nhất. Trong trường hợp này, technical debt có thể sửa được trong tám tuần; sự đổ vỡ trong giao tiếp mới là vấn đề khó hơn, và nó cần đến một đội ngũ bên ngoài có tư cách để nói không với các tính năng, và có đủ uy tín để bảo vệ quyết định đó trước CEO. Cuộc giải cứu đã thành công bởi vì khách hàng sẵn sàng đóng băng phạm vi và xây lại niềm tin song song, chứ không phải vì chúng tôi đã đưa vào một công nghệ kỳ lạ nào đó.
Nếu đội của bạn đang nhìn vào một dự án vượt ngân sách, quá deadline và đang đánh mất niềm tin của các bên liên quan, câu trả lời gần như không bao giờ là thêm chín tháng nữa và một đội triển khai lớn hơn. Nó thường là một cuộc audit ngắn, trung thực, tiếp theo là một kế hoạch giải cứu với phạm vi cố định, được xây quanh một sự kiện ship duy nhất có ý nghĩa với doanh nghiệp. Đây chính là loại công việc mà chúng tôi nhận tại MerkTechs: mang một góc nhìn tươi mới và giàu kinh nghiệm đến các dự án đã đi lạc đường, và đưa chúng trở lại sản xuất mà không kèm theo drama.