Steven
Steven6 phút đọc

Một codebase, hai ứng dụng: cách white-label mà không cần fork

GeekBye và Pavleur là hai ứng dụng desktop mang thương hiệu khác nhau được build từ một repository duy nhất — không fork, không nhân đôi codebase. Đây là bộ máy build-time giúp một codebase biên dịch ra hai sản phẩm, và cái bug một dòng khiến ứng dụng thứ hai của chúng tôi tự giới thiệu bằng cái tên sai.

Kỹ thuật
Kiến trúc
Build
Bản phát hành GeekBye
Một codebase, hai ứng dụng: cách white-label mà không cần fork

GeekBye là một ứng dụng ghi chú cuộc họp. Pavleur là một ứng dụng luyện phỏng vấn bằng AI. Chúng trông như hai sản phẩm khác nhau bởi chúng hai sản phẩm khác nhau — nhưng được build từ một codebase, không fork và không có nhánh nào trôi dạt. Đây là mặt kỹ thuật của white-label: không phải "vì sao nên bán một ứng dụng mang thương hiệu riêng" (đó là một bài khác), mà là làm thế nào bạn thực sự biên dịch một repository thành hai ứng dụng — và cái bug kém hào nhoáng ẩn trong mọi hệ thống white-label cho tới khi bạn chủ động đi tìm nó.

Sinh ra, đừng rẽ nhánh

Cách sai để chạy hai sản phẩm từ một codebase là rắc if (product === 'pavleur') khắp ứng dụng, hoặc fork rồi cầu nguyện hai bản sao không bao giờ tách xa nhau. Cả hai đều mục nát rất nhanh.

Cách tiếp cận của GeekBye là sinh mã lúc build. Mỗi sản phẩm có một file config — configs/geekbye/config.ts, configs/pavleur/config.ts — chứa danh tính của nó: tên, bundle id, URL protocol, repository chứa các bản phát hành cho auto-update, v.v. Trước mỗi lần build, một script chạy:

node scripts/build-product.js pavleur

Script đó đọc config của sản phẩm được chọn và làm ba việc cụ thể:

  1. Sinh ra hai module config — một cho renderer (src/config/product.ts), một cho main process (electron/config/product.ts) — mỗi cái được đóng dấu header "DO NOT EDIT MANUALLY — changes will be overwritten". Phần còn lại của ứng dụng chỉ việc import productName, applicationIdprotocol từ config và không bao giờ phải bận tâm mình là thương hiệu nào.
  2. Chỉ copy assets của thương hiệu đó — các icon, logo nhỏ, icon thông báo toast. Một bản build GeekBye về mặt vật lý không chứa một mẩu đồ họa Pavleur nào và ngược lại. Không có gì để rò rỉ.
  3. Ghi lại package.json (ở các bản build phát hành) — app id, tên sản phẩm, URL protocol, repo GitHub releases để publish lên, và, rất gọn gàng, cả đoạn văn bản xin quyền microphone mang thương hiệu để macOS hỏi "Pavleur muốn truy cập microphone," chứ không phải nhầm thương hiệu.

Toàn bộ ứng dụng không biết gì về thương hiệu; thương hiệu được tiêm vào lúc build. Không có công tắc chuyển sản phẩm lúc runtime để mà làm sai, bởi lúc runtime chỉ có duy nhất một sản phẩm đã được nướng sẵn vào.

Một backend, hai thương hiệu

Cả hai ứng dụng nói chuyện với cùng một backend. Vậy làm sao máy chủ biết một request thuộc về sản phẩm nào — với bảng giá, prompt và giới hạn nào?

Hai tín hiệu, cả hai đều được gắn lúc build. Thứ nhất, một application id (geekbye hoặc pavleur) được nướng vào config và gửi kèm các request, để backend định tuyến đến hành vi của đúng sản phẩm. Thứ hai, một User-Agent mà client đính vào mọi lệnh gọi backend, theo dạng Product/version (platform) — ví dụ Pavleur/1.2.5 (win32) hay GeekBye/1.7.3 (darwin). Chỉ một header đó đã cho backend khả năng quy nguồn theo sản phẩm theo hệ điều hành miễn phí, không cần một trường analytics riêng. Trong khi đó, auto-update vẫn được cách ly: mỗi sản phẩm publish lên repository releases của riêng mình, nên một bản cập nhật GeekBye không bao giờ có thể được đưa tới một bản cài Pavleur.

Onboarding cũng khác nhau theo từng sản phẩm — wizard của GeekBye nói về phiên âm cuộc họp, của Pavleur nói về luyện phỏng vấn — và cái đúng được chọn lúc runtime từ chính application id duy nhất đã nướng sẵn. Cùng một bộ máy, hai cửa trước.

Cái bug: khi Pavleur tự gọi mình là GeekBye

Đây là chuyện chiến trường, và mọi hệ thống white-label đều có một phiên bản của nó.

Việc sinh config chạy tốt. Icon đổi đúng. Lời nhắc mic mang đúng thương hiệu. Protocol và bundle id đều chuẩn. Theo mọi thước đo nhìn thấy được, một bản build Pavleur chính là Pavleur. Vậy mà, trong một thời gian, mọi bản build Pavleur đều gọi về backend và tự xưng là GeekBye.

