Evolving Mercari's iOS codebase into a multi-product monorepo

Bài tóm tắt Mercari 2025 3. Quy mô lớn Độ khó: Trung bình

Mercari · 2025 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Một bài trong series Global App (ngoài sheet). Android đi cùng hướng: Mercari 2025.

Bối cảnh

  • Lịch sử app iOS của Mercari: app Nhật native; Mỹ và Anh bản đầu dùng cùng repo với Nhật, chuyển hành vi bằng directive biên dịch, không có kiến trúc module, rồi mỗi nước fork riêng vì quá phức tạp; Mỹ bản 2 lai native + React Native, bản 3 React Native toàn bộ; Nhật viết lại từ đầu (GroundUp App) native; Mercari Hallo dùng Flutter; Atte, Kauru mỗi app một repo mới.
  • App Nhật đã dùng Bazel với kiến trúc micro-module (ranh giới chặt, trừu tượng rõ), remote cache và Remote Build Execution sẵn có; cộng cả hai app hiện hơn 900 module.

Thay đổi chính

  • Global App phát triển trong repo của app Nhật, đổi repo thành monorepo nhiều sản phẩm:
    • Cấu trúc mới: Products (code riêng từng sản phẩm), Company và InHouse (module dùng lại giữa sản phẩm; InHouse là module nói chuyện với dịch vụ nội bộ như nền tảng định danh toàn cầu).
    • Phân loại module: A dùng lại được ngay (kiến trúc, Design System 4.0), B cần refactor (nhiều module trong Group chỉ đúng cho app Nhật), C không dùng lại được vì gắn khái niệm của app Nhật. Kiểm cả việc module có nên dùng lại không về khái niệm, không chỉ hành vi hiện tại.
    • Script, cấu hình Bazel, rule tự viết, CI tách phần chung khỏi phần riêng sản phẩm; mỗi sản phẩm vẫn có luồng nộp store riêng. Phụ thuộc ngoài (Firebase) mặc định cùng phiên bản và cấu hình, cho phép khác khi cần.
    • Chuyển dần: hơn 50 engineer iOS vẫn đang làm trong repo; tạo cấu trúc và chuyển nhóm A trước, rồi refactor nhóm B từng module một (phải thống nhất với các bên) song song với phát triển Global App.
  • Quy tắc: không bao giờ dùng lại module ở mức tính năng giữa hai app, dù lúc đầu hai app trông giống nhau; phải viết lại theo nhu cầu Global App. Lý do: dùng lại mà không có định hướng từ trên dẫn tới trừu tượng sai, sửa một app làm đổi app kia. Ngoại lệ phải đồng ý với đội sở hữu module chung.
  • Global App mặc định theo kiến trúc và công cụ của app Nhật (swiftui-atom-properties, SwiftUI, design system, DI, A/B test), chỉ khác khi có lợi rõ: gRPC tới BFF, Phrase cho đa ngôn ngữ. Cải tiến làm ở Global App đôi khi được chuyển ngược về app Nhật.
  • Kích thước binary 38,8 MB ở bản 1.17.0 (App Store Đài Loan).

Vì sao

  • Đội nhỏ, lịch gấp; dùng lại nền móng, kiến thức và hạ tầng build đã tối ưu thay vì dựng lại như các app trước.

Bài học cho SWE

  • Monorepo nhiều sản phẩm chỉ khả thi khi ranh giới module đã chặt; Bazel chỉ kéo module cần nên app mới không gánh build time hay kích thước của app cũ.
  • Dùng lại nền (design system, client dịch vụ nội bộ), không dùng lại tính năng.
  • Mặt trái theo bài: thay đổi module chung phải review, QA cho mọi sản phẩm; sinh project Xcode "chứa tất cả" chậm và nặng (đội enablement cho chọn phạm vi target Bazel); quy tắc phụ thuộc giữa sản phẩm mới chỉ là guideline, cần kiểm tự động.
  • Lợi ích phụ không dự tính: code nhất quán giữa hai app làm agent AI sinh code đúng mẫu hơn.

Liên quan