คำแนะนำเกี่ยวกับ Monorepos สำหรับ Front-end Code

เผยแพร่แล้ว: 2022-03-11

Monorepos เป็นหัวข้อยอดนิยมสำหรับการสนทนา เมื่อเร็ว ๆ นี้มีบทความมากมายเกี่ยวกับสาเหตุที่คุณควรและไม่ควรใช้สถาปัตยกรรมประเภทนี้สำหรับโครงการของคุณ แต่ส่วนใหญ่มีอคติไม่ทางใดก็ทางหนึ่ง ชุดนี้เป็นความพยายามที่จะรวบรวมและอธิบายข้อมูลให้ได้มากที่สุดเพื่อทำความเข้าใจว่าควรใช้ monorepos อย่างไรและเมื่อใด

Monorepository เป็นแนวคิดทางสถาปัตยกรรมซึ่งโดยทั่วไปแล้วมีความหมายทั้งหมดในชื่อ แทนที่จะจัดการหลายที่เก็บ คุณเก็บส่วนโค้ดแยกทั้งหมดไว้ในที่เก็บเดียว โปรดจำไว้ว่าคำที่ แยกได้ — หมายความว่า monorepo ไม่มีอะไรที่เหมือนกันกับแอปแบบเสาหิน คุณสามารถเก็บแอพลอจิกหลายประเภทไว้ใน repo เดียว ตัวอย่างเช่น เว็บไซต์และแอป iOS

การเปรียบเทียบ monorepo, single repo และ multi-repo

แนวคิดนี้ค่อนข้างเก่าและปรากฏเมื่อประมาณทศวรรษที่แล้ว Google เป็นหนึ่งในบริษัทแรกๆ ที่นำแนวทางนี้มาใช้ในการจัดการฐานรหัสของตน คุณอาจจะถามว่าถ้ามันมีมานานนับทศวรรษแล้วทำไมมันถึงกลายเป็นประเด็นร้อนแค่ตอนนี้ล่ะ? ส่วนใหญ่ในช่วง 5-6 ปีที่ผ่านมา หลายสิ่งหลายอย่างได้รับการเปลี่ยนแปลงอย่างมาก ES6, ตัวประมวลผลล่วงหน้าของ SCSS, ตัวจัดการงาน, npm ฯลฯ—ในปัจจุบัน เพื่อรักษาแอปที่ใช้ React ขนาดเล็ก คุณต้องจัดการกับตัวรวมโปรเจ็กต์ ชุดทดสอบ สคริปต์ CI/CD การกำหนดค่า Docker และใครจะรู้อะไรอีก และตอนนี้ลองนึกดูว่า แทนที่จะใช้แอปขนาดเล็ก คุณต้องรักษาแพลตฟอร์มขนาดใหญ่ที่ประกอบด้วยส่วนการทำงานจำนวนมาก หากคุณกำลังคิดเกี่ยวกับสถาปัตยกรรม คุณจะต้องทำสองสิ่งหลัก: แยกข้อกังวลและหลีกเลี่ยงการหลอกล่อ

ในการทำให้สิ่งนี้เกิดขึ้น คุณอาจต้องการแยกคุณสมบัติขนาดใหญ่ออกเป็นแพ็คเกจบางตัว แล้วใช้งานผ่านจุดเข้าใช้งานเดียวในแอปหลักของคุณ แต่คุณจะจัดการแพ็คเกจเหล่านั้นอย่างไร แต่ละแพ็คเกจจะต้องมีการกำหนดค่าสภาพแวดล้อมเวิร์กโฟลว์ของตัวเอง ซึ่งหมายความว่าทุกครั้งที่คุณต้องการสร้างแพ็คเกจใหม่ คุณจะต้องกำหนดค่าสภาพแวดล้อมใหม่ คัดลอกไฟล์การกำหนดค่าทั้งหมด และอื่นๆ หรือ ตัวอย่างเช่น หากคุณต้องเปลี่ยนแปลงบางสิ่งในระบบบิลด์ของคุณ คุณจะต้องผ่านแต่ละ repo ทำคอมมิต สร้างคำขอดึง และรอแต่ละบิลด์ ซึ่งจะทำให้คุณช้าลงมาก ในขั้นตอนนี้ เรากำลังพบกับ monorepos

