ความเป็นเอกภาพไม่ใช่เป้าหมายที่คุณจะไปถึง แต่มันคือค่าสมาชิกที่คุณต้องจ่าย องค์กรวิศวกรรมทุกแห่งจะค้นพบเรื่องนี้ในที่สุด โดยมักจะเป็นช่วงที่ทีมที่สองหรือสามเริ่มทำการ commit ลงใน repository เดียวกัน ไม่ว่าคุณจะรัน React monolith เพียงตัวเดียว หรือกลุ่มของ frontend ที่สามารถ deploy แยกกันได้อย่างอิสระ คุณไม่ได้กำลังปรับแต่งเพื่อต้นทุนที่เป็นศูนย์ คุณเพียงแค่กำลังเลือกใบแจ้งหนี้ที่จะปรากฏขึ้นในทุกไตรมาสเท่านั้น
ภาษีแห่งการประสานงานของ Monoliths
ในสถาปัตยกรรมแบบ monolithic ใบแจ้งหนี้นั้นถูกเขียนขึ้นด้วยชั่วโมงการทำงานของมนุษย์ ทีมต่างๆ ต้องใช้เวลาทั้งวันไปกับการปรับจูนโค้ด สไตล์ และกำหนดการ release ให้ตรงกัน นักพัฒนาที่ต้องการแก้ไขระบบ checkout เพียงเล็กน้อยอาจต้องอัปเดต shared dependency ที่ทีมอื่นๆ อีกครึ่งโหลใช้งานอยู่ จากนั้นต้องรอให้ regression suite ทำงานจนเสร็จ ต้นทุนนี้จะสะสมเพิ่มขึ้นอย่างเงียบๆ มันไม่เคยปรากฏเป็นรายการในใบแจ้งหนี้ของ cloud แต่มันซ่อนอยู่ในความเร็วในการทำงานที่ลดลง ในการที่วิศวกรต้องสลับบริบทการทำงาน (context-switching) ไปมาระหว่าง Slack thread เกี่ยวกับสไตล์ของโค้ด และในแรงเสียดทานที่เชื่องช้าของสถาปัตยกรรม CSS ที่ไม่มีใครเป็นเจ้าของแต่ทุกคนต้องแตะต้อง
เมื่อทีมของคุณเติบโตขึ้น ภาษีนี้ก็จะเติบโตตามไปด้วย คอขวดในการทำ code review จะเปลี่ยนจากประเด็นทางเทคนิคไปเป็นประเด็นทางสังคม Repository เดียวที่มีผู้ร่วมพัฒนาสองร้อยคนไม่ได้ขยายตัวแบบเส้นตรง แต่มันขยายตัวแบบคอมบิเนทอเรียล (combinatorially) คิวการ merge จะติดขัด Release trains จะลากยาวเป็นวันๆ Design system จะกลายเป็นองค์กรทางการเมืองที่ต้องมีสภาบริหารมาคอยอนุมัติปุ่มรูปแบบใหม่ Monolith ไม่ได้ต่อต้านการเปลี่ยนแปลงเพราะความประสงค์ร้าย แต่มันต่อต้านเพราะทุกพื้นผิวถูกใช้งานร่วมกัน และทุกการเปลี่ยนแปลงล้วนต้องการฉันทามติ
การขีดเส้นแบ่ง
Microfrontends ย้ายต้นทุนการประสานงานไปไว้ในขอบเขตที่เฉพาะเจาะจง แทนที่จะต้องประชุมรายสัปดาห์เรื่องการจัดการ shared state คุณก็แค่ขีดเส้นแบ่ง ทีม A เป็นเจ้าของ product catalog ทีม B เป็นเจ้าของ cart พวกเขาตกลงกันด้วยสัญญา (contract) ซึ่งมักจะเป็นขอบเขตการ routing หรือ event schema ที่แคบๆ จากนั้นพวกเขาก็หยุดคุยกัน นี่คือการแลกเปลี่ยนที่สำคัญ: ความเป็นอิสระเพื่อแลกกับระเบียบวินัยในอีกรูปแบบหนึ่ง
ทฤษฎีนั้นดูสะอาดตา หากทีม Shipping ทำการ refactor เลเยอร์การ routing ทีม Billing ก็ไม่ควรจะได้รับผลกระทบ หากอินเทอร์เฟซการค้นหาจำเป็นต้อง deploy วันละห้าครั้ง มันก็ไม่ควรต้องรอให้หน้าการตั้งค่าบัญชีทำ end-to-end tests ให้เสร็จสิ้น ขอบเขตจะเปลี่ยนแรงเสียดทานในองค์กรให้กลายเป็น technical interfaces แต่การขีดเส้นนั้นไม่มีคำว่าฟรี
ใบแจ้งหนี้ด้านโครงสร้างพื้นฐาน
Microfrontends สร้างต้นทุนด้านแพลตฟอร์ม คุณต้องมี shell application ที่สามารถประกอบชิ้นส่วนต่างๆ เข้าด้วยกันในขณะ runtime คุณต้องมี deployment pipeline ที่เข้าใจวิธีรวบรวม artifacts จาก build jobs หลายตัวให้กลายเป็นหน้าเว็บที่สอดคล้องกันเพียงหน้าเดียว หากคุณใช้ Webpack Module Federation ตอนนี้คุณกำลังจัดการเวอร์ชันของ shared dependency ข้าม bundle ที่ถูก build แยกกัน หากคุณใช้ iframes คุณกำลังดีบั๊กเรื่อง cross-origin messaging และต่อสู้กับ layout shifts หากคุณใช้ web components คุณกำลังจัดการเวอร์ชันของ custom elements ใน distributed graph ที่การอัปเกรดของทีมหนึ่งอาจไปบดบังอีกทีมหนึ่งได้
ต้นทุนเหล่านี้เป็นสิ่งที่จับต้องได้และเกิดขึ้นซ้ำๆ คุณต้องจ่ายให้กับ build orchestration ที่สามารถ release frontend หกตัวได้โดยไม่ทำให้ตัวที่เจ็ดพัง คุณต้องจ่ายให้กับ observability ที่สามารถติดตามการกระทำของผู้ใช้ผ่าน JavaScript bundles สามตัวที่แยกจากกันและเป็นของสามทีมที่ต่างกัน คุณต้องจ่ายให้กับ performance governance เพราะหากทั้งหกทีมต่าง bundle สำเนาของ utility libraries ของตัวเอง หน้าเว็บของคุณจะกลายเป็นสิ่งที่หนักอึ้งและเชื่องช้า เว้นแต่จะมีใครสักคนสร้างและดูแลกลยุทธ์การลดความซ้ำซ้อน (deduplication strategy) เมื่อถึงจุดนั้น คุณได้สร้างส่วนหนึ่งของ monolith ที่คุณพยายามจะหนีออกมาขึ้นมาใหม่ เพียงแต่ตอนนี้มันต้องการ platform team มาคอยดูแล
เมื่อต้นทุนเปลี่ยนทิศทาง
ลองพิจารณาบริษัท SaaS ขนาดกลางที่มีทีม frontend สี่ทีมใช้งาน Next.js application ร่วมกันเพียงแอปเดียว การ deploy เกิดขึ้นวันละสองครั้งหลังจากรัน CI นานสามชั่วโมง เมื่อทีม shipping ต้องการ refactor ส่วนการนำทาง (navigation) พวกเขาต้องส่งคำขอความคิดเห็น อัปเดต import paths ทั่วทั้ง tree และรออีกสองสัปดาห์เพื่อให้ทีม billing ปรับปรุง integration tests ของตน ต้นทุนที่เกิดขึ้นคือการประสานงาน ซึ่งเป็นเรื่องที่เรียบง่ายและชัดเจน
พวกเขาแยกออกเป็น microfrontends ตอนนี้แต่ละทีมเป็นเจ้าของส่วนงานในแนวตั้ง (vertical) และ push ไปยัง production ตามกำหนดการของตนเอง เดือนแรกจะรู้สึกเหมือนได้รับอิสรภาพ จากนั้นบั๊กก็ปรากฏขึ้น Global header ไม่แสดงผลใน Safari เพราะทีม shipping ได้อัปเกรด CSS-in-JS library ที่ขัดแย้งกับ base styles ที่ถูกฉีดเข้ามาโดยทีม search การดีบั๊กต้องใช้ engineer ที่อยู่ในช่วง on-call ถึงสามคน ต้องมี war room ร่วมกัน และต้องทำการ rollback สองบริการอย่างเจ็บปวดเพราะ shell app ทำการ cache module manifests ไว้ ต้นทุนไม่ได้หายไปไหน แต่มันแค่ย้ายที่เท่านั้น
คณิตศาสตร์ของการขยายตัว
ไม่มีโมเดลไหนที่ไม่มีค่าใช้จ่าย สตาร์ทอัพที่มีพนักงาน 15 คนไม่จำเป็นต้องมีทีมแพลตฟอร์ม (platform team) ภาระงาน (overhead) ของ module federation, independent deployment pipelines และ distributed contract testing จะกัดกินความเร็ว (velocity) ทั้งหมดของพวกเขา พวกเขาควรจ่ายด้วยการประสานงาน (coordination) เพราะการประสานงานนั้นมีราคาถูก พวกเขาสามารถตกลงเรื่องรูปแบบการจัดการสถานะ (state management pattern) ได้ในการสนทนาเพียงสิบนาที และส่งมอบงาน (ship) ได้ภายในบ่ายวันเดียวกัน
ในทางตรงกันข้าม องค์กรขนาดใหญ่ที่มีพนักงาน 500 คน และมีหน่วยธุรกิจ (business units) นับสิบแห่งที่ดำเนินงานด้วยรอบไตรมาสที่แตกต่างกัน กำลังเผชิญกับปัญหาที่ตรงกันข้าม "ภาษีการประสานงาน" (coordination tax) ได้เพิ่มขึ้นแบบทวีคูณ การปล่อยซอฟต์แวร์ (release trains) ต้องใช้เวลาหลายสัปดาห์ จำนวนพนักงานด้าน platform engineering เป็นสิ่งที่ต้องตั้งงบประมาณไว้อยู่แล้ว ดังนั้นการเพิ่มโครงสร้างพื้นฐาน microfrontend จึงเป็นเพียงต้นทุนส่วนเพิ่ม (marginal cost) ไม่ใช่รายการงบประมาณใหม่ สำหรับพวกเขา การแลกการประชุมเพื่อปรับความเข้าใจให้ตรงกัน (alignment meetings) กับแผนผังการติดตั้ง (deployment graphs) ถือเป็นตรรกะทางคณิตศาสตร์ที่สมเหตุสมผล
คำถามที่แท้จริงคือ "บิล" ใบไหนที่ขยายตัว (scale) ได้ดีกว่าสำหรับทีมของคุณ Monoliths เก็บภาษีคุณที่ขีดจำกัดของการประสานงานระหว่างมนุษย์ ส่วน Microfrontends เก็บภาษีคุณที่รากฐานของ platform engineering
การเลือกสกุลเงินของคุณ
หากคุณเลือก microfrontends จงระบุให้ชัดเจนว่าคุณกำลังซื้ออะไร คุณกำลังซื้อความเป็นอิสระของทีม (team autonomy) และความสามารถในการติดตั้งใช้งานได้อย่างอิสระ (independent deployability) และจงเตรียมงบประมาณสำหรับสิ่งต่อไปนี้:
- runtime shell ที่จัดการเรื่องการประกอบ (composition), การกำหนดเส้นทาง (routing) และ error boundaries ระหว่าง fragments
- นโยบายการใช้ dependency ร่วมกันที่มุ่งเน้นไปที่กลยุทธ์การลดความซ้ำซ้อน (deduplication strategy) ไม่ใช่ตรรกะการทำงานร่วมกัน (shared implementation logic)
- การทดสอบสัญญา (contract testing) ข้ามทีมสำหรับทุกจุดที่มีการเชื่อมต่อ (integration surface)
- ระบบการสังเกตการณ์แบบรวมศูนย์ (unified observability) ที่สามารถเชื่อมโยงการคลิกของผู้ใช้ผ่าน bundle ต่างๆ ที่กระจายตัวอยู่ได้
- โมเดลการกำกับดูแลประสิทธิภาพ (performance governance model) เพราะไม่มีทีมใดทีมหนึ่งเป็นเจ้าของ payload สุดท้ายที่เบราว์เซอร์ดาวน์โหลด
หากคุณเลือก monolith จงซื่อสัตย์กับใบแจ้งหนี้ คุณกำลังซื้อความเรียบง่ายเพื่อแลกกับการทำงานที่สอดประสานกัน (synchronization) เตรียมใจที่จะจ่ายสำหรับ:
- การเป็นเจ้าของโค้ดร่วมกัน (shared code ownership) และพิธีกรรมการกำกับดูแล (governance rituals) ที่จำเป็นเพื่อให้โค้ดมีความสอดคล้องกัน
- รอบการปล่อยซอฟต์แวร์ (release cadence) ที่ถูกกำหนดโดยการทดสอบการเชื่อมต่อ (integration test) ที่ช้าที่สุดใน pipeline
- ผลกระทบที่แผ่ขยายเป็นวงกว้าง (wide blast radius) เมื่อมีการอัปเกรดไลบรารี
- ความจริงที่ค่อยๆ คืบคลานเข้ามาว่า วิศวกรที่ทำงานเร็วที่สุดของคุณจะต้องเคลื่อนที่ด้วยความเร็วของวิศวกรที่ระมัดระวังที่สุดของคุณ
บทสรุปที่แท้จริง
ไม่มีสถาปัตยกรรมใดที่ไม่มีราคา มีเพียงการเลือกสกุลเงินเท่านั้น องค์กรที่ชาญฉลาดจะหยุดมองหาทางเลือกที่ฟรี และเริ่มตรวจสอบว่าต้นทุนแบบไหนที่พวกเขาสามารถแบกรับได้จริง คุณต้องตัดสินใจว่าคุณต้องการจ่ายด้วยการประสานงานระหว่างมนุษย์ หรือด้วยภาระงานด้านแพลตฟอร์ม (platform overhead) ไม่ว่าจะเลือกทางไหน ความเป็นเอกภาพ (uniformity) ก็ยังคงเป็นเหมือนการสมัครสมาชิก (subscription) คำถามเดียวคือ ใครจะเป็นคนเซ็นเช็คจ่าย
