เจาะลึก Module Federation สำหรับ Micro-Frontends เรียนรู้วิธีแชร์โค้ดและไลบรารี ณ รันไทม์ (runtime) ลดขนาด bundle และเปิดใช้งานการ deploy ที่เป็นอิสระต่อกัน
Module Federation: คู่มือฉบับสมบูรณ์สำหรับการแชร์โมดูลแบบ Runtime ในสถาปัตยกรรม Micro-Frontends
ในโลกของการพัฒนาเว็บที่เปลี่ยนแปลงตลอดเวลา เราได้เห็นการเปลี่ยนแปลงทางสถาปัตยกรรมครั้งสำคัญ เราเดินทางจากสถาปัตยกรรมแบบ Monolithic ไปสู่ Microservices ฝั่ง Backend เพื่อแสวงหาความสามารถในการขยายระบบ (scalability) และความเป็นอิสระของทีม (team autonomy) และตอนนี้ การปฏิวัติเดียวกันนั้นกำลังเปลี่ยนแปลงฝั่ง Frontend ยุคของ Micro-Frontends ได้มาถึงแล้ว และหัวใจสำคัญของมันคือเทคโนโลยีอันทรงพลังที่ทำให้ทุกอย่างเป็นไปได้จริง นั่นคือ Module Federation
ความท้าทายหลักของ Micro-Frontends นั้นพูดง่ายแต่ทำยากมาโดยตลอด นั่นคือ: เราจะสร้างประสบการณ์ผู้ใช้ที่เป็นหนึ่งเดียวกันจากแอปพลิเคชันหลายตัวที่ deploy อย่างอิสระต่อกันได้อย่างไร โดยไม่ทำให้ระบบช้า เทอะทะ และจัดการได้ยาก? เราจะแชร์โค้ดที่ใช้ร่วมกัน เช่น ไลบรารีคอมโพเนนต์ หรือเฟรมเวิร์กอย่าง React ได้อย่างไร โดยไม่สร้างฝันร้ายเรื่องเวอร์ชันหรือบังคับให้ต้อง release พร้อมกัน?
นี่คือปัญหาที่ Module Federation เข้ามาแก้ไขอย่างงดงาม มันถูกนำเสนอใน Webpack 5 และไม่ใช่แค่ฟีเจอร์ใหม่ แต่เป็นการเปลี่ยนกระบวนทัศน์ (paradigm shift) ในวิธีที่เราคิดเกี่ยวกับการสร้างและ deploy เว็บแอปพลิเคชัน คู่มือฉบับสมบูรณ์นี้จะสำรวจว่า Module Federation คืออะไร ทำไมต้องใช้ และใช้อย่างไร โดยเน้นไปที่ความสามารถที่พลิกโฉมวงการที่สุดของมัน: การแชร์โมดูล ณ รันไทม์ (runtime module sharing)
ทบทวนสั้นๆ: Micro-Frontends คืออะไร?
ก่อนที่จะเจาะลึกกลไกของ Module Federation เรามาทำความเข้าใจให้ตรงกันก่อนว่า Micro-Frontends หมายถึงอะไร ลองนึกภาพเว็บไซต์ E-commerce ขนาดใหญ่ ในโลกของ Monolithic ทั้งหมดของ Frontend ไม่ว่าจะเป็นการค้นหาสินค้า รายละเอียดสินค้า ตะกร้าสินค้า และการชำระเงิน จะเป็นแอปพลิเคชันขนาดใหญ่เพียงตัวเดียว การเปลี่ยนแปลงปุ่มชำระเงินอาจต้องทดสอบและ deploy แอปพลิเคชันใหม่ทั้งหมด
สถาปัตยกรรม Micro-Frontends จะแบ่ง Monolith นี้ออกตามขอบเขตของธุรกิจ (business domains) คุณอาจจะมี:
- ทีมค้นหา (Search Team) ที่เป็นเจ้าของแถบค้นหาและหน้าผลการค้นหา
- ทีมผลิตภัณฑ์ (Product Team) ที่เป็นเจ้าของหน้ารายละเอียดสินค้าและสินค้าแนะนำ
- ทีมชำระเงิน (Checkout Team) ที่เป็นเจ้าของตะกร้าสินค้าและกระบวนการชำระเงิน
แต่ละทีมสามารถสร้าง ทดสอบ และ deploy ส่วนของแอปพลิเคชันของตนได้อย่างอิสระ สิ่งนี้นำไปสู่ประโยชน์หลักหลายประการ:
- ทีมที่เป็นอิสระ (Autonomous Teams): ทีมสามารถทำงานและ release ตามตารางเวลาของตนเองได้ ซึ่งช่วยเร่งความเร็วในการพัฒนา
- การ Deploy ที่เป็นอิสระ (Independent Deployments): บั๊กในระบบแนะนำสินค้าจะไม่ขัดขวางการอัปเดตที่สำคัญของขั้นตอนการชำระเงิน
- ความยืดหยุ่นทางเทคโนโลยี (Technology Flexibility): ทีมค้นหาอาจใช้ Vue.js ในขณะที่ทีมผลิตภัณฑ์ใช้ React ทำให้ทีมสามารถเลือกเครื่องมือที่ดีที่สุดสำหรับโดเมนของตนได้ (แม้ว่าสิ่งนี้จะต้องมีการจัดการอย่างระมัดระวัง)
อย่างไรก็ตาม แนวทางนี้ก็นำมาซึ่งความท้าทายในตัวเอง โดยเฉพาะอย่างยิ่งเรื่องการแชร์และความสอดคล้องกัน ซึ่งนำเราไปสู่วิธีการแบบเก่าๆ
วิธีการแชร์โค้ดแบบเก่า (และทำไมถึงไม่ดีพอ)
ในอดีต ทีมต่างๆ ได้ลองใช้วิธีการหลายอย่างเพื่อแชร์โค้ดระหว่างแอปพลิเคชัน Frontend ที่แตกต่างกัน ซึ่งแต่ละวิธีก็มีข้อเสียที่สำคัญในบริบทของ Micro-Frontends
NPM Packages
แนวทางที่พบบ่อยที่สุดคือการเผยแพร่คอมโพเนนต์หรือ utility ที่ใช้ร่วมกันเป็น NPM package ที่มีเวอร์ชัน ตัวอย่างคลาสสิกคือไลบรารีคอมโพเนนต์ที่ใช้ร่วมกัน
- ปัญหาคือ: นี่คือการพึ่งพา ณ เวลา build (build-time dependency) หากทีม A อัปเดตคอมโพเนนต์ `Button` ที่ใช้ร่วมกันใน `my-ui-library` จากเวอร์ชัน 1.1 เป็น 1.2 ทีม B และทีม C จะไม่ได้รับการอัปเดตนั้นจนกว่าพวกเขาจะอัปเดต `package.json` ด้วยตนเอง รัน `npm install` และ deploy Micro-Frontend ของตนใหม่ทั้งหมด สิ่งนี้สร้างการผูกมัดที่แน่นหนาและทำลายจุดประสงค์ของการ deploy ที่เป็นอิสระ นอกจากนี้ยังนำไปสู่การโหลดคอมโพเนนต์เดียวกันหลายเวอร์ชันในเบราว์เซอร์ ทำให้ bundle สุดท้ายมีขนาดใหญ่ขึ้นโดยไม่จำเป็น
Monorepos กับ Shared Workspaces
Monorepos (โดยใช้เครื่องมืออย่าง Lerna หรือ Yarn/NPM workspaces) จะเก็บ Micro-Frontends ทั้งหมดไว้ใน repository เดียว ซึ่งช่วยให้การจัดการ package ที่ใช้ร่วมกันง่ายขึ้น
- ปัญหาคือ: แม้ว่า Monorepos จะช่วยในเรื่องประสบการณ์ของนักพัฒนา แต่ก็ไม่ได้แก้ปัญหาหลักที่ runtime คุณยังคงต้องพึ่งพา dependency ณ เวลา build การเปลี่ยนแปลงไลบรารีที่ใช้ร่วมกันยังคงต้องการให้แอปพลิเคชันทั้งหมดที่ใช้งานต้องถูก build และ deploy ใหม่เพื่อให้การเปลี่ยนแปลงมีผล