แทนที่จะมีที่เก็บจำนวนมากพร้อมการกำหนดค่าของตัวเอง เราจะมีแหล่งความจริงเพียงแหล่งเดียว—โมโนเรโป: หนึ่งชุดทดสอบชุดทดสอบ ไฟล์การกำหนดค่า Docker หนึ่งไฟล์ และการกำหนดค่าสำหรับ Webpack หนึ่งรายการ และคุณยังคงมีความสามารถในการปรับขนาด โอกาสในการแยกข้อกังวล การแบ่งปันรหัสกับแพ็คเกจทั่วไป และข้อดีอื่นๆ อีกมากมาย ฟังดูดีใช่มั้ย? มันเป็น แต่ก็มีข้อเสียอยู่บ้างเช่นกัน มาดูข้อดีและข้อเสียของการใช้ monorepo ในป่ากัน

ข้อดีของโมโนเรโป:

  • ที่เดียวสำหรับจัดเก็บการกำหนดค่าและการทดสอบทั้งหมด เนื่องจากทุกอย่างอยู่ใน repo เดียว คุณจึงสามารถกำหนดค่า CI/CD และ Bundler ได้เพียงครั้งเดียว จากนั้นใช้การกำหนดค่าใหม่เพื่อสร้างแพ็คเกจทั้งหมดก่อนที่จะเผยแพร่ไปยังรีโมต เช่นเดียวกันสำหรับการทดสอบหน่วย e2e และการรวม—CI ของคุณจะสามารถเริ่มการทดสอบทั้งหมดได้โดยไม่ต้องจัดการกับการกำหนดค่าเพิ่มเติม
  • รีแฟกเตอร์ฟีเจอร์ทั่วโลกได้อย่างง่ายดายด้วย atomic commit แทนที่จะทำการร้องขอการดึงสำหรับแต่ละ repo การหาว่าจะสร้างการเปลี่ยนแปลงของคุณอย่างไร คุณเพียงแค่ทำการร้องขอการดึงแบบอะตอม ซึ่งจะประกอบด้วยการคอมมิตทั้งหมดที่เกี่ยวข้องกับคุณลักษณะที่คุณกำลังทำงานอยู่
  • การเผยแพร่แพ็คเกจแบบง่าย หากคุณวางแผนที่จะใช้คุณลักษณะใหม่ภายในแพ็คเกจที่ขึ้นอยู่กับแพ็คเกจอื่นที่มีรหัสที่ใช้ร่วมกัน คุณสามารถทำได้ด้วยคำสั่งเดียว เป็นฟังก์ชันที่ต้องการการกำหนดค่าเพิ่มเติม ซึ่งจะกล่าวถึงในภายหลังในส่วนการตรวจสอบเครื่องมือของบทความนี้ ปัจจุบันมีเครื่องมือให้เลือกมากมาย เช่น Lerna, Yarn Workspaces และ Bazel
  • การจัดการการพึ่งพาที่ง่ายขึ้น package.json เดียวเท่านั้น ไม่จำเป็นต้องติดตั้งการพึ่งพาใหม่ในแต่ละ repo เมื่อใดก็ตามที่คุณต้องการอัปเดตการพึ่งพาของคุณ
  • นำรหัสกลับมาใช้ใหม่กับแพ็คเกจที่แชร์โดยที่ยังคงแยกออกจากกัน Monorepo ช่วยให้คุณสามารถนำแพ็คเกจของคุณกลับมาใช้ใหม่จากแพ็คเกจอื่น ๆ ในขณะที่แยกจากกัน คุณสามารถใช้การอ้างอิงถึงแพ็คเกจระยะไกลและใช้งานผ่านจุดเข้าใช้งานเดียว หากต้องการใช้เวอร์ชันท้องถิ่น คุณสามารถใช้ symlink ในเครื่องได้ คุณลักษณะนี้สามารถใช้งานได้ผ่านสคริปต์ทุบตีหรือโดยการแนะนำเครื่องมือเพิ่มเติม เช่น Lerna หรือ Yarn

