Steven
Steven3 นาทีที่อ่าน

โค้ดเบสเดียว สองแอป: วิธีทำ White-Label โดยไม่ต้อง Fork

GeekBye และ Pavleur คือแอปเดสก์ท็อปสองแบรนด์ที่สร้างจาก repository เดียว — ไม่มี fork ไม่มีโค้ดเบสซ้ำซ้อน นี่คือกลไกตอน build ที่ทำให้โค้ดเบสเดียวคอมไพล์ออกมาเป็นสองโปรดักต์ และบั๊กบรรทัดเดียวที่ทำให้แอปตัวที่สองของเราแนะนำตัวเองด้วยชื่อที่ผิด

วิศวกรรม
สถาปัตยกรรม
บิลด์
GeekBye รีลีส
โค้ดเบสเดียว สองแอป: วิธีทำ White-Label โดยไม่ต้อง Fork

GeekBye คือแอปจดโน้ตการประชุม Pavleur คือแอปฝึกสัมภาษณ์งานด้วย AI ทั้งสองดูเหมือนโปรดักต์คนละตัวเพราะมันเป็นโปรดักต์คนละตัวจริง ๆ — แต่ถูกสร้างจากโค้ดเบสเดียว ไม่มี fork และไม่มี branch ที่ค่อย ๆ เพี้ยนออกจากกัน นี่คือด้านวิศวกรรมของ white-label: ไม่ใช่ "ทำไมถึงควรขายแอปติดแบรนด์" (นั่นคืออีกโพสต์หนึ่ง) แต่คือวิธีคอมไพล์ repository เดียวออกมาเป็นสองแอปกันจริง ๆ — และบั๊กไร้ความหรูหราที่ซ่อนอยู่ในทุกระบบ white-label จนกว่าคุณจะลงมือออกตามหามัน

Generate อย่าแตก branch

วิธีที่ผิดในการรันสองโปรดักต์จากโค้ดเบสเดียวคือโปรย if (product === 'pavleur') ไว้ทั่วทั้งแอป หรือ fork แล้วภาวนาว่าสำเนาทั้งสองจะไม่มีวันแตกต่างกัน ทั้งสองทางเน่าเร็วพอกัน

แนวทางของ GeekBye คือ การ generate โค้ดตอน build แต่ละโปรดักต์มีไฟล์ config — configs/geekbye/config.ts, configs/pavleur/config.ts — เก็บตัวตนของมันไว้: ชื่อ, bundle id, URL protocol, repository ของ releases สำหรับอัปเดตอัตโนมัติ และอื่น ๆ ก่อน build สคริปต์ตัวหนึ่งจะรัน:

node scripts/build-product.js pavleur

สคริปต์นั้นอ่าน config ของโปรดักต์ที่เลือกแล้วทำสามอย่างอย่างเป็นรูปธรรม:

  1. Generate โมดูล config สองไฟล์ — หนึ่งสำหรับ renderer (src/config/product.ts) หนึ่งสำหรับ main process (electron/config/product.ts) — แต่ละไฟล์ประทับหัวไฟล์ว่า "DO NOT EDIT MANUALLY — changes will be overwritten" ส่วนที่เหลือของแอปแค่ import productName, applicationId และ protocol จาก config โดยไม่ต้องคิดเลยว่าตัวเองเป็นแบรนด์ไหน
  2. คัดลอกเฉพาะ assets ของแบรนด์นั้น — ไอคอน โลโก้เล็ก ไอคอนของการแจ้งเตือนแบบ toast — build ของ GeekBye ไม่มีงานศิลป์ของ Pavleur อยู่ในตัวเลยแม้แต่ชิ้นเดียว และกลับกันก็เช่นกัน ไม่มีอะไรให้รั่ว
  3. เขียน package.json ใหม่ (ใน build สำหรับ release) — app id, ชื่อโปรดักต์, URL protocol, repo releases บน GitHub ที่จะ publish ไป และที่เนียนมากคือข้อความขอสิทธิ์ไมโครโฟนที่ติดแบรนด์ เพื่อให้ macOS ถามว่า "Pavleur ต้องการเข้าถึงไมโครโฟน" ไม่ใช่แบรนด์ที่ผิด

ทั้งแอปไม่รู้จักแบรนด์ของตัวเอง (brand-agnostic) แบรนด์ถูกฉีดเข้าไปตอน build ไม่มีสวิตช์สลับโปรดักต์ตอน runtime ให้ทำพลาด เพราะตอน runtime มีโปรดักต์เพียงตัวเดียวที่ถูกอบมากับตัวแอปเสมอ

Backend เดียว สองแบรนด์

ทั้งสองแอปคุยกับ backend เดียวกัน แล้วเซิร์ฟเวอร์รู้ได้อย่างไรว่าคำขอหนึ่งเป็นของโปรดักต์ไหน — และควรใช้ราคา พรอมป์ต และลิมิตชุดไหน?

