คู่มือฉบับสมบูรณ์เกี่ยวกับ Routing สำหรับ Micro-Frontend สำรวจกลยุทธ์การนำทางข้ามแอปพลิเคชัน ประโยชน์ และแนวทางปฏิบัติที่ดีที่สุดในการสร้างเว็บแอปพลิเคชันที่ขยายและดูแลรักษาง่าย
เราเตอร์ Micro-Frontend สำหรับ Frontend: การนำทางข้ามแอปพลิเคชัน
ในการพัฒนาเว็บสมัยใหม่ สถาปัตยกรรม micro-frontend ได้รับความนิยมอย่างมากในฐานะวิธีการสร้างแอปพลิเคชันขนาดใหญ่และซับซ้อน โดยเกี่ยวข้องกับการแบ่งย่อย frontend แบบ monolithic ออกเป็นหน่วยย่อยๆ ที่สามารถ deploy ได้อย่างอิสระ (micro-frontends) หนึ่งในความท้าทายหลักของสถาปัตยกรรมนี้คือการจัดการการนำทางข้ามแอปพลิเคชัน (cross-application navigation) เพื่อให้ผู้ใช้สามารถเคลื่อนย้ายระหว่าง micro-frontends ที่เป็นอิสระเหล่านี้ได้อย่างราบรื่น บทความนี้จะให้คำแนะนำที่ครอบคลุมเกี่ยวกับ routing สำหรับ micro-frontend และการนำทางข้ามแอปพลิเคชัน
Micro-Frontends คืออะไร?
Micro-frontends เป็นรูปแบบสถาปัตยกรรมที่แอปพลิเคชัน frontend ที่สามารถส่งมอบได้อย่างอิสระถูกนำมารวมกันเป็นประสบการณ์ผู้ใช้เดียวที่สอดคล้องกัน ซึ่งคล้ายคลึงกับ microservices ในฝั่ง backend โดยทั่วไปแล้วแต่ละ micro-frontend จะเป็นของทีมที่แยกจากกัน ทำให้มีความเป็นอิสระมากขึ้น รอบการพัฒนาที่เร็วขึ้น และการบำรุงรักษาที่ง่ายขึ้น ประโยชน์ของ micro-frontends ได้แก่:
- Independent Deployment: ทีมสามารถ deploy micro-frontends ของตนได้โดยไม่ส่งผลกระทบต่อส่วนอื่นๆ ของแอปพลิเคชัน
- Technology Diversity: micro-frontends ที่แตกต่างกันสามารถสร้างขึ้นโดยใช้เทคโนโลยีที่แตกต่างกัน ทำให้ทีมสามารถเลือกเครื่องมือที่ดีที่สุดสำหรับงานได้ ตัวอย่างเช่น ทีมหนึ่งอาจใช้ React ในขณะที่อีกทีมใช้ Vue.js หรือ Angular
- Scalability: แอปพลิเคชันสามารถขยายขนาดได้ง่ายขึ้น เนื่องจากแต่ละ micro-frontend สามารถปรับขนาดได้อย่างอิสระ
- Improved Maintainability: โค้ดเบสที่เล็กลงจะเข้าใจและบำรุงรักษาได้ง่ายกว่า
- Team Autonomy: ทีมต่างๆ สามารถควบคุมโค้ดและกระบวนการพัฒนาของตนเองได้มากขึ้น
ความจำเป็นของ Micro-Frontend Router
หากไม่มีกลยุทธ์การกำหนดเส้นทาง (routing) ที่ชัดเจน ผู้ใช้จะได้รับประสบการณ์ที่ไม่ต่อเนื่องและน่าหงุดหงิดเมื่อนำทางระหว่าง micro-frontends ซึ่ง micro-frontend router จะเข้ามาช่วยแก้ไขปัญหานี้โดยการจัดหากลไกแบบรวมศูนย์สำหรับจัดการการนำทางทั่วทั้งแอปพลิเคชัน ซึ่งรวมถึงการจัดการ:
- URL Management: ทำให้แน่ใจว่า URL สะท้อนตำแหน่งปัจจุบันของผู้ใช้ภายในแอปพลิเคชันอย่างถูกต้อง
- State Management: การแบ่งปัน state ระหว่าง micro-frontends เมื่อจำเป็น
- Lazy Loading: การโหลด micro-frontends เฉพาะเมื่อจำเป็นเพื่อปรับปรุงประสิทธิภาพ
- Authentication and Authorization: การจัดการการพิสูจน์ตัวตนและการอนุญาตผู้ใช้ข้าม micro-frontends ที่แตกต่างกัน
กลยุทธ์การนำทางข้ามแอปพลิเคชัน
มีหลายแนวทางในการนำการนำทางข้ามแอปพลิเคชันไปใช้ในสถาปัตยกรรม micro-frontend แต่ละแนวทางมีข้อดีและข้อเสียที่แตกต่างกัน และตัวเลือกที่ดีที่สุดขึ้นอยู่กับความต้องการเฉพาะของแอปพลิเคชันของคุณ
1. การใช้ Centralized Router (Single-Spa)
Single-Spa เป็นเฟรมเวิร์กยอดนิยมสำหรับสร้าง micro-frontends โดยใช้ router แบบรวมศูนย์เพื่อจัดการการนำทางระหว่างแอปพลิเคชันต่างๆ แอปพลิเคชันหลักทำหน้าที่เป็นผู้ควบคุม (orchestrator) และรับผิดชอบในการเรนเดอร์และถอนการติดตั้ง (unmount) micro-frontends ตาม URL ปัจจุบัน
วิธีการทำงาน:
- ผู้ใช้นำทางไปยัง URL ที่ต้องการ
- single-spa router จะดักจับการเปลี่ยนแปลงของ URL
- router จะพิจารณาว่า micro-frontend ใดควรทำงานอยู่โดยอิงตาม URL
- router จะเปิดใช้งาน micro-frontend ที่เกี่ยวข้องและถอนการติดตั้ง micro-frontends อื่นๆ ที่ทำงานอยู่
ตัวอย่าง (Single-Spa):
สมมติว่าคุณมี micro-frontends สามตัว: home, products และ cart single-spa router จะถูกกำหนดค่าดังนี้:
import { registerApplication, start } from 'single-spa';
registerApplication(
'home',
() => import('./home/home.app.js'),
location => location.pathname === '/'
);
registerApplication(
'products',
() => import('./products/products.app.js'),
location => location.pathname.startsWith('/products')
);
registerApplication(
'cart',
() => import('./cart/cart.app.js'),
location => location.pathname.startsWith('/cart')
);
start();
ในตัวอย่างนี้ แต่ละ micro-frontend จะถูกลงทะเบียนกับ single-spa และมีฟังก์ชันที่กำหนดว่าเมื่อใดที่ micro-frontend ควรจะทำงานโดยพิจารณาจาก URL เมื่อผู้ใช้นำทางไปยัง /products ไมโครฟรอนต์เอนด์ products จะถูกเปิดใช้งาน
ข้อดี:
- ควบคุม routing แบบรวมศูนย์
- การจัดการ state ที่ง่ายขึ้น (สามารถจัดการโดย single-spa orchestrator)
- ง่ายต่อการผสานรวมกับแอปพลิเคชันที่มีอยู่
ข้อเสีย:
- เป็นจุดบกพร่องเดียว (Single point of failure) หาก orchestrator ล่ม แอปพลิเคชันทั้งหมดจะได้รับผลกระทบ
- อาจกลายเป็นคอขวดด้านประสิทธิภาพหากไม่ได้นำไปใช้อย่างมีประสิทธิภาพ
2. Module Federation (Webpack 5)
Module Federation ของ Webpack 5 ช่วยให้คุณสามารถแชร์โค้ดระหว่าง Webpack build ที่แตกต่างกันได้ในขณะรันไทม์ ซึ่งหมายความว่าคุณสามารถเปิดเผย (expose) components, modules หรือแม้กระทั่งทั้งแอปพลิเคชันจาก build หนึ่ง (host) ไปยังอีก build หนึ่ง (remote) ได้ สิ่งนี้ช่วยอำนวยความสะดวกในการสร้าง micro-frontends โดยแต่ละ micro-frontend เป็น Webpack build ที่แยกจากกัน
วิธีการทำงาน:
- แต่ละ micro-frontend ถูกสร้างเป็นโปรเจกต์ Webpack แยกต่างหาก
- micro-frontend หนึ่งจะถูกกำหนดให้เป็น host application
- host application จะกำหนดว่าต้องการใช้โมดูลใดจาก remote micro-frontends
- remote micro-frontends จะกำหนดว่าต้องการเปิดเผยโมดูลใดให้กับ host application
- ในขณะรันไทม์ host application จะโหลดโมดูลที่เปิดเผยจาก remote micro-frontends ตามความจำเป็น
ตัวอย่าง (Module Federation):
สมมติว่ามีแอป host และแอป remote
host/webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
remote: 'remote@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};
remote/webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'remote',
exposes: {
'./Button': './src/Button',
},
shared: ['react', 'react-dom'],
}),
],
};
ในตัวอย่างนี้ แอปพลิเคชัน host จะใช้คอมโพเนนต์ Button จากแอปพลิเคชัน remote ตัวเลือก shared ช่วยให้แน่ใจว่าทั้งสองแอปพลิเคชันใช้ react และ react-dom เวอร์ชันเดียวกัน
ข้อดี:
- สถาปัตยกรรมแบบกระจายศูนย์ (Decentralized) แต่ละ micro-frontend เป็นอิสระและสามารถพัฒนาและ deploy แยกกันได้
- การแชร์โค้ด Module Federation ช่วยให้คุณสามารถแชร์โค้ดระหว่างแอปพลิเคชันต่างๆ ในขณะรันไทม์ได้
- Lazy loading โมดูลจะถูกโหลดเมื่อจำเป็นเท่านั้น ซึ่งช่วยปรับปรุงประสิทธิภาพ
ข้อเสีย:
- ตั้งค่าและกำหนดค่าซับซ้อนกว่า single-spa
- ต้องมีการจัดการ dependencies ที่ใช้ร่วมกันอย่างระมัดระวังเพื่อหลีกเลี่ยงปัญหาความขัดแย้งของเวอร์ชัน
3. Web Components
Web Components เป็นชุดของมาตรฐานเว็บที่ช่วยให้คุณสร้างองค์ประกอบ HTML แบบกำหนดเองที่สามารถนำกลับมาใช้ใหม่ได้ คอมโพเนนต์เหล่านี้สามารถใช้ได้ในเว็บแอปพลิเคชันใดก็ได้ โดยไม่คำนึงถึงเฟรมเวิร์กที่ใช้ สิ่งนี้ทำให้เหมาะสมอย่างยิ่งสำหรับสถาปัตยกรรม micro-frontend เนื่องจากเป็นวิธีการสร้างและแชร์ UI components ที่ไม่ขึ้นกับเทคโนโลยี
วิธีการทำงาน:
- แต่ละ micro-frontend เปิดเผย UI ของตนเป็นชุดของ Web Components
- แอปพลิเคชันหลัก (หรือ micro-frontend อื่น) จะใช้ Web Components เหล่านี้โดยการนำเข้าและใช้งานใน HTML ของตน
- Web Components จะจัดการการเรนเดอร์และตรรกะของตัวเอง
ตัวอย่าง (Web Components):
micro-frontend-a.js:
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
Hello from Micro-Frontend A!
`;
}
}
customElements.define('micro-frontend-a', MyComponent);
index.html (แอปพลิเคชันหลัก):
Main Application
Main Application
ในตัวอย่างนี้ ไฟล์ micro-frontend-a.js จะกำหนด Web Component ที่ชื่อว่า micro-frontend-a ไฟล์ index.html จะนำเข้าไฟล์นี้และใช้ Web Component ใน HTML ของมัน เบราว์เซอร์จะเรนเดอร์ Web Component และแสดงข้อความ "Hello from Micro-Frontend A!"
ข้อดี:
- ไม่ขึ้นกับเทคโนโลยี (Technology-agnostic) Web Components สามารถใช้ได้กับทุกเฟรมเวิร์กหรือไม่ใช้เฟรมเวิร์กเลยก็ได้
- การนำกลับมาใช้ใหม่ (Reusability) Web Components สามารถนำกลับมาใช้ซ้ำในแอปพลิเคชันต่างๆ ได้อย่างง่ายดาย
- การห่อหุ้ม (Encapsulation) Web Components จะห่อหุ้มสไตล์และตรรกะของตัวเองไว้ ป้องกันความขัดแย้งกับส่วนอื่นๆ ของแอปพลิเคชัน
ข้อเสีย:
- อาจมีขั้นตอนการเขียนโค้ดที่ละเอียดกว่าแนวทางอื่น
- อาจต้องใช้ polyfills เพื่อรองรับเบราว์เซอร์รุ่นเก่า
4. Iframes
Iframes (Inline Frames) เป็นตัวเลือกที่เก่าแก่แต่ยังคงใช้งานได้สำหรับการแยก micro-frontends แต่ละ micro-frontend จะทำงานภายใน iframe ของตัวเอง ทำให้มีการแยกส่วนในระดับสูง การสื่อสารระหว่าง iframes สามารถทำได้โดยใช้ postMessage API
วิธีการทำงาน:
- แต่ละ micro-frontend ถูก deploy เป็นเว็บแอปพลิเคชันแยกต่างหาก
- แอปพลิเคชันหลักจะรวมแต่ละ micro-frontend ไว้ใน iframe
- การสื่อสารระหว่างแอปพลิเคชันหลักและ micro-frontends ทำได้โดยใช้
postMessageAPI
ตัวอย่าง (Iframes):
index.html (แอปพลิเคชันหลัก):
Main Application
Main Application
ในตัวอย่างนี้ ไฟล์ index.html จะมี iframes สองอัน ซึ่งแต่ละอันชี้ไปยัง micro-frontend ที่แตกต่างกัน
ข้อดี:
- การแยกส่วนในระดับสูง micro-frontends จะถูกแยกออกจากกันอย่างสมบูรณ์ ป้องกันความขัดแย้ง
- ง่ายต่อการนำไปใช้ Iframes เป็นเทคโนโลยีที่เรียบง่ายและเป็นที่เข้าใจกันดี
ข้อเสีย:
- การสื่อสารระหว่าง iframes อาจทำได้ยาก
- อาจมีปัญหาด้านประสิทธิภาพเนื่องจาก overhead ของการมี iframes หลายอัน
- ประสบการณ์ผู้ใช้ที่ไม่ดีเนื่องจากขาดการบูรณาการที่ราบรื่น
การจัดการ State ข้าม Micro-Frontends
การจัดการ state ข้าม micro-frontends เป็นส่วนสำคัญของการนำทางข้ามแอปพลิเคชัน มีหลายกลยุทธ์ที่สามารถนำมาใช้ได้:
- URL-Based State: การเข้ารหัส state ไว้ใน URL แนวทางนี้ทำให้ state ของแอปพลิเคชันสามารถแชร์ผ่าน URL และบุ๊กมาร์กได้ง่าย
- Centralized State Management (Redux, Vuex): การใช้ไลบรารีการจัดการ state ส่วนกลางเพื่อแชร์ state ระหว่าง micro-frontends ซึ่งมีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันที่ซับซ้อนและมี state ที่ต้องแชร์จำนวนมาก
- Custom Events: การใช้ custom events เพื่อสื่อสารการเปลี่ยนแปลง state ระหว่าง micro-frontends แนวทางนี้ช่วยให้เกิดการเชื่อมต่อที่หลวม (loose coupling) ระหว่าง micro-frontends
- Browser Storage (LocalStorage, SessionStorage): การจัดเก็บ state ในที่จัดเก็บของเบราว์เซอร์ แนวทางนี้เหมาะสำหรับ state ง่ายๆ ที่ไม่จำเป็นต้องแชร์ข้ามทุก micro-frontends อย่างไรก็ตาม ควรคำนึงถึงข้อควรพิจารณาด้านความปลอดภัยเมื่อจัดเก็บข้อมูลที่ละเอียดอ่อน
การพิสูจน์ตัวตนและการให้สิทธิ์ (Authentication and Authorization)
การพิสูจน์ตัวตนและการให้สิทธิ์เป็นส่วนสำคัญของเว็บแอปพลิเคชันใดๆ และจะยิ่งมีความสำคัญมากขึ้นในสถาปัตยกรรม micro-frontend แนวทางทั่วไป ได้แก่:
- Centralized Authentication Service: บริการเฉพาะที่จัดการการพิสูจน์ตัวตนผู้ใช้และออกโทเค็น (เช่น JWT) จากนั้น micro-frontends สามารถตรวจสอบโทเค็นเหล่านี้เพื่อกำหนดสิทธิ์ของผู้ใช้ได้
- Shared Authentication Module: โมดูลที่ใช้ร่วมกันซึ่งรับผิดชอบในการจัดการตรรกะการพิสูจน์ตัวตน โมดูลนี้สามารถใช้ได้โดยทุก micro-frontends
- Edge Authentication: การพิสูจน์ตัวตนจะถูกจัดการที่ขอบของเครือข่าย (เช่น การใช้ reverse proxy หรือ API gateway) แนวทางนี้สามารถลดความซับซ้อนของตรรกะการพิสูจน์ตัวตนใน micro-frontends ได้
แนวทางปฏิบัติที่ดีที่สุดสำหรับ Micro-Frontend Routing
นี่คือแนวทางปฏิบัติที่ดีที่สุดที่ควรคำนึงถึงเมื่อนำ micro-frontend routing ไปใช้:
- ทำให้เรียบง่าย: เลือกกลยุทธ์การกำหนดเส้นทางที่ง่ายที่สุดที่ตรงกับความต้องการของคุณ
- แยกส่วน Micro-Frontends: ลดการพึ่งพาระหว่าง micro-frontends เพื่อส่งเสริมการพัฒนาและ deploy ที่เป็นอิสระ
- ใช้โครงสร้าง URL ที่สอดคล้องกัน: รักษาโครงสร้าง URL ที่สอดคล้องกันในทุก micro-frontends เพื่อปรับปรุงประสบการณ์ผู้ใช้และ SEO
- ใช้ Lazy Loading: โหลด micro-frontends เฉพาะเมื่อจำเป็นเพื่อปรับปรุงประสิทธิภาพ
- ติดตามประสิทธิภาพ: ติดตามประสิทธิภาพของแอปพลิเคชัน micro-frontend ของคุณอย่างสม่ำเสมอเพื่อระบุและแก้ไขปัญหาคอขวด
- สร้างช่องทางการสื่อสารที่ชัดเจน: ตรวจสอบให้แน่ใจว่าทีมที่ทำงานกับ micro-frontends ต่างๆ มีช่องทางการสื่อสารที่ชัดเจนเพื่อประสานงานการพัฒนาและแก้ไขปัญหาการผสานรวม
- ใช้การจัดการข้อผิดพลาดที่แข็งแกร่ง: ใช้การจัดการข้อผิดพลาดที่แข็งแกร่งเพื่อรับมือกับความล้มเหลวในแต่ละ micro-frontend อย่างนุ่มนวลและป้องกันไม่ให้ส่งผลกระทบต่อทั้งแอปพลิเคชัน
- การทดสอบอัตโนมัติ: ใช้การทดสอบอัตโนมัติที่ครอบคลุม รวมถึง unit tests, integration tests และ end-to-end tests เพื่อให้แน่ใจในคุณภาพและความเสถียรของแอปพลิเคชัน micro-frontend ของคุณ
สรุป
Micro-frontend routing เป็นเรื่องที่ซับซ้อนแต่จำเป็นอย่างยิ่งสำหรับการสร้างเว็บแอปพลิเคชันที่สามารถขยายขนาดและบำรุงรักษาได้ โดยการพิจารณากลยุทธ์การกำหนดเส้นทางและแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในบทความนี้อย่างรอบคอบ คุณสามารถสร้างประสบการณ์ที่ราบรื่นและเป็นมิตรกับผู้ใช้ได้ การเลือกแนวทางที่เหมาะสม ไม่ว่าจะเป็น router แบบรวมศูนย์อย่าง Single-Spa, Module Federation, Web Components หรือแม้แต่ Iframes ขึ้นอยู่กับความต้องการและลำดับความสำคัญเฉพาะของคุณ อย่าลืมให้ความสำคัญกับการแยกส่วน (decoupling) โครงสร้าง URL ที่สอดคล้องกัน และการเพิ่มประสิทธิภาพ โดยการนำกลยุทธ์การกำหนดเส้นทางที่ออกแบบมาอย่างดีไปใช้ คุณจะสามารถปลดล็อกศักยภาพสูงสุดของสถาปัตยกรรม micro-frontend และสร้างเว็บแอปพลิเคชันที่ยอดเยี่ยมสำหรับผู้ใช้ทั่วโลกได้