ข้อเสียของโมโนเรโป:

  • ไม่มีวิธีจำกัดการเข้าถึงเฉพาะบางส่วนของแอป ขออภัย คุณไม่สามารถแบ่งปันเฉพาะบางส่วนของ monorepo ของคุณได้ คุณจะต้องให้สิทธิ์การเข้าถึงฐานรหัสทั้งหมด ซึ่งอาจนำไปสู่ปัญหาด้านความปลอดภัยบางอย่าง
  • ประสิทธิภาพ Git แย่เมื่อทำงานกับโครงการขนาดใหญ่ ปัญหานี้เริ่มปรากฏเฉพาะในแอปพลิเคชัน ขนาดใหญ่ ที่มีการคอมมิตมากกว่าหนึ่งล้านครั้ง และนักพัฒนาหลายร้อยรายทำงานพร้อมกันทุกวันบน repo เดียวกัน สิ่งนี้จะกลายเป็นเรื่องยุ่งยากโดยเฉพาะอย่างยิ่งเนื่องจาก Git ใช้กราฟ acyclic แบบกำกับ (DAG) เพื่อแสดงประวัติของโครงการ ด้วยคอมมิตจำนวนมาก คำสั่งใดๆ ก็ตามที่เดินบนกราฟอาจทำงานช้าเมื่อประวัติเข้มข้นขึ้น ประสิทธิภาพก็ช้าลงเช่นกันเนื่องจากจำนวนผู้อ้างอิง (เช่น สาขาหรือแท็ก แก้ไขได้โดยการลบการอ้างอิงที่คุณไม่ต้องการอีกต่อไป) และจำนวนไฟล์ที่ติดตาม (รวมถึงน้ำหนักของไฟล์อ้างอิง แม้ว่าปัญหาไฟล์หนักจะสามารถแก้ไขได้โดยใช้ Git LFS).

    หมายเหตุ: ปัจจุบัน Facebook พยายามแก้ไขปัญหาเกี่ยวกับความสามารถในการปรับขนาด VCS โดยแพตช์ Mercurial และอาจไม่ใช่ปัญหาใหญ่ในเร็วๆ นี้

  • เวลาสร้างที่สูงขึ้น เนื่องจากคุณจะมีซอร์สโค้ดจำนวนมากในที่เดียว CI ของคุณจะต้องใช้เวลามากขึ้นในการรันทุกอย่างเพื่ออนุมัติ PR ทุกครั้ง

การตรวจทานเครื่องมือ

ชุดเครื่องมือสำหรับจัดการ monorepos มีการเติบโตอย่างต่อเนื่อง และในปัจจุบัน เป็นเรื่องง่ายมากที่จะหลงทางในระบบการสร้างที่หลากหลายสำหรับ monorepos คุณสามารถรับทราบวิธีแก้ปัญหายอดนิยมได้เสมอโดยใช้ repo นี้ แต่สำหรับตอนนี้ มาดูเครื่องมือที่ใช้กันอย่างแพร่หลายในปัจจุบันกับ JavaScript กัน:

  • Bazel เป็นระบบสร้างแบบ monorepo ของ Google ข้อมูลเพิ่มเติมเกี่ยวกับ Bazel: Awesome-bazel
  • Yarn เป็นเครื่องมือจัดการการพึ่งพา JavaScript ที่รองรับ monorepos ผ่านพื้นที่ทำงาน
  • Lerna เป็นเครื่องมือสำหรับจัดการโปรเจ็กต์ JavaScript ที่มีหลายแพ็คเกจ สร้างขึ้นจาก Yarn

เครื่องมือส่วนใหญ่ใช้แนวทางที่คล้ายคลึงกันจริงๆ แต่มีความแตกต่างบางประการ

ภาพประกอบของกระบวนการ CI/CD ของที่เก็บ monorepo git

เราจะเจาะลึกลงไปในเวิร์กโฟลว์ของ Lerna รวมถึงเครื่องมืออื่นๆ ในส่วนที่ 2 ของบทความนี้ เนื่องจากเป็นหัวข้อที่ค่อนข้างใหญ่ ในตอนนี้ เรามาดูภาพรวมของสิ่งที่อยู่ภายในกันดีกว่า:

เล่อนา

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