สัญญาณสองอย่าง ทั้งคู่ถูกกำหนดตอน build อย่างแรกคือ application id (geekbye หรือ pavleur) ที่อบไว้ใน config และส่งไปพร้อมคำขอ เพื่อให้ backend route ไปยังพฤติกรรมของโปรดักต์ที่ถูกต้อง อย่างที่สองคือ User-Agent ที่ client แนบไปกับทุกการเรียก backend ในรูปแบบ Product/version (platform) — เช่น Pavleur/1.2.5 (win32) หรือ GeekBye/1.7.3 (darwin) เฮดเดอร์ตัวเดียวนั้นให้การระบุที่มาทั้งต่อโปรดักต์และต่อ OS แก่ backend ฟรี ๆ โดยไม่ต้องมีฟิลด์ analytics แยกต่างหาก ส่วนการอัปเดตอัตโนมัตินั้นแยกขาดจากกัน: แต่ละโปรดักต์ publish ไปยัง repository ของ releases ของตัวเอง อัปเดตของ GeekBye จึงไม่มีวันถูกเสิร์ฟให้เครื่องที่ติดตั้ง Pavleur

ขั้นตอน onboarding ก็ต่างกันไปตามโปรดักต์ — วิซาร์ดของ GeekBye ว่าด้วยการถอดเสียงการประชุม ของ Pavleur ว่าด้วยการฝึกสัมภาษณ์ — และตัวที่ถูกต้องถูกเลือกตอน runtime จาก application id ตัวเดียวที่อบมาแล้วนั่นเอง กลไกเดียวกัน ประตูหน้าสองบาน

บั๊ก: ตอนที่ Pavleur เรียกตัวเองว่า GeekBye

นี่คือเรื่องเล่าจากสนามจริง และมันคือเรื่องที่ทุกระบบ white-label มีเวอร์ชันของตัวเอง

การ generate config ทำงานได้ ไอคอนสลับถูก ข้อความขอไมค์ติดแบรนด์แล้ว protocol และ bundle id ถูกต้องหมด วัดด้วยทุกมาตรวัดที่มองเห็นได้ build ของ Pavleur ก็คือ Pavleur แต่กระนั้น อยู่พักหนึ่ง ทุก build ของ Pavleur ก็ โทรกลับบ้านหา backend โดยเรียกตัวเองว่า GeekBye

ตัวการคือสตริง hardcode ตัวเดียว และมันซ่อนอยู่ในที่ที่ไร้ความหรูหราที่สุดเท่าที่จะจินตนาการได้: ตัวสร้าง User-Agent ที่ฝังอยู่ใน retry helper ของเน็ตเวิร์ก ทุกอย่างที่ผู้ใช้มองเห็นถูกทำให้วิ่งผ่าน config หมดแล้ว แต่ยูทิลิตี้ท่อลึกตัวนี้ยังมีลิเทอรัล `GeekBye/${version}` อยู่ข้างใน ไม่มีใครมองว่า retry utility เป็น "งานแบรนด์ดิ้ง" จึงไม่มีใครเช็คมัน ตัวแก้คือโค้ดสี่บรรทัด — import productName แบบไดนามิกจาก config แล้ว interpolate มันแทนแบรนด์ที่ hardcode — แต่บทเรียนต่างหากคือประเด็นทั้งหมด:

White-label ไม่ใช่คำถามว่า "โลโก้เปลี่ยนหรือยัง" แต่คือ "สตริงตัวตนทุกตัววิ่งผ่าน config หรือยัง" และตัวตกค้างสุดท้ายไม่เคยอยู่ใน UI ที่คุณจะไปมองหา พวกมันอยู่ใน User-Agent, ตัวจัดการ protocol ของ deep link, ข้อความขอสิทธิ์, ข้อความ error — ท่อ plumbing ที่ไม่มีใครจัดหมวดว่าเป็น "แบรนด์" เกราะป้องกันเดียวที่วางใจได้คือปฏิบัติกับลิเทอรัลแบรนด์เหมือนสิ่งที่ต้องออกล่าอย่างจริงจัง: grep ทั้งโค้ดเบสหาชื่อที่ hardcode แล้วทำให้ build ล้มเหลวถ้ามันโผล่ที่ไหนก็ตามนอก config ที่ควรเป็นผู้นิยามมัน

สามสิ่งที่ white-label สอนเรา

  1. Generate อย่าแตก branch การ generate โค้ดตอน build เป็นโมดูล config เล็ก ๆ สองไฟล์บวกการคัดลอก assets แบบคัดเลือก ชนะทั้งการ fork และดงเช็คโปรดักต์ตอน runtime แอปคงความไม่รู้จักแบรนด์ไว้ แบรนด์เป็นแค่อินพุตของ build
  2. สตริงตัวตนรั่วเข้าไปในท่อ plumbing UI คือ 90% ที่ง่าย 10% ที่ยากคือ User-Agent, URL protocol, สตริงขอสิทธิ์ไมค์, ข้อความ error จงให้ทั้งหมดวิ่งผ่าน export productName / applicationId ชุดเดียว — และ grep หาชื่อแบรนด์ดิบเป็นเกตใน CI เพื่อไม่ให้ลิเทอรัลหลงเหลือหลุดออกไปได้เลย
  3. Backend เดียว หลายแบรนด์ แยกจากกันด้วย id และเฮดเดอร์ application id ตอน build บวก User-Agent แบบ Product/version (platform) ให้การระบุที่มาต่อโปรดักต์และต่อแพลตฟอร์มโดยไม่ต้องมีโครงสร้างพื้นฐานซ้ำซ้อนเลยแม้แต่นิดเดียว — แยกตัวตนตอน build ที่ไม่เปลี่ยนแปลงออกจากพฤติกรรมตอน runtime อย่างสะอาดหมดจด

สำหรับด้านโปรดักต์และความเป็นส่วนตัวของการรัน notetaker แบบ white-label ใต้แบรนด์ของคุณเอง ดูAI notetaker แบบ white-label นี่คือบทที่หกของเรื่องราวยุค v1 ที่จะกลายมาเป็น GeekBye v2 — สำหรับบทก่อนหน้า ดูรีลีสขนาด 127 คอมมิตจริง ๆ แล้วคืออะไร (v1.7.0); และสำหรับภาพรวมทั้งเส้นเรื่อง ดูกายวิภาคของการส่งมอบซอฟต์แวร์ให้สมบูรณ์แบบ

บทความที่เกี่ยวข้อง

รีลีสขนาด 127 คอมมิตจริง ๆ แล้วคืออะไร
Steven
Steven2 นาทีที่อ่าน

รีลีสขนาด 127 คอมมิตจริง ๆ แล้วคืออะไร

GeekBye v1.7.0 คือ 127 คอมมิตในสิบเอ็ดวัน จากภายนอกมันดูเหมือนเรื่องเล็ก ๆ ร้อยกว่าเรื่อง แต่จากภายในมันคือฟีเจอร์ใหญ่สองอย่างที่ถักทอเข้าด้วยกัน — และหนึ่งในนั้นถูกสร้างผิดที่ แล้วต้องรื้อออกมาสร้างใหม่กลางรีลีส นี่คือกายวิภาคของรีลีสขนาดใหญ่

วิศวกรรม
รีลีส
สถาปัตยกรรม
การหลุดของการเชื่อมต่อครั้งเดียวไม่ควรทำให้ทั้งแอปพัง — แต่ของเราพัง
Steven
Steven2 นาทีที่อ่าน

การหลุดของการเชื่อมต่อครั้งเดียวไม่ควรทำให้ทั้งแอปพัง — แต่ของเราพัง

เมื่อแบ็กเอนด์ของเราหลุดออฟไลน์กลางการประชุม มันไม่ได้แค่หยุดการถอดเสียง — มันทำให้ทั้งแอปพัง สาเหตุคือเหตุการณ์ที่ไม่ถูกจัดการเพียงตัวเดียว และการแก้ไขมีแค่สิบบรรทัด นี่คือชุดรีลีสที่ทำให้ GeekBye ยังคงล็อกอินอยู่และยังคงเชื่อมต่ออยู่ ผ่านสิ่งที่เคยฆ่ามันมาก่อน

วิศวกรรม
ความน่าเชื่อถือ
Electron
กายวิภาคของการส่งซอฟต์แวร์ให้สมบูรณ์แบบ: เมื่อ Code Review จับสิ่งที่ Test จับไม่ได้
Steven
Steven2 นาทีที่อ่าน

กายวิภาคของการส่งซอฟต์แวร์ให้สมบูรณ์แบบ: เมื่อ Code Review จับสิ่งที่ Test จับไม่ได้

ตลอดซีรีส์ GeekBye v2 เรื่องเดิมเกิดขึ้นซ้ำแล้วซ้ำเล่า: การแก้ไขผ่านทุกเทสต์บนเครื่องของนักพัฒนา แล้ว code review ก็พิสูจน์ว่ามันจะพังสำหรับเกือบทุกคน นี่คือเวิร์กโฟลว์เบื้องหลังเก้ารีลีส — ด่านรีวิว การจับปัญหาแบบแก้-ก่อน และวินัยเทสต์-ก่อน-ส่ง ที่เปลี่ยน "มันทำงานบนเครื่องผม" ให้เป็น "มันทำงานได้จริง"

วิศวกรรม
กระบวนการ
การรีวิวโค้ด