Thủ phạm là một chuỗi hardcode duy nhất, và nó ẩn ở nơi kém hào nhoáng nhất có thể tưởng tượng: bộ dựng User-Agent, nằm gọn trong một retry helper của tầng mạng. Mọi thứ hướng tới người dùng đều đã được dẫn qua config, nhưng cái tiện ích nằm sâu trong đường ống này vẫn còn một literal `GeekBye/${version}` bên trong. Chẳng ai nghĩ một retry utility là "việc thương hiệu," nên chẳng ai kiểm tra nó. Bản sửa chỉ bốn dòng — import productName động từ config và nội suy nó thay cho thương hiệu hardcode — nhưng bài học mới là toàn bộ vấn đề:

White-label không phải là "logo đã đổi chưa." Mà là "mọi chuỗi định danh có đi qua config không." Và những kẻ sót lại cuối cùng không bao giờ nằm trong UI, nơi bạn sẽ nhìn vào. Chúng nằm trong User-Agent, trình xử lý protocol của deep link, câu chữ xin quyền, các thông báo lỗi — thứ đường ống mà không ai xếp vào mục "thương hiệu." Phòng tuyến đáng tin duy nhất là coi literal thương hiệu như một thứ phải chủ động săn lùng: grep cả codebase tìm cái tên hardcode và làm fail bản build nếu nó xuất hiện ở bất kỳ đâu ngoài chỗ config vốn được giao nhiệm vụ định nghĩa nó.

Ba điều white-label dạy chúng tôi

  1. Sinh ra, đừng rẽ nhánh. Sinh mã lúc build cho hai module config nhỏ cộng với copy assets có chọn lọc thắng cả fork lẫn một rừng kiểm tra product lúc runtime. Ứng dụng giữ nguyên trạng thái không biết gì về thương hiệu; thương hiệu là một đầu vào của build.
  2. Chuỗi định danh rò rỉ vào đường ống. UI là 90% dễ. 10% khó là User-Agent, URL protocol, chuỗi xin quyền mic, câu chữ báo lỗi. Hãy dẫn tất cả qua một export productName / applicationId duy nhất — và grep tên thương hiệu thô như một cổng chặn trong CI để không một literal lạc loài nào có thể lên tàu.
  3. Một backend, nhiều thương hiệu, phân biệt bằng id và header. Một application id lúc build cộng với User-Agent dạng Product/version (platform) cho khả năng quy nguồn theo sản phẩm và theo nền tảng mà không nhân đôi chút hạ tầng nào — tách bạch gọn gàng danh tính build-time bất biến khỏi hành vi runtime.

Về mặt sản phẩm và quyền riêng tư của việc vận hành một notetaker white-label dưới thương hiệu của riêng bạn, xem AI notetaker white-label. Đây là chương thứ sáu của câu chuyện v1 sẽ trở thành GeekBye v2 — về chương trước, xem một bản phát hành 127 commit thực chất là gì (v1.7.0); và cho toàn bộ vòng cung, xem giải phẫu việc xuất xưởng phần mềm đến độ hoàn hảo.

Bài Viết Liên Quan

Gỡ Backend Ra Khỏi Đường Đi Của Upload
Steven
Steven10 phút đọc

Gỡ Backend Ra Khỏi Đường Đi Của Upload

GeekBye quay lại màn hình của bạn và lưu video vào Google Drive của bạn. Phiên bản đầu tiên đẩy mọi bản ghi qua chính máy chủ của GeekBye trên đường tới đó; một bản phát hành sau, tập tin đi thẳng từ máy của bạn tới Drive, và backend bị giáng xuống thành thứ chỉ giữ một con trỏ. Phần thú vị là cái phiên bản «trực tiếp, có thể tiếp tục» ấy thực ra chứa ít code đến mức nào — vì khả năng tiếp tục đến từ việc xóa đi một proxy, chứ không phải từ việc viết ra một cái.

Kỹ thuật
Kiến trúc
Desktop
Một bản phát hành 127 commit thực chất là gì
Steven
Steven8 phút đọc

Một bản phát hành 127 commit thực chất là gì

GeekBye v1.7.0 là 127 commit trong mười một ngày. Nhìn từ ngoài, nó giống như một trăm chuyện lặt vặt. Nhìn từ bên trong, đó là hai tính năng lớn được bện vào nhau — và một trong hai được xây sai chỗ, rồi bị gỡ ra và dựng lại giữa chừng bản phát hành. Đây là giải phẫu của một bản phát hành lớn.

Kỹ thuật
Bản phát hành
Kiến trúc
Sự Im Lặng Vốn Chịu Tải
Steven
Steven10 phút đọc

Sự Im Lặng Vốn Chịu Tải

Hai bản phát hành cuối cùng của GeekBye v1 đều xoay quanh cùng một sự thật khó chịu: phiên âm thời gian thực trên một mạng thật không phải là không mất mát, và nước đi trung thực là thôi giả vờ rằng nó như vậy. v1.8.20 giữ một bản sao của mỗi audio chunk trên đĩa trước khi bỏ nó trong một lần kết nối lại, và bắt đầu đánh dấu các khoảng trống trong transcript ra thành tiếng. v1.9.0 thôi gửi im lặng để tiết kiệm bandwidth — rồi phát hiện ra im lặng chính là tín hiệu chính xác mà bộ phiên âm dùng để biết một câu đã kết thúc. Hai bản phát hành về cái giá của việc vứt đồ đi.

Kỹ thuật
Audio
Độ tin cậy