ด้วย Lerna คุณมีสองทางเลือกในการใช้แพ็คเกจของคุณ:

  1. โดยไม่ต้องกดไปที่รีโมท (NPM)
  2. ผลักแพ็คเกจของคุณไปที่รีโมท

ในขณะที่ใช้วิธีแรก คุณสามารถใช้การอ้างอิงโลคัลสำหรับแพ็คเกจของคุณ และโดยพื้นฐานแล้วไม่สนใจเกี่ยวกับ symlink เพื่อแก้ไขปัญหา

แต่ถ้าคุณใช้วิธีที่สอง คุณจะถูกบังคับให้นำเข้าแพ็คเกจของคุณจากระยะไกล (เช่น import { something } from @yourcompanyname/packagename; ) ซึ่งหมายความว่าคุณจะได้รับเวอร์ชันระยะไกลของแพ็คเกจของคุณเสมอ สำหรับการพัฒนาในพื้นที่ คุณจะต้องสร้างลิงก์เชื่อมโยงในรูทของโฟลเดอร์เพื่อให้บันเดิลแก้ไขแพ็กเกจในเครื่องแทนการใช้ที่อยู่ใน node_modules/ ของคุณ ด้วยเหตุนี้ ก่อนเปิดตัว Webpack หรือ Bundler ที่คุณชื่นชอบ คุณจะต้องเปิดใช้ lerna bootstrap ซึ่งจะเชื่อมโยงแพ็คเกจทั้งหมดโดยอัตโนมัติ

ภาพประกอบของการเนมสเปซโมดูลของคุณภายในแพ็คเกจโหนดเดียว

เส้นด้าย

เส้นด้ายในขั้นต้นเป็นตัวจัดการการพึ่งพาสำหรับแพ็คเกจ NPM ซึ่งไม่ได้สร้างขึ้นในขั้นต้นเพื่อรองรับ monorepos แต่ในเวอร์ชัน 1.0 นักพัฒนา Yarn ได้เปิดตัวคุณลักษณะที่เรียกว่า Workspaces เมื่อถึงเวลาเปิดตัว มันไม่เสถียรขนาดนั้น แต่หลังจากนั้นไม่นาน มันก็ใช้ได้กับโปรเจ็กต์การผลิต

พื้นที่ทำงาน นั้นเป็นแพ็คเกจซึ่งมี package.json เป็นของตัวเองและสามารถมีกฎการสร้างที่เฉพาะเจาะจงได้ (เช่น tsconfig.json แยกต่างหากหากคุณใช้ TypeScript ในโครงการของคุณ) คุณสามารถจัดการได้โดยไม่ต้องใช้ Yarn Workspaces โดยใช้ bash และมีการตั้งค่าเหมือนกันทุกประการ แต่เครื่องมือนี้ช่วยให้ขั้นตอนการติดตั้งและอัปเดตการพึ่งพาต่อแพ็คเกจง่ายขึ้น

โดยสรุป Yarn ที่มีพื้นที่ทำงานมีคุณสมบัติที่มีประโยชน์ดังต่อไปนี้:

  1. โฟลเดอร์ node_modules เดียวในรูทสำหรับแพ็คเกจทั้งหมด ตัวอย่างเช่น หากคุณมี packages/package_a และ packages/package_b —กับ package.json ของตัวเอง — การอ้างอิงทั้งหมดจะถูกติดตั้งในรูทเท่านั้น นั่นเป็นหนึ่งในความแตกต่างระหว่างวิธีการทำงานของเส้นด้ายและเลอนา
  2. การพึ่งพาการเชื่อมโยงกันเพื่อให้สามารถพัฒนาแพ็คเกจในเครื่องได้
  3. lockfile เดียวสำหรับการพึ่งพาทั้งหมด
  4. การอัปเดตการขึ้นต่อกันแบบโฟกัส ในกรณีที่คุณต้องการติดตั้งการขึ้นต่อกันอีกครั้งสำหรับแพ็คเกจเดียวเท่านั้น ซึ่งสามารถทำได้โดยใช้แฟ -focus
  5. บูรณาการกับ Lerna คุณสามารถทำให้ Yarn จัดการการติดตั้ง/เชื่อมโยงทั้งหมดได้อย่างง่ายดาย และปล่อยให้ Lerna ดูแลการเผยแพร่และควบคุมเวอร์ชัน นี่เป็นการตั้งค่าที่ได้รับความนิยมมากที่สุดเนื่องจากต้องใช้ความพยายามน้อยลงและใช้งานได้ง่าย

