0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Chuyển đổi 120k Dòng JavaScript Cũ Sang TypeScript Không Gián Đoạn

Đăng vào 3 tháng trước

• 10 phút đọc

Giới thiệu

Việc chuyển đổi một ứng dụng sản xuất quy mô lớn không bao giờ là một quyết định nhỏ, đặc biệt khi ứng dụng đó hỗ trợ các hoạt động như quản lý ví, thanh toán hóa đơn và chuyển đổi tiền điện tử sang tiền pháp định. Hệ thống mà tôi làm việc đã phát triển vượt quá 120.000 dòng JavaScript, nhiều phần trong số đó là thiết yếu và đang phục vụ người dùng với tiền thật hàng ngày.

Sự thúc đẩy để thực hiện chuyển đổi không chỉ đến từ bản thân tôi, mặc dù tôi đã lâu Advocating cho TypeScript bên trong tổ chức. Điểm quyết định đến từ sự kết hợp giữa phản hồi của đồng nghiệp, những khó khăn của nhà phát triển trong việc duy trì, và những rủi ro lâu dài của việc để một hệ thống lớn như vậy trong chế độ “JavaScript thuần túy”.

Công việc này cũng chịu ảnh hưởng nặng nề từ Ben Howdle. Bài nói chuyện xuất sắc của anh về việc từ bỏ chủ nghĩa hoàn hảo trong hệ thống phân tán và bài viết của anh về chuyển đổi trên Ngân hàng Sản xuất đã thuyết phục tôi rằng điều này có thể thực hiện được, và có thể thực hiện mà không có thời gian ngừng hoạt động. Dưới đây là quy trình và giọng nói của tôi được ghi lại trong lịch sử Git.

Tại Sao Chúng Tôi Chuyển Đổi

Trong một công ty fintech, thời gian ngừng hoạt động không phải chỉ là một sự bất tiện nhỏ, mà là một cú đánh trực tiếp vào niềm tin của khách hàng và hoạt động tài chính. Ngay cả một sự gián đoạn ngắn cũng có thể dẫn đến các giao dịch bị mất hoặc thanh toán bị trì hoãn, và những hạn chế của thiết lập hiện tại ngày càng trở nên rõ ràng:

  • Quy mô & Độ phức tạp: Khi các tính năng như thẻ ảo, chuyển đổi tiền điện tử, tích hợp nhà cung cấp và nhiều đường thanh toán liên tục phát triển, mã nguồn trở nên phức tạp hơn và đòi hỏi một nền tảng vững chắc hơn.
  • Trải nghiệm của nhà phát triển: Không có kiểu dữ liệu, việc thay đổi một mô-đun thường đồng nghĩa với sự lo sợ làm hỏng mô-đun khác. Quá trình xem xét mã trở nên chậm hơn. Ngay cả những thay đổi nhỏ cũng yêu cầu một khối lượng tâm trí đáng kể để tránh sự suy giảm chức năng.
  • Quản lý rủi ro: Trong fintech, lỗi không chỉ đơn thuần là “bug”. Chúng gây mất niềm tin, đôi khi là tiền (hoàn tiền), hoặc gây ra đau đầu về quy định. Chúng tôi muốn phát hiện vấn đề sớm hơn.
  • Độ ổn định của mã không theo kịp với quy mô. Logic ngân hàng không có sự khoan dung cho “undefined không phải là một hàm” trong thời gian chạy.

TypeScript hứa hẹn hợp đồng mạnh mẽ hơn, phát hiện lỗi tại thời gian biên dịch, và công cụ tốt hơn cho toàn đội. Câu hỏi chính không phải là liệu chúng tôi có nên chuyển đổi hay không, mà là làm thế nào để chúng tôi chuyển đổi mà không làm gián đoạn các hệ thống sản xuất?

Những Thách Thức Cốt Lõi

Việc chuyển đổi 120k+ dòng mã sản xuất đã đặt ra ba thách thức chính:

  1. Yêu cầu không có thời gian ngừng hoạt động
    Chúng tôi không thể chấp nhận bất kỳ sự gián đoạn nào. Người dùng phụ thuộc vào nền tảng hàng ngày cho các hoạt động ví, thanh toán hóa đơn, chuyển đổi tiền điện tử và nhiều hơn nữa. Thời gian ngừng hoạt động có nghĩa là các giao dịch không thành công, tác động đến quy định và giảm niềm tin.

  2. Mã nguồn lớn
    Backend bao gồm một API Node.js Express, các dịch vụ GraphQL và một hệ thống xử lý sự kiện thời gian thực. Các dịch vụ bổ sung chạy trên AWS Lambda và ECS worker thực hiện các logic giao dịch như chuyển đổi tiền tệ và kiểm tra tuân thủ.

  3. Phát triển song song
    Doanh nghiệp không thể tạm dừng việc cung cấp tính năng. Các tính năng mới, sửa lỗi và yêu cầu tuân thủ phải tiếp tục trong khi việc chuyển đổi đang diễn ra.

