Monorepo vs polyrepo: เก็บเว็บ มือถือ แบ็กเอนด์ ให้สอดคล้องกัน
อธิบายความต่างระหว่าง monorepo กับ polyrepo สำหรับทีมที่ปล่อยเว็บ มือถือ และแบ็กเอนด์ เปรียบเทียบการจัดการ dependency การประสานปล่อยรุ่น และกลยุทธ์ CI เพื่อรักษาความเร็ว

ปัญหาจริง: ส่งการเปลี่ยนแปลงให้ตรงกันข้ามสามฐานโค้ด\n\nทีมไม่ได้ถกเถียงเรื่อง monorepo กับ polyrepo เพราะสนใจปรัชญา Git แต่เพราะการเปลี่ยนแปลงผลิตภัณฑ์เล็กๆ มักกลายเป็นการแก้สามจุดคือเว็บ มือถือ และแบ็กเอนด์ แล้วบางอย่างก็พังระหว่างทาง\n\nสิ่งที่พังไม่ใช่ UI เสมอไป แต่เป็นกาวที่มองไม่เห็น: สัญญา API ถูกเปลี่ยนโดยไม่มีอัพเดตที่ตรงกัน ไลบรารีแชร์ถูกอัพเดตที่เดียวแต่ลืมอีกที่ หรือ pipeline การ build ต้องมีขั้นตอนใหม่ เมื่อส่วนหนึ่งออกเร็วกว่าส่วนอื่น ผู้ใช้จะรู้สึกเป็นบั๊ก เช่น “ปุ่มมีบนเว็บ แต่แอปมือถือบอกว่าไม่รองรับ” หรือ “แอปรอโหลดตลอดไปเพราะการตอบกลับจากแบ็กเอนด์เปลี่ยนไป”\n\nเว็บ มือถือ และแบ็กเอนด์ ก็มีจังหวะการปล่อยรุ่นต่างกัน เว็บอาจปล่อยหลายครั้งต่อวัน แบ็กเอนด์ปล่อยได้บ่อยแต่ต้องวางแผนการโรลเอาต์ มือถือช้าที่สุดเพราะการรีวิวสโตร์และการอัพเดตของผู้ใช้ทำให้เกิดความล่าช้าจริงๆ การเปลี่ยนแปลง “ง่าย” เช่น เปลี่ยนชื่อฟิลด์ อาจบังคับให้คุณต้องวางแผนตามเลนที่ช้าที่สุด แม้ว่าจะมีเพียงหน้าจอเดียวที่ต้องการมัน\n\nคุณอาจจ่ายภาษีการประสานรีโพถ้าปัญหาเหล่านี้ยังเกิดขึ้น:\n\n- การเปลี่ยนแปลง API ที่ทำให้แตกหักถูกค้นพบหลัง merge\n- การปรับเวอร์ชันพึ่งพาการเตือนด้วยมือและสเปรดชีต\n- ฟีเจอร์หนึ่งต้องใช้หลาย PR ที่รอกันและกัน\n- CI ช้าเพราะบิลด์และทดสอบสิ่งที่เกินกว่าสิ่งที่เปลี่ยนจริง\n- การย้อนกลับรู้สึกเสี่ยงเพราะไม่ชัดว่า commit ไหนไปกับ release ไหน\n\nขนาดทีมและความเป็นผู้ใหญ่ของผลิตภัณฑ์จะเปลี่ยนคำตอบที่เหมาะสม ในช่วงแรก ทีมมักชนะโดยทำให้การประสานงานราคาถูกและมองเห็นได้สูง ถึงแม้สิ่งต่างๆ อาจรก ในขณะที่ทีมโตขึ้น ขอบเขตก็เริ่มมีความหมาย แต่ต้องมีสัญญาที่เสถียรและความเป็นเจ้าของที่ชัดเจน\n\nถ้าทุกการเปลี่ยนแปลงที่มีความหมายต้องไปลงสามที่ คุณจะจ่ายภาษีนั้นเสมอ กลยุทธ์รีโพคือเรื่องวิธีที่คุณอยากจ่ายมัน\n\n## พื้นฐานของ Monorepo และ Polyrepo แบบไม่ใช้ศัพท์เทคนิค\n\nรีโพคือที่เก็บโค้ดและประวัติของมัน เมื่อมีเว็บ มือถือ และแบ็กเอนด์ ตัวเลือกก็ตรงไปตรงมา: เก็บทุกอย่างด้วยกัน หรือแยกออก\n\nMonorepo คือรีโพเดียวที่มีหลายแอปและมักมีโค้ดแชร์อยู่ด้วย เว็บ, iOS/Android, บริการแบ็กเอนด์ และไลบรารีแชร์ นั่งอยู่ข้างกัน\n\nPolyrepo ตรงข้าม: แต่ละแอป (และบางครั้งแต่ละบริการ) มีรีโพของตัวเอง โค้ดแชร์มักกลายเป็นแพ็กเกจแยก หรือทีมก็คัดลอกชิ้นเล็กๆ เมื่อจำเป็น\n\nในงานประจำวัน Monorepo มักให้ความรู้สึกว่าแชร์โค้ดง่าย การเปลี่ยนข้ามแอปสามารถเป็น PR เดียว และกฎมีความสอดคล้อง ข้อแลกเปลี่ยนคือเรื่องเชิงสังคม: ความเป็นเจ้าของอาจไม่ชัดถ้าไม่ตั้งขอบเขตให้ดี และการเช็คแบบทั่วรีโพอาจรู้สึกเข้มงวด\n\nPolyrepo ให้ความรู้สึกว่าทีมแต่ละทีมเคลื่อนที่อิสระ รีโพโฟกัส และการควบคุมการเข้าถึงง่ายขึ้น แต่ข้อแลกเปลี่ยนคือการประสานงาน: การแชร์โค้ดต้องวางแผน และการเปลี่ยนข้ามแอปมักกลายเป็นหลาย PR ที่ต้องจับเวลาอย่างระมัดระวัง\n\nหลายทีมจบด้วยไฮบริด: แอปแยกรีโพ แต่สัญญาแชร์อยู่ที่เดียว หรือ monorepo เดียวแต่มีขอบเขตชัดเจนให้แต่ละทีมอยู่ในพื้นที่ของตัวเองส่วนใหญ่\n\nถ้าคุณใช้แพลตฟอร์มที่สร้างแบ็กเอนด์ เว็บ และมือถือจากแหล่งความจริงเดียว คุณจะลดการ drift ได้เพราะสัญญาและตรรกะอยู่ด้วยกัน AppMaster, for example, generates production-ready backend, web, and native mobile apps from a single model. นั่นไม่ทำให้ความเป็นจริงของการปล่อยรุ่นหายไป (มือถือยังช้ากว่า) แต่สามารถกำจัดงาน “อัปเดตครบสามที่หรือยัง?” ได้มาก\n\n## การจัดการ dependency: รักษาโค้ดแชร์ให้ปลอดภัย\n\nโค้ดแชร์คือที่ทีมเสียเวลา ไม่ว่าจัดรีโพแบบไหน การเปลี่ยนเล็กๆ ในไลบรารีแชร์หรือสัญญา API สามารถทำลายการบิลด์เว็บ การปล่อยมือถือ และการดีพลอยแบ็กเอนด์ในแบบต่างๆ\n\nเมื่อคุณแชร์ไลบรารี (คอมโพเนนต์ UI, กฎการตรวจสอบ, ตัวช่วย auth) คุณกำลังเลือกระหว่างเวอร์ชันเดียวสำหรับทุกคน หรือหลายเวอร์ชันเมื่อเวลาผ่านไป\n\n- เวอร์ชันเดียวเรียบง่ายและหลีกเลี่ยงความประหลาดใจ “มันทำงานบน branch ของฉัน”\n- หลายเวอร์ชันให้ทีมเคลื่อนที่ตามจังหวะตัวเอง แต่สร้างความรกและทำให้การแก้ช่องโหว่ยากขึ้น\n\nไคลเอนต์ API และสกีมาได้รับการดูแลเป็นพิเศษ การอัพเดตด้วยมือช้าและผิดพลาดง่าย รูปแบบที่ดีกว่าคือถือสกีมาเป็นแหล่งความจริงและสร้างไคลเอนต์จากมัน แล้วเช็คอินหรือสร้างใน CI เป้าหมายคือล้มเหลวเร็ว: ถ้าแบ็กเอนด์เพิ่มฟิลด์ที่ต้องมี ไคลเอนต์มือถือควรล้มเหลวตอน build ไม่ใช่สามวันใน QA\n\nการเปลี่ยนแปลงที่แตกหักแพร่กระจายเมื่อพฤติกรรมเปลี่ยนโดยไม่มีทางเลื่อนลงอย่างปลอดภัย เลือกการเปลี่ยนแบบ additive ก่อน (ฟิลด์ใหม่, endpoint ใหม่) แล้วค่อย deprecate หากต้องทำการแตกหัก ให้ใช้ endpoint เวอร์ชันหรือหน้าต่างความเข้ากันได้สั้นๆ\n\nตัวอย่างชัดเจน: แบ็กเอนด์เปลี่ยนชื่อ status เป็น state ถ้าเว็บอัพเดตวันนี้แต่มือถือปล่อยไม่ได้เป็นสัปดาห์ แบ็กเอนด์ต้องรับทั้งสองฟิลด์ในช่วงสัปดาห์นั้น หรือออก adapter ที่แมปฟิลด์เก่าไปใหม่\n\nกฎไม่กี่ข้อที่ทำให้การอัพเดต dependency น่าเบื่อ (ในทางที่ดี):\n\n- อัพเดตเป็นรอบเล็กๆ เป็นประจำ สัปดาห์ละครั้งชนะไตรมาสละครั้งใหญ่\n- ต้องมีการอนุมัติชัดเจนสำหรับการเปลี่ยนแปลงที่แตกหัก พร้อมโน้ตการย้ายสั้นๆ\n- อัตโนมัติเช็ค: การอัพเดต dependency, การสร้างไคลเอนต์ใหม่ และการทดสอบสัญญาพื้นฐาน\n\nกำหนดคำว่า “เสร็จ” เป็น “เว็บ มือถือ และแบ็กเอนด์ build ผ่าน” ไม่ใช่แค่รีโพของฉันผ่าน\n\nโค้ดที่สร้างอัตโนมัติช่วยลด drift แต่ไม่สามารถทดแทนวินัยได้ คุณยังต้องมีสัญญาเดียว ชัดเจน, การยกเลิกอย่างเป็นขั้นตอน และการอัพเดตที่คาดเดาได้\n\n## การประสานปล่อยรุ่น: ทำให้เว็บ มือถือ และแบ็กเอนด์สอดคล้องกัน\n\nการประสานปล่อยรุ่นคือจุดที่กลยุทธ์รีโพหยุดเป็นทฤษฎี ถ้าแบ็กเอนด์เปลี่ยนชื่อฟิลด์ เว็บอาจอัพเดตและปล่อยได้วันเดียวกัน มือถือต่างออกไป: การรีวิวสโตร์และการกระจายการอัพเดตของผู้ใช้สามารถเปลี่ยนความไม่ตรงกันเล็กๆ ให้กลายเป็นการร้องเรียนหลายวัน\n\nเป้าหมายปฏิบัติคือ: การกระทำของผู้ใช้ควรทำงานไม่ว่าชิ้นไหนอัพเดตก่อน ซึ่งหมายถึงการวางแผนสำหรับสถานะผสม ไม่ใช่สมมติว่าปล่อยพร้อมกันอย่างสมบูรณ์\n\n### รูปแบบเวอร์ชันที่ทีมใช้จริง\n\nทีมส่วนใหญ่ลงตัวในหนึ่งในแนวทางเหล่านี้:\n\n1) รถไฟปล่อยเดียว (shared release train): เว็บ มือถือ และแบ็กเอนด์ปล่อยในหน่วยเวอร์ชันเดียวกัน\n\n2) เวอร์ชันแยกตามบริการพร้อมกฎความเข้ากันได้: แต่ละแอป/บริการมีเวอร์ชันตัวเอง และแบ็กเอนด์รองรับช่วงเวอร์ชันลูกค้าที่กำหนดไว้\n\nรถไฟปล่อยเดียวดูเรียบร้อย แต่พังได้เมื่อมือถือช้า เวอร์ชันแยกทำให้กระดาษรกกว่า แต่เข้ากับความจริงมากกว่า ถ้าคุณเลือกแบบแยก ให้เขียนกฎเดียวแล้วบังคับใช้: แบ็กเอนด์เวอร์ชันไหนต้องรองรับมือถือเวอร์ชันใด และนานเท่าไร\n\nความล่าช้าของมือถือยังเปลี่ยนวิธีจัดการ hotfixs แก้ไขฝั่งเซิร์ฟเวอร์ออกได้เร็ว มือถืออาจไม่ถึงผู้ใช้เป็นวันๆ ช่วยให้แก้ไขฝั่งเซิร์ฟเวอร์ที่ทำให้เวอร์ชันมือถือเก่ายังทำงานได้ เมื่อจำเป็นต้องเปลี่ยนไคลเอนต์ ให้ใช้ feature flags และหลีกเลี่ยงการเอาฟิลด์เก่าออกจนกว่าจะรู้ว่าผู้ใช้ส่วนใหญ่ได้อัพเดตแล้ว\n\nตัวอย่าง: คุณเพิ่ม “คำสั่งการจัดส่ง” ไปยังฟอร์มคำสั่งซื้อ แบ็กเอนด์เพิ่มฟิลด์ใหม่ที่ไม่จำเป็น เว็บแสดงได้ทันที มือถือแสดงในสปรินต์ถัดไป ถ้าแบ็กเอนด์รับคำขอแบบเก่าและคงฟิลด์เป็น optional ทุกอย่างจะยังทำงานได้จนกว่ามือถือจะตามทัน\n\n### ใครเป็นเจ้าของปฏิทินการปล่อยรุ่น\n\nการประสานงานล้มเหลวเมื่อ “ทุกคนเป็นเจ้าของ” ดังนั้นไม่มีใครเป็น เจ้าของอาจเป็น tech lead, release manager หรือ product manager ที่มีการสนับสนุนด้านวิศวกรรม งานของพวกเขาคือป้องกันความประหลาดใจโดยทำให้ความคาดหวังการปล่อยมองเห็นและสม่ำเสมอ\n\nพวกเขาไม่จำเป็นต้องมีกระบวนการซับซ้อน แต่ต้องมีนิสัยที่ทำซ้ำได้: ปฏิทินปล่อยรุ่นง่ายๆ กับ cutoff และช่วง freeze, การตรวจข้ามทีมอย่างรวดเร็วก่อนเปลี่ยน API, และแผนชัดเจนเมื่อมือถือล่าช้า (รอแบ็กเอนด์หรือรักษาความเข้ากันได้)\n\nถ้าเวิร์กโฟลว์ของคุณสร้างเว็บ มือถือ และแบ็กเอนด์จากโมเดลเดียว ยังต้องมีเจ้าของปล่อยรุ่นอยู่ คุณมักจะมีจำนวนน้อยของเหตุการณ์ “อัปเดตครบทั้งสามที่หรือยัง?”\n\n## คุมเวลา CI ไม่ให้พุ่ง\n\nCI ช้ามักมาจากสาเหตุเดียวกันในทั้งสองรูปแบบ: คุณบิลด์มากเกินไป ติดตั้ง dependency ซ้ำๆ และรันการทดสอบทุกอย่างในทุกการเปลี่ยนแปลง\n\nตัวชวนเวลาทั่วไปได้แก่ การบิลด์เต็มสำหรับการเปลี่ยนแปลงเล็กๆ, ขาด cache, ชุดทดสอบที่รันทุกอย่าง และงานแบบอนุกรมที่สามารถรันวางคู่ขนานได้\n\nเริ่มจากการปรับปรุงที่ช่วยได้ทุกที่:\n\n- เก็บ cache ของการดาวน์โหลด dependency และผลลัพธ์การบิลด์\n- รัน lint, unit tests, และการบิลด์แบบคู่ขนานเท่าที่ทำได้\n- แยกการเช็คแบบเร็ว (ทุก commit) กับการเช็คช้า (main, กลางคืน, หรือก่อนปล่อย)\n\n### กลยุทธ์ Monorepo ที่ช่วยได้\n\nMonorepo เจ็บปวดเมื่อทุก commit ทริกเกอร์ pipeline “บิลด์ทั้งโลก” วิธีแก้คือบิลด์และทดสอบเฉพาะสิ่งที่ได้รับผลกระทบจากการเปลี่ยน\n\nใช้ตัวกรองเส้นทางและวิธีการเฉพาะสิ่งที่ได้รับผลกระทบ: ถ้าคุณเปลี่ยนโค้ด UI มือถือ อย่าบิลด์อิมเมจแบ็กเอนด์ ถ้าคุณแตะไลบรารีแชร์ ให้บิลด์และทดสอบเฉพาะแอปที่พึ่งพามัน หลายทีมทำให้เป็นทางการด้วยกราฟ dependency ง่ายๆ เพื่อให้ CI ตัดสินใจแทนการเดา\n\n### กลยุทธ์ Polyrepo ที่ป้องกันการ drift\n\nPolyrepo เร็วเพราะแต่ละรีโพเล็ก แต่บ่อยครั้งเสียเวลาเพราะการทำซ้ำและเครื่องมือไม่สอดคล้องกัน\n\nเก็บชุดเทมเพลต CI ร่วม (ขั้นตอนเดียวกัน แคชเดียวกัน ข้อตกลงเดียวกัน) เพื่อไม่ให้แต่ละรีโพคิดซ้ำ ปัก toolchain (เวอร์ชัน runtime, เครื่องมือบิลด์, linter) เพื่อหลีกเลี่ยงปัญหา “ทำงานในรีโพนี้ แต่ไม่ทำงานในอีกรีโพ” ถ้าการดาวน์โหลด dependency เป็นคอขวด ให้ตั้ง shared cache หรือ internal mirror\n\nตัวอย่าง: ฟีเจอร์เพิ่มฟิลด์ใหม่ “status” แบ็กเอนด์เปลี่ยน เว็บแสดง มือถือแสดง ใน monorepo CI ควรรันการทดสอบแบ็กเอนด์บวกเฉพาะเว็บและมือถือที่พึ่งพาไคลเอนต์ API ใน polyrepo แต่ละรีโพควรรันการเช็คเร็วของตัวเองและมี pipeline อินทิเกรชันแยกเพื่อตรวจสอบว่าทั้งสามรีลีสยังสอดคล้องกัน\n\nถ้าคุณ export โค้ดและรัน CI ของตัวเอง กฎเดียวกันใช้ได้: บิลด์เฉพาะสิ่งที่เปลี่ยน, ใช้แคชซ้ำอย่างแรง และสงวนการเช็คช้าสำหรับเมื่อมันเพิ่มมูลค่าจริงๆ\n\n## ขั้นตอนทีละขั้น: เลือกกลยุทธ์รีโพที่เหมาะกับทีม\n\nการตัดสินใจง่ายขึ้นเมื่อเริ่มจากงานประจำวันไม่ใช่อุดมคติ\n\n### 1) เขียนสิ่งที่ต้องเปลี่ยนพร้อมกัน\n\nเลือก 5–10 ฟีเจอร์ล่าสุดและจดว่าต้องแก้อะไรพร้อมกัน มาร์กว่ามีการแตะหน้าจอ UI, endpoint API, ตารางข้อมูล, กฎการยืนยันตัวตน หรือการตรวจสอบร่วมหรือไม่ ถ้าฟีเจอร์ส่วนใหญ่ต้องเปลี่ยนพร้อมกัน การแยกรีโพจะเจ็บปวดเว้นแต่กระบวนการปล่อยของคุณเข้มงวดมาก\n\n### 2) ติดตามโค้ดแชร์และการตัดสินใจร่วม\n\nโค้ดแชร์ไม่ใช่แค่ไลบรารี แต่รวมถึงสัญญา (สกีมา API), รูปแบบ UI และกฎธุรกิจ จดว่าพวกนั้นอยู่ที่ไหน ใครแก้ และการอนุมัติทำอย่างไร ถ้าชิ้นแชร์ถูกคัดลอกระหว่างรีโพ นั่นสัญญาณว่าคุณต้องการการควบคุมที่เข้มขึ้น ผ่าน monorepo หรือกฎเวอร์ชันที่เข้มงวด\n\n### 3) กำหนดขอบเขตและเจ้าของ\n\nตัดสินใจว่าหน่วยคืออะไร (แอป, บริการ, ไลบรารี) แล้วมอบเจ้าของให้แต่ละหน่วย ขอบเขตสำคัญกว่าการจัดรีโพ ถ้าไม่มีเจ้าของ monorepo จะกลายเป็นเสียงดัง ถ้าไม่มีเจ้าของ polyrepo จะหลุดการเชื่อมต่อ\n\nเช็คลิสต์ง่ายๆ: รีโพหรือโฟลเดอร์ต่อบริการ/แอปที่ deploy ได้, ที่เดียวสำหรับสัญญาแชร์, ที่เดียวสำหรับคอมโพเนนต์ UI ที่ใช้ร่วมกันจริงๆ, กฎชัดเจนว่าตรรกะธุรกิจอยู่ที่ใด, และเจ้าของที่เอกสารไว้สำหรับแต่ละอัน\n\n### 4) เลือกรูปแบบการปล่อยที่ทำตามได้\n\nถ้าการปล่อยมือถือช้ากว่าการเปลี่ยนแบ็กเอนด์ คุณต้องมีแผนความเข้ากันได้ (API เวอร์ชัน, ฟิลด์ที่เข้ากันได้ย้อนหลัง, หรือหน้าต่างสนับสนุนที่ชัดเจน) ถ้าทุกอย่างต้องปล่อยพร้อมกัน release train อาจใช้ได้แต่เพิ่มงานประสาน\n\nทำกฎการทำงานสั้นๆ: branch สั้นๆ, merge เล็กๆ, และเส้นทาง hotfix ชัดเจน\n\n### 5) ออกแบบ CI รอบการเปลี่ยนแปลงทั่วไป\n\nอย่าออกแบบ CI สำหรับกรณีแย่ที่สุดตั้งแต่วันแรก ออกแบบให้ตรงกับสิ่งที่คนทำทุกวัน\n\nถ้าการ commit ส่วนใหญ่แตะแค่ UI เว็บ ให้รัน lint และ unit tests เว็บเป็นค่าเริ่มต้น และรัน end-to-end เต็มรูปแบบเป็นตารางหรือก่อนปล่อย ถ้าปัญหาส่วนใหญ่เกิดจากการ drift ของ API ลงทุนใน contract tests และการสร้างไคลเอนต์ก่อน\n\n## ตัวอย่าง: ฟีเจอร์เดียวแตะเว็บ มือถือ และแบ็กเอนด์\n\nภาพทีมเล็กที่สร้างพอร์ทัลลูกค้า (เว็บ), แอปภาคสนาม (มือถือ), และ API (แบ็กเอนด์) คำขอเข้ามา: เพิ่มฟิลด์ใหม่ “Service status” ลงในงานและแสดงทุกที่\n\nการเปลี่ยนฟังดูเล็ก แต่มันคือการทดสอบการประสาน แบ็กเอนด์เพิ่มฟิลด์และอัพเดตการตรวจสอบ เว็บแสดงและอัพเดตฟิลเตอร์ มือถือต้องแสดงแบบออฟไลน์ ซิงค์ และจัดการกรณีขอบ\n\nปัญหาจริง: การเปลี่ยน API ทำให้แตกหัก ชื่อฟิลด์เปลี่ยนจาก status เป็น service_status และไคลเอนต์เก่าจะพังถ้าไม่จัดการ\n\n### Monorepo เปลี่ยนอะไรได้\n\nที่นี่ monorepo มักให้ความรู้สึกสงบกว่า แพตช์แบ็กเอนด์ เว็บ และมือถือสามารถลงใน PR เดียว (หรือชุด commit ที่ประสานกัน) CI รันการทดสอบเฉพาะส่วนที่ได้รับผลกระทบ และคุณสามารถแท็ก release เดียวที่รวมการอัพเดตทั้งสาม\n\nความเสี่ยงหลักเป็นเชิงสังคมไม่ใช่เชิงเทคนิค: รีโพเดียวทำให้ merge การเปลี่ยนแปลงที่แตกหักง่าย ดังนั้นกฎการรีวิวต้องเข้มงวด\n\n### Polyrepo เปลี่ยนอะไรได้\n\nในรีโพแยกแต่ละแอปมีตารางของตัวเอง แบ็กเอนด์อาจปล่อยก่อน เว็บและมือถือจะพยายามตาม ถ้าการปล่อยมือถือต้องรีวิวสโตร์ “การแก้” อาจใช้เวลาหลายวันแม้โค้ดจะเล็ก ทีมมักแก้ด้วยโครงสร้างมากขึ้น: endpoint เวอร์ชัน, การตอบกลับที่เข้ากันได้ย้อนหลัง, หน้าต่าง deprecation ยาวขึ้น และขั้นตอนการโรลเอาต์ชัดเจน มันใช้งานได้ แต่มันคือการทำงานต่อเนื่อง\n\nถ้าตัดสินจากข้อมูลล่าสุด ให้ดูเดือนที่ผ่านมา:\n\n- ถ้าเหตุการณ์มักมาจากเวอร์ชันที่ไม่ตรงกัน ให้เอียงไปหาการประสานที่เข้มกว่า\n- ถ้าการปล่อยเกิดบ่อยและต้องตรงเวลา (โดยเฉพาะมือถือ) หลีกเลี่ยงการเปลี่ยนแปลงที่แตกหักหรือรวมศูนย์การเปลี่ยนแปลง\n- ถ้าทีมเป็นอิสระและไม่ค่อยแตะฟีเจอร์เดียวกัน overhead ของ polyrepo อาจคุ้มค่า\n\n## ความผิดพลาดและกับดักที่ควรหลีกเลี่ยง\n\nทีมส่วนใหญ่ไม่ล้มเพราะเลือกโครงสร้างรีโพ “ผิด” แต่เพราะนิสัยประจำวันค่อยๆ เพิ่มแรงเสียดทานจนการเปลี่ยนแปลงทุกอย่างดูเสี่ยง\n\n### โค้ดแชร์กลายเป็นที่ทิ้งของ\n\nไลบรารีแชร์น่าดึงดูด: helper, types, UI bits, “ชั่วคราว” สักพักก็กลายเป็นที่เก็บของเก่าและไม่มีใครรู้ว่าปลอดภัยจะแก้ไหม\n\nเก็บโค้ดแชร์ให้เล็กและเข้มงวด “แชร์” ควรหมายถึงถูกใช้โดยหลายทีม, รีวิวอย่างรอบคอบ, และเปลี่ยนด้วยเจตนา\n\n### การผูกติดแน่นผ่านสมมติฐานที่ซ่อนอยู่\n\nแม้ในรีโพแยก ระบบยังผูกติดกัน สมมติฐานย้ายที่: รูปแบบวันที่ ค่า enum กฎสิทธิ์ และ “ฟิลด์นี้มีเสมอ”\n\nตัวอย่าง: มือถือถือ status = 2 เป็น “Approved”, เว็บถือเป็น “Confirmed”, แบ็กเอนด์เปลี่ยนลำดับ enum และทุกอย่างพังในแบบที่ดูสุ่ม ป้องกันด้วยการเอกสารสัญญา (ความหมายของฟิลด์ ค่าอนุญาต) และปฏิบัติต่อมันเป็นกฎผลิตภัณฑ์ ไม่ใช่เรื่องเล็กๆ\n\n### ความเป็นเจ้าของไม่ชัดเจน\n\nเมื่อทุกคนเปลี่ยนได้ทุกอย่าง การรีวิวผิวเผินและข้อผิดพลาดหลุดผ่าน เมื่อไม่มีใครเป็นเจ้าของ พบบั๊กค้างเป็นสัปดาห์\n\nกำหนดเจ้าของสำหรับเว็บ มือถือ แบ็กเอนด์ และโมดูลแชร์ Ownership ไม่ได้ปิดกั้นการมีส่วนร่วม แต่มั่นใจว่าการเปลี่ยนแปลงได้รับการพิจารณาที่ถูกต้อง\n\n### CI โตขึ้นโดยไม่มีการตัดแต่ง\n\nCI มักเริ่มเล็ก แต่เหตุการณ์แต่ละอย่างเพิ่มงานใหม่ “แค่เผื่อไว้” หลายเดือนต่อมา มันช้าและแพง ผู้คนเลี่ยงมัน\n\nกฎง่ายๆ ช่วยได้: ทุกงาน CI ต้องมีวัตถุประสงค์และเจ้าของชัดเจน และควรถูกลบเมื่อมันไม่จับปัญหาแล้ว สัญญาณเตือนคือการทดสอบซ้ำซ้อน ข้ามงาน, งานที่แดงหลายวัน, หรือ pipeline ที่ทริกเกอร์บิลด์มือถือบนการเปลี่ยนแปลงเฉพาะแบ็กเอนด์\n\n### การประสานปล่อยรุ่นพึ่งพาความรู้เฒ่า\n\nถ้าการปล่อยพึ่งพาคนคนเดียวที่จำขั้นตอนและเรื่องพิเศษ คุณจะช้าลงและพังบ่อย จดขั้นตอนปล่อย ทำให้เป็นแบบซ้ำได้ และอัตโนมัติการตรวจสอบน่าเบื่อ แม้เครื่องมือของคุณจะสร้างแบ็กเอนด์และไคลเอนต์สม่ำเสมอ คุณยังต้องมีกฎการปล่อยที่ชัดเจน\n\n## เช็คลิสต์ก่อนย้ายหรือจัดรีโพใหม่\n\nก่อน reorganize รีโพ ให้ตรวจสอบการทำงานจริงของทีม เป้าหมายไม่ใช่โครงสร้างสมบูรณ์แบบ แต่ลดความประหลาดใจเมื่อการเปลี่ยนแตะเว็บ มือถือ และแบ็กเอนด์\n\nถามห้าข้อ:\n\n- Independent shipping: คุณปล่อยแพตช์แบ็กเอนด์ได้โดยไม่บังคับให้มือถืออัพเดตวันเดียวกันหรือไม่?\n- API change rules: คุณมีสัญญาเป็นลายลักษณ์อักษรว่าจะ deprecate ยังไงและนานเท่าไรหรือไม่?\n- Shared code discipline: ไลบรารีแชร์ถูกรีวิวและเวอร์ชันอย่างสม่ำเสมอหรือไม่?\n- CI that runs what matters: CI บอกได้ไหมว่าอะไรเปลี่ยนและรันการบิลด์/เทสต์เฉพาะส่วนที่เกี่ยวข้อง?\n- One release view: มีที่เดียวดูได้ไหมว่าสิ่งที่กำลังจะปล่อยออกไปคืออะไร ข้ามเว็บ มือถือ และแบ็กเอนด์ พร้อมเจ้าของและวันที่?\n\nตัวอย่างง่ายๆ: ฟิลด์ “address” ใหม่เพิ่มในเช็คเอาต์ ถ้าแบ็กเอนด์ปล่อยก่อน แอปมือถือเก่าไม่ควรพัง นั่นหมายถึง API ยอมรับทั้ง payload เก่าและใหม่สักระยะ และการอัพเดตไคลเอนต์เป็นทางเลือก ไม่ใช่ข้อบังคับ\n\n## ขั้นตอนต่อไป: ลดงานประสานและปล่อยด้วยความมั่นใจ\n\nเป้าหมายไม่ใช่โครงสร้างรีโพที่ “ถูก” แต่คือการลดการส่งต่อ ความประหลาดใจ และคำถามว่า “อันไหนออนไลน์แล้ว?”\n\nเขียนบันทึกการตัดสินใจสั้นๆ: ทำไมคุณเลือกวิธีปัจจุบัน คุณคาดหวังว่าจะดีขึ้นอย่างไร และยอมรับข้อแลกเปลี่ยนอะไร บทบันทึกนี้ควรทบทวนทุก 6–12 เดือน หรือเร็วกว่านั้นถ้าขนาดทีมหรือความถี่ปล่อยเปลี่ยน\n\nก่อนย้ายไฟล์ ค้นหาการเปลี่ยนแปลงเล็กที่สุดที่แก้ปัญหาได้จริง:\n\n- เพิ่มและปฏิบัติตามกฎเวอร์ชันสำหรับแพ็กเกจแชร์\n- กำหนดสัญญา API และบังคับด้วย contract tests ใน CI\n- ตกลงเช็คลิสต์การปล่อยเดียวข้ามเว็บ มือถือ และแบ็กเอนด์\n- ใช้ preview environments สำหรับการเปลี่ยนที่แตะหลายส่วน\n- กำหนดงบประมาณเวลา CI (เช่น การเช็ค PR ภายใน 15 นาที)\n\nถ้าการผูกติดข้ามฐานโค้ดคือคอขวด การลดจำนวนจุดที่ต้องเปลี่ยนอาจสำคัญกว่าการจัดรีโพ บางทีมทำโดยย้ายตรรกะและการสร้างข้อมูลไปไว้ในแหล่งความจริงเดียว\n\nถ้าคุณอยากสำรวจวิธีนั้น AppMaster (appmaster.io) ถูกออกแบบมาเพื่อสร้างบริการแบ็กเอนด์ แอปเว็บ และแอปมือถือเนทีฟจากโมเดลข้อมูลและตรรกะธุรกิจร่วมกัน วิธีประเมินความเสี่ยงต่ำคือสร้างเครื่องมือภายในเล็กๆ สักตัวก่อน แล้วตัดสินใจจากว่ามันลดงานประสานได้แค่ไหน\n\nเส้นทางที่มั่นใจมักน่าเบื่อโดยตั้งใจ: เอกสารการตัดสินใจ ลดการผูกติด อัตโนมัติการเช็ค และเปลี่ยนโครงสร้างรีโพเฉพาะเมื่อข้อมูลบอกว่าจะช่วยจริงๆ\n
คำถามที่พบบ่อย
เริ่มจากดูว่าแต่ละฟีเจอร์ต้องเปลี่ยนอะไรบ้างระหว่างเว็บ มือถือ และแบ็กเอนด์ หากงานส่วนใหญ่ข้ามหลายส่วนและการประสานงานคือปัญหาใหญ่ โมโนรีโปหรือแนวทาง “สัญญาเดียว” ที่เข้มแข็งมักลดการเกิดบั๊กได้ดี แต่ถ้าทีมเกือบไม่ทับซ้อนและต้องการความเป็นอิสระ Polyrepo ก็ทำงานได้ดี โดยต้องมีกฎความเข้ากันได้ชัดเจน
สาเหตุหลักคือการลื่นไหลของ API (API drift), เวอร์ชันของไลบรารีที่ไม่ตรงกัน และความต่างของเวลาปล่อยรุ่นโดยเฉพาะการส่งแอปขึ้นสโตร์มือถือ ทางแก้คือวางแผนรับมือกับสถานะที่มีหลายเวอร์ชันพร้อมกันในโลกจริง และทำให้การเปลี่ยนแปลงที่ทำให้แตกหักเกิดขึ้นน้อยและมีการอนุมัติอย่างตั้งใจ
ถือสกีม่าของ API เป็นแหล่งความจริงและสร้างไคลเอนต์จากสกีมาเหล่านั้น เพื่อให้ความไม่ตรงกันล้มเหลวตั้งแต่ขั้นตอน build แทนที่จะเจอใน QA หรือโปรดักชัน แนะนำให้เพิ่มฟิลด์อย่างเสริมก่อน แล้วค่อย deprecate ของเก่าเมื่อพร้อม และกำหนดหน้าต่างความเข้ากันได้สั้นๆ เมื่อจำเป็นต้องเปลี่ยนชื่อหรือเอาออก
อัพเดตเป็นระยะสั้นและบ่อย (เช่น รายสัปดาห์) ดีกว่าการอัพเดตครั้งใหญ่เป็นไตรมาส ต้องมีการอนุมัติชัดเจนสำหรับการเปลี่ยนแปลงที่แตกหัก และแนบโน้ตการย้ายสั้นๆ กับแต่ละการเปลี่ยนแปลง กำหนดว่า “เสร็จ” คือเมื่อเว็บ มือถือ และแบ็กเอนด์ build ผ่าน ไม่ใช่แค่รีโพเดียวผ่าน
โดยทั่วไปให้ใช้โมเดลเวอร์ชันแยกตามบริการ แต่มีกรอบความเข้ากันได้ชัดเจน: เวอร์ชันแบ็กเอนด์ใดรองรับไคลเอนต์เวอร์ชันไหนและนานเท่าไร “Release train” เดียวอาจเหมาะในช่วงแรก แต่ความล่าช้าของมือถือมักทำให้วิธีนั้นลำบาก
ทำให้แบ็กเอนด์ยังรองรับไคลเอนต์เก่าเพื่อให้ผู้ใช้มือถือไม่แตกหักในช่วงผู้ใช้ยังไม่ได้อัพเดต ใช้ฟิลด์แบบ additive, อย่าเอาพฤติกรรมเก่าออกเร็วเกินไป และใช้ feature flags เมื่อจำเป็นต้องปล่อยการเปลี่ยนแปลงที่ผู้ใช้เห็นทีละส่วน
มอบหมายหน้าที่ชัดเจน—อาจเป็น tech lead, release manager หรือ product owner ที่มีทีมวิศวกรรมช่วย—ให้ดูแลปฏิทินการปล่อยรุ่น หน้าที่คือทำให้ความคาดหวังมองเห็นได้และสม่ำเสมอ เช่น กำหนด cutoff, เวลาห้ามเปลี่ยน และแผนสำรองเมื่อมือถือล่าช้า
สร้าง CI ให้รันเฉพาะสิ่งที่เปลี่ยน และเก็บ cache ให้มากที่สุด แยกการตรวจสอบเร็ว (ทุก commit) กับการตรวจสอบช้า (main, กลางคืน หรือก่อนปล่อย) เพื่อให้ dev ได้ feedback เร็วโดยไม่ต้องจ่ายต้นทุนการทดสอบเต็มๆ ทุกครั้ง
ใช้ตัวกรองเส้นทางและวิธี “เฉพาะสิ่งที่ได้รับผลกระทบ” เพื่อไม่ให้รีบิวด์ทั้งโปรเจกต์สำหรับการเปลี่ยนแปลงเล็กๆ ถ้าโมดูลแชร์เปลี่ยน ให้รันเฉพาะแอปที่พึ่งพามัน และรักษากฎการเป็นเจ้าของและรีวิวให้ชัดเจน
มาตรฐานเครื่องมือและเทมเพลต CI ข้ามรีโพ จะช่วยให้แต่ละรีโพไม่ต้องคิดซ้ำเรื่องขั้นตอน แคชหรือมิร์เรอร์ภายในช่วยลดเวลาดาวน์โหลด dependency และเพิ่มการตรวจสอบอินทิเกรชันที่ validate สัญญาหลักระหว่างการปล่อยรุ่น