ลิงค์ที่เป็นประโยชน์:

  • พื้นที่ทำงานเส้นด้าย
  • วิธีสร้างโปรเจ็กต์ mono-repo ของ TypeScript

บาเซล

Bazel เป็นเครื่องมือสร้างสำหรับแอปพลิเคชันขนาดใหญ่ ซึ่งสามารถจัดการกับการพึ่งพาหลายภาษาและรองรับภาษาสมัยใหม่จำนวนมาก (Java, JS, Go, C++ เป็นต้น) ในกรณีส่วนใหญ่ การใช้ Bazel สำหรับแอปพลิเคชัน JS ขนาดเล็กถึงขนาดกลางนั้นเกินความสามารถ แต่ในวงกว้าง อาจให้ประโยชน์มากมายเนื่องจากประสิทธิภาพ

โดยธรรมชาติแล้ว Bazel นั้นดูคล้ายกับ Make, Gradle, Maven และเครื่องมืออื่นๆ ที่อนุญาตให้สร้างโปรเจ็กต์ตามไฟล์ซึ่งมีคำอธิบายของกฎบิลด์และการพึ่งพาโปรเจ็กต์ ไฟล์เดียวกันใน Bazel เรียกว่า BUILD และตั้งอยู่ภายในพื้นที่ทำงานของโครงการ Bazel ไฟล์ BUILD ใช้ Starlark ซึ่งเป็นภาษาสร้างระดับสูงที่มนุษย์สามารถอ่านได้ ซึ่งดูเหมือน Python มาก

โดยปกติ คุณจะไม่ต้องจัดการกับ BUILD มากนัก เนื่องจากมีต้นแบบจำนวนมากที่สามารถพบได้ง่ายบนเว็บ และมีการกำหนดค่าไว้แล้วและพร้อมสำหรับการพัฒนา เมื่อใดก็ตามที่คุณต้องการสร้างโครงการของคุณ โดยทั่วไปแล้ว Bazel จะทำสิ่งต่อไปนี้:

  1. โหลด ไฟล์ BUILD ที่เกี่ยวข้องกับเป้าหมาย
  2. วิเคราะห์ อินพุตและการอ้างอิง ใช้กฎบิลด์ที่ระบุ และสร้างกราฟการดำเนินการ
  3. ดำเนิน การบิลด์ของอินพุตจนกว่าจะสร้างเอาต์พุตบิลด์สุดท้าย

ลิงค์ที่เป็นประโยชน์:

  • JavaScript และ Bazel – เอกสารสำหรับการตั้งค่าโปรเจ็กต์ Bazel สำหรับ JS ตั้งแต่เริ่มต้น
  • กฎ JavaScript และ TypeScript สำหรับ Bazel – Boilerplate สำหรับ JS

บทสรุป

Monorepos เป็นเพียงเครื่องมือ มีข้อโต้แย้งมากมายว่ามีอนาคตหรือไม่ แต่ความจริงก็คือในบางกรณี เครื่องมือนี้ทำงานและจัดการกับมันอย่างมีประสิทธิภาพ ในช่วงไม่กี่ปีที่ผ่านมา เครื่องมือนี้มีการพัฒนา ได้รับความยืดหยุ่นมากขึ้น เอาชนะปัญหามากมาย และขจัดชั้นความซับซ้อนในแง่ของการกำหนดค่า

ยังมีปัญหาอีกมากที่ต้องค้นหา เช่น ประสิทธิภาพ Git ที่ไม่ดี แต่หวังว่าสิ่งนี้จะได้รับการแก้ไขในอนาคตอันใกล้

หากคุณต้องการเรียนรู้การสร้างไปป์ไลน์ CI/CD ที่มีประสิทธิภาพสำหรับแอปของคุณ ฉันขอแนะนำ วิธีสร้างไปป์ไลน์การปรับใช้เบื้องต้นที่มีประสิทธิภาพด้วย GitLab CI

ที่เกี่ยวข้อง: อธิบาย Git Flow ที่ปรับปรุงแล้ว