Chiến Lược Chuyển Đổi

1. Tạo một Nhánh Chuyển Đổi Riêng

Vào thời điểm đó, tôi đóng vai trò lãnh đạo, lùi lại khỏi công việc hàng ngày về tính năng và lỗi để tổ chức việc chuyển đổi. Bước đầu tiên là tạo một nhánh riêng. Mọi tệp .js.jsx trong mã nguồn đều được đổi tên thành .ts.tsx, tạo môi trường riêng để làm việc mà không làm gián đoạn sản xuất.

Nhánh “chuyển đổi” này hoạt động như một khu vực thử nghiệm. Chúng tôi có thể dần dần giới thiệu các tính năng TypeScript và cài đặt trình biên dịch mà không làm mất ổn định việc phát triển đang diễn ra.

Mẹo Chuyên Nghiệp: Sử dụng các cam kết gia tăng theo mô-đun thay vì một đợt đẩy lớn. Điều này giữ cho các thay đổi dễ dàng được kiểm tra và dễ dàng gỡ lỗi.

2. Cập Nhật Định Kỳ So Với Nhánh Chính

Vì nhánh chính tiếp tục di chuyển với việc cung cấp tính năng, nhánh chuyển đổi cần phải cập nhật định kỳ. Điều này đảm bảo rằng khi việc gộp cuối cùng diễn ra, các xung đột được tối thiểu hóa.

Trên thực tế, điều này có nghĩa là áp dụng lại các chuyển đổi TypeScript cho bất kỳ tệp .js mới nào được giới thiệu kể từ lần cập nhật cuối. Một chu kỳ điển hình trông như sau:

Copy
git checkout migration-branch
git fetch origin
git rebase origin/main
# giải quyết xung đột
# chuyển đổi bất kỳ tệp .js/.jsx mới nào thành .ts/.tsx

3. Gõ Dần Dần & Tiến Trình Theo Mô-đun

Thay vì chuyển đổi mọi thứ cùng một lúc, chúng tôi ưu tiên gõ dần dần:

  • Các quy trình quan trọng (cấp vốn ví, logic chuyển khoản, phát hành thẻ ảo).
  • Các tiện ích chia sẻ cốt lõi (xử lý lỗi, ghi log, xác thực).
  • Các tính năng ít quan trọng hơn hoặc bên ngoài (các mô-đun không quan trọng đối với giao diện người dùng, bảng điều khiển nội bộ).

Nới lỏng các cài đặt tsconfig.json ở giai đoạn đầu (ví dụ, strict: false) và dần dần siết chặt chúng.

Vào một thời điểm nào đó, chúng tôi gặp phải hàng ngàn lỗi duy nhất (thiếu kiểu, chữ ký hàm sai, kiểu trả về không đúng). Điều đó là điều hiển nhiên. Mỗi lỗi đều được ghi lại, phân loại và sửa chữa theo từng lô, theo mô-đun.

4. Tích Hợp CI/CD & Kiểm Tra Tự Động

Mỗi lần cập nhật đều kích hoạt pipeline CI/CD của chúng tôi, chạy:

  • Kiểm tra Jest. (kiểm tra đơn vị + tích hợp).
  • Kiểm tra E2E Frontend (Cypress chạy trên việc nạp tiền vào ví, tạo thẻ, thanh toán hóa đơn).
  • Kiểm tra kiểu thông qua tsc --noEmit.

Không có gì được đưa vào staging trừ khi nó vượt qua cùng một sự kiểm tra như sản xuất.

Kiểm Tra Trên Staging: Mạng An Toàn Của Chúng Tôi

Trước khi chúng tôi mơ về việc hợp nhất sản xuất, staging là chiến trường của chúng tôi. Chúng tôi đã mô phỏng luồng người dùng thực tế trong nhiều ngày nạp tiền, chuyển đổi, giao dịch thẻ, đôi khi thậm chí còn lặp lại “chuyển khoản nhịp tim” giữa các tài khoản thử nghiệm chỉ để đảm bảo không có gì bị lỗi âm thầm.

  • Chúng tôi đã triển khai phiên bản đã chuyển đổi TypeScript đến staging (gương dịch vụ sản xuất).
  • Trong 5-6 ngày, chúng tôi đã chạy mô phỏng sử dụng thực tế: tạo thẻ, thanh toán, cấp vốn ví, chuyển đổi, thanh toán hóa đơn.
  • Các giao dịch tổng hợp là một phần của việc giám sát để chúng tôi có thể phát hiện sự suy giảm trong các luồng sớm.

Nếu bạn chưa bao giờ quan sát một ví thử nghiệm gửi $1 qua lại giữa chính nó trong 48 giờ liên tục, tôi có thể đảm bảo với bạn rằng đó là sự tương đương của fintech với một chiếc đèn lava: kỳ quặc mê hoặc, một chút vô lý, nhưng vô cùng hữu ích.

Cờ Lùi / Cờ An Toàn Trong Quá Trình Triển Khai

Vì không phải mọi mô-đun giao diện người dùng được chuyển đổi cùng một lúc, chúng tôi đã sử dụng cờ tính năng và giải quyết điều kiện trong frontend. Ý tưởng là nếu một phiên bản biên dịch TypeScript của một thành phần/trang tồn tại, ứng dụng sẽ tải nó; nếu không, nó sẽ quay lại phiên bản JavaScript cũ.

Điều này cho phép chúng tôi kiểm soát chi tiết trong quá trình triển khai, vì các mô-đun chưa hoàn thành không làm tắc nghẽn các triển khai và chúng tôi có thể an toàn gộp nhánh chuyển đổi của mình vào nhánh chính mà không rủi ro xây dựng bị hỏng.

Dưới đây là một đoạn mã đại diện từ một trong các điểm vào của chúng tôi:

Copy
// utils/loadModule.ts
export async function loadModule(moduleName: string) {
  try {
    return await import(`../dist/${moduleName}.js`);
  } catch {
    return await import(`../src/${moduleName}.js`);
  }
}

5. Cắt Đứt: Sát Nhập Không Thời Gian Ngừng Hoạt Động

Khi staging đã hoạt động tốt trong nhiều ngày, chúng tôi đã gộp nhánh và triển khai. 🪔 Nhờ các cờ tính năng, các rào chắn CI/CD, và các trình tải lùi, việc cắt đứt diễn ra suôn sẻ. Người dùng không bao giờ nhận thấy sự thay đổi lớn đang diễn ra dưới tài khoản của họ.

Kết Quả: Những Gì Chúng Tôi Đạt Được (và Những Khó Khăn)

Lợi Ích

  • Sự tự tin của nhà phát triển tăng mạnh. Với TypeScript, nhiều lỗi đã được phát hiện trước khi QA hoặc staging.
  • Chi phí bảo trì giảm, đặc biệt là cho việc refactor, ít “phát triển do sợ hãi”. Tôi gọi điều này là FDD.
  • Tính dễ đọc của mã và sự hiểu biết chung được cải thiện: các kỹ sư mới trong đội có thể theo dõi hợp đồng qua các kiểu.

Phần Khó Khăn

  • Danh sách lỗi ban đầu rất áp đảo. Việc ưu tiên là rất quan trọng.
  • Việc cập nhật thường xuyên tạo ra xung đột khi gộp (đặc biệt xung quanh các tiện ích chia sẻ). Đôi khi mất nhiều giờ.
  • Một số thư viện bên thứ ba có kiểu kém hoặc không có, điều này có nghĩa là phải viết các tệp .d.ts của riêng chúng tôi hoặc bao bọc chúng.

Công Cụ & Cài Đặt Chính

Một số công cụ đã chứng minh là vô giá trong quá trình chuyển đổi:

  • Typescript Hero và TypeScript Import Sorter: quản lý nhập khẩu.
  • Công cụ chuyển đổi mã (ts-migrate) để tự động hóa các phần tẻ nhạt (đổi tên nhập khẩu, cập nhật require → import).
  • Cờ tính năng / tải mô-đun động như đoạn mã trên để cho phép trộn mã cũ và mới một cách an toàn.
  • ESLint với plugin TypeScript. Đảm bảo kiểu dáng nhất quán và phát hiện nhiều lỗi nhập/xuất sớm.
  • Jest + kiểm tra kiểu trong CI. Đảm bảo cả kiểu và kiểm tra đều vượt qua.

Kết Luận: Liệu Có Đáng Không?

100%. Việc chuyển đổi một hệ thống ngân hàng sản xuất quy mô lớn từ JavaScript sang TypeScript không phải là điều đơn giản. Nó đòi hỏi kế hoạch, kỷ luật và sự hợp tác trong toàn bộ đội ngũ kỹ thuật. Nhưng phần thưởng là rõ ràng: mã an toàn hơn, nhà phát triển hạnh phúc hơn, và một nền tảng có thể mở rộng một cách tự tin.

Đối với chúng tôi, quá trình chuyển đổi mất khoảng một tháng cùng với nỗ lực tập trung. Nó đầy thách thức nhưng cuối cùng là một sự chuyển mình.

Tôi muốn ghi nhận Ben Howdle vì đã chia sẻ công khai hành trình của mình, điều này đã ảnh hưởng trực tiếp đến cách tiếp cận của chúng tôi. Nếu bạn đang xem xét một cuộc chuyển đổi như vậy, tôi khuyến khích bạn nghiên cứu kinh nghiệm của anh ấy.

TypeScript không chỉ là về kiểu dữ liệu, mà còn là về sự tin tưởng. Niềm tin rằng hệ thống của bạn sẽ hoạt động nhất quán dưới tải, niềm tin rằng các nhà phát triển có thể thực hiện thay đổi mà không lo sợ, và niềm tin rằng tiền của người dùng vẫn an toàn.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào