การจัดการการพึ่งพา

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

เมื่อดูหน้าก่อนหน้า เราจะเห็นธีมหนึ่งซ้ำไปซ้ำมา นั่นคือการจัดการโค้ดของคุณเองนั้นค่อนข้างตรงไปตรงมา แต่การจัดการการขึ้นต่อกันของโค้ดนั้นยากกว่ามาก การพึ่งพามีหลายรูปแบบ บางครั้งก็เป็นการพึ่งพางาน (เช่น "ส่งเอกสารก่อนที่จะทำเครื่องหมายการเผยแพร่ว่าเสร็จสมบูรณ์") และบางครั้งก็เป็นการพึ่งพาสิ่งประดิษฐ์ (เช่น "ฉันต้องมีไลบรารี Computer Vision เวอร์ชันล่าสุดเพื่อสร้างโค้ด") บางครั้งคุณก็มีการพึ่งพาภายในส่วนอื่นของโค้ดเบส และบางครั้งก็มีการพึ่งพาภายนอกโค้ดหรือข้อมูลที่ทีมอื่นเป็นเจ้าของ (ไม่ว่าจะในองค์กรของคุณหรือบุคคลที่สาม) แต่ไม่ว่าในกรณีใด แนวคิดที่ว่า "ฉันต้องมีสิ่งนั้นก่อนจึงจะมีสิ่งนี้ได้" เป็นสิ่งที่เกิดขึ้นซ้ำๆ ในการออกแบบระบบบิลด์ และการจัดการการขึ้นต่อกันอาจเป็นงานพื้นฐานที่สุดของระบบบิลด์

การจัดการโมดูลและทรัพยากร Dependency

โปรเจ็กต์ที่ใช้ระบบบิลด์ที่อิงตามอาร์ติแฟกต์ เช่น Bazel จะแบ่งออกเป็นชุด ของโมดูล โดยโมดูลจะแสดงทรัพยากร Dependency ซึ่งกันและกันผ่านBUILD ไฟล์ การจัดระเบียบโมดูลและการขึ้นต่อกันอย่างเหมาะสมจะส่งผลอย่างมากต่อทั้งประสิทธิภาพของระบบบิลด์และปริมาณงานที่ต้องใช้ในการบำรุงรักษา

การใช้โมดูลแบบละเอียดและกฎ 1:1:1

คำถามแรกที่เกิดขึ้นเมื่อสร้างโครงสร้างการสร้างที่อิงตามอาร์ติแฟกต์คือ การตัดสินใจว่าโมดูลแต่ละโมดูลควรมีฟังก์ชันการทำงานมากน้อยเพียงใด ใน Bazel โมดูลจะแสดงด้วยเป้าหมายที่ระบุหน่วยที่สร้างได้ เช่น java_library หรือ go_binary ในกรณีที่ซับซ้อนที่สุด โปรเจ็กต์ทั้งหมดอาจอยู่ในโมดูลเดียวโดยวางไฟล์ BUILD ไว้ที่รูทและ ใช้ฟังก์ชัน glob แบบเรียกซ้ำเพื่อรวมไฟล์ต้นฉบับทั้งหมดของโปรเจ็กต์นั้นเข้าด้วยกัน ในอีกด้านหนึ่ง ไฟล์ต้นฉบับเกือบทุกไฟล์สามารถสร้างเป็นโมดูลของตัวเองได้ ซึ่งจะทำให้ต้องระบุไฟล์อื่นๆ ที่ขึ้นอยู่กับไฟล์นั้นในไฟล์ BUILD

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

แม้ว่าระดับความละเอียดที่แน่นอนจะแตกต่างกันไปตามภาษา (และมักจะแตกต่างกันแม้ในภาษาเดียวกัน) แต่ Google มักจะชอบโมดูลที่มีขนาดเล็กกว่ามากเมื่อเทียบกับโมดูลที่อาจเขียนในระบบบิลด์ตามงาน โดยปกติแล้วไบนารีการผลิตที่ Google มักขึ้นอยู่กับเป้าหมายหลายหมื่นรายการ และแม้แต่ทีมขนาดกลางก็อาจเป็นเจ้าของเป้าหมายหลายร้อยรายการภายในโค้ดเบส สำหรับภาษาอย่าง Java ซึ่งมีแนวคิดการจัดแพ็กเกจในตัวที่ชัดเจน โดยปกติแล้วแต่ละไดเรกทอรีจะมีแพ็กเกจ เป้าหมาย และไฟล์ BUILD เพียงรายการเดียว (Pants ซึ่งเป็นระบบบิลด์อีกระบบหนึ่งที่อิงตาม Bazel เรียกกฎนี้ว่ากฎ 1:1:1) ภาษาที่มีรูปแบบการแพ็กเกจที่อ่อนแอกว่า มักจะกำหนดเป้าหมายหลายรายการต่อไฟล์ BUILD

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

เครื่องมือบางอย่าง เช่น buildifier และ buildozer จะพร้อมใช้งานกับ Bazel ในไดเรกทอรี buildtools

การลดการแสดงโมดูล

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

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

การจัดการทรัพยากร Dependency

โมดูลต้องอ้างอิงถึงกันได้ ข้อเสียของการแบ่งโค้ดเบสออกเป็นโมดูลแบบละเอียดคือคุณต้องจัดการการอ้างอิงระหว่างโมดูลเหล่านั้น (แม้ว่าเครื่องมือจะช่วยทำให้กระบวนการนี้เป็นอัตโนมัติได้) โดยปกติแล้ว การระบุการอ้างอิงเหล่านี้ มักจะเป็นเนื้อหาส่วนใหญ่ในไฟล์ BUILD

ทรัพยากร Dependency ภายใน

ในโปรเจ็กต์ขนาดใหญ่ที่แบ่งออกเป็นโมดูลแบบละเอียด การอ้างอิงส่วนใหญ่มีแนวโน้มที่จะเป็นภายใน นั่นคือในเป้าหมายอื่นที่กำหนดและสร้างในที่เก็บแหล่งข้อมูลเดียวกัน Dependency ภายในแตกต่างจาก Dependency ภายนอกตรงที่สร้างจากแหล่งที่มาแทนที่จะดาวน์โหลดเป็นอาร์ติแฟกต์ที่สร้างไว้ล่วงหน้า ขณะเรียกใช้บิลด์ ซึ่งหมายความว่าไม่มีแนวคิดเรื่อง "เวอร์ชัน" สำหรับ การอ้างอิงภายใน โดยเป้าหมายและการอ้างอิงภายในทั้งหมดจะ สร้างขึ้นที่คอมมิต/การแก้ไขเดียวกันในที่เก็บเสมอ ปัญหาหนึ่งที่ควร จัดการอย่างระมัดระวังเกี่ยวกับ Dependency ภายในคือวิธีจัดการกับ Dependency แบบทรานซิทีฟ (รูปที่ 1) สมมติว่าเป้าหมาย A ขึ้นอยู่กับเป้าหมาย B ซึ่งขึ้นอยู่กับเป้าหมายไลบรารีทั่วไป C เป้าหมาย A ควรใช้คลาสที่กำหนดไว้ในเป้าหมาย C ได้ไหม

การอ้างอิงแบบทรานซิทีฟ

รูปที่ 1 ทรัพยากร Dependency แบบทรานซิทีฟ

เครื่องมือพื้นฐานไม่มีปัญหาใดๆ ในกรณีนี้ เนื่องจากทั้ง B และ C จะลิงก์ไปยังเป้าหมาย A เมื่อสร้างขึ้น ดังนั้น A จะรู้จักสัญลักษณ์ใดๆ ที่กำหนดไว้ใน C Bazel อนุญาตให้ทำเช่นนี้มาหลายปี แต่เมื่อ Google เติบโตขึ้น เราก็เริ่มเห็นปัญหา สมมติว่ามีการปรับโครงสร้าง B เพื่อให้ไม่จำเป็นต้อง ขึ้นอยู่กับ C อีกต่อไป หากมีการนำการขึ้นต่อกันของ B กับ C ออก A และเป้าหมายอื่นๆ ที่ใช้ C ผ่านการขึ้นต่อกันของ B จะใช้งานไม่ได้ ในทางปฏิบัติแล้ว การอ้างอิงของเป้าหมายกลายเป็นส่วนหนึ่งของสัญญาแบบสาธารณะและไม่สามารถเปลี่ยนแปลงได้อย่างปลอดภัย ซึ่งหมายความว่าการพึ่งพาอาศัยกันสะสมไปเรื่อยๆ และการสร้างที่ Google เริ่มช้าลง

ในที่สุด Google ก็แก้ไขปัญหานี้ได้ด้วยการเปิดตัว "โหมดการอ้างอิงแบบเข้มงวด" ใน Bazel ในโหมดนี้ Bazel จะตรวจหาว่าเป้าหมายพยายามอ้างอิงสัญลักษณ์โดยไม่ได้ขึ้นอยู่กับสัญลักษณ์นั้นโดยตรงหรือไม่ หากตรวจพบ Bazel จะล้มเหลวพร้อมกับข้อผิดพลาดและคำสั่งเชลล์ที่ใช้เพื่อแทรกการอ้างอิงโดยอัตโนมัติ การเปิดตัวการเปลี่ยนแปลงนี้ในโค้ดเบสทั้งหมดของ Google และ การปรับโครงสร้างเป้าหมายการสร้างหลายล้านรายการของเราแต่ละรายการเพื่อระบุ การอ้างอิงอย่างชัดเจนเป็นความพยายามที่ใช้เวลาหลายปี แต่ก็คุ้มค่า ตอนนี้การสร้างของเราเร็วขึ้นมากเนื่องจากเป้าหมายมีทรัพยากร Dependency ที่ไม่จำเป็นน้อยลง และวิศวกรก็มีสิทธิ์นำทรัพยากร Dependency ที่ไม่จำเป็นออกได้โดยไม่ต้องกังวลว่าจะทำให้เป้าหมายที่ขึ้นอยู่กับทรัพยากรเหล่านั้นเสียหาย

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

ทรัพยากร Dependency ภายนอก

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

การจัดการทรัพยากร Dependency โดยอัตโนมัติเทียบกับการจัดการด้วยตนเอง

ระบบบิลด์สามารถอนุญาตให้จัดการเวอร์ชันของทรัพยากร Dependency ภายนอกได้ ทั้งด้วยตนเองหรือโดยอัตโนมัติ เมื่อจัดการด้วยตนเอง ไฟล์บิลด์จะแสดงรายการเวอร์ชันที่ต้องการดาวน์โหลดจากที่เก็บอาร์ติแฟกต์อย่างชัดเจน โดยมักจะใช้สตริงเวอร์ชันเชิงความหมาย เช่น 1.1.4 เมื่อมีการจัดการโดยอัตโนมัติ ไฟล์ต้นฉบับจะระบุช่วงของเวอร์ชันที่ยอมรับได้ และระบบบิลด์จะดาวน์โหลดเวอร์ชันล่าสุดเสมอ ตัวอย่างเช่น Gradle อนุญาตให้ประกาศเวอร์ชันการขึ้นต่อกันเป็น "1.+" เพื่อระบุว่ายอมรับเวอร์ชันย่อยหรือเวอร์ชันแพตช์ของการขึ้นต่อกันได้ตราบใดที่เวอร์ชันหลักเป็น 1

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

ในทางตรงกันข้าม เนื่องจากทรัพยากร Dependency ที่จัดการด้วยตนเองต้องมีการเปลี่ยนแปลงในระบบควบคุมแหล่งที่มา จึงค้นหาและย้อนกลับได้ง่าย และสามารถเช็คเอาต์ที่เก็บเวอร์ชันเก่าเพื่อสร้างด้วยทรัพยากร Dependency เก่าได้ Bazel กำหนดให้ต้องระบุเวอร์ชันของทรัพยากร Dependency ทั้งหมดด้วยตนเอง แม้ในระดับปานกลาง ค่าใช้จ่ายในการจัดการเวอร์ชันด้วยตนเองก็คุ้มค่ากับความเสถียรที่ได้รับ

กฎเวอร์ชันเดียว

โดยปกติแล้ว ไลบรารีเวอร์ชันต่างๆ จะแสดงด้วยอาร์ติแฟกต์ที่แตกต่างกัน ดังนั้นในทางทฤษฎีแล้วจึงไม่มีเหตุผลที่เวอร์ชันต่างๆ ของการอ้างอิงภายนอกเดียวกัน จะประกาศในระบบบิลด์ภายใต้ชื่อที่แตกต่างกันไม่ได้ ด้วยวิธีนี้ เป้าหมายแต่ละรายการจะเลือกเวอร์ชันของทรัพยากร Dependency ที่ต้องการใช้ได้ ซึ่งทำให้เกิดปัญหามากมายในทางปฏิบัติ ดังนั้น Google จึงบังคับใช้กฎเวอร์ชันเดียวที่เข้มงวดสำหรับ การขึ้นต่อกันของบุคคลที่สามทั้งหมดในโค้ดเบสของเรา

ปัญหาที่ใหญ่ที่สุดของการอนุญาตให้ใช้หลายเวอร์ชันคือปัญหาการขึ้นต่อกันแบบไดมอนด์ สมมติว่าเป้าหมาย A ขึ้นอยู่กับเป้าหมาย B และไลบรารีภายนอกเวอร์ชัน v1 หากต่อมามีการปรับโครงสร้างเป้าหมาย B เพื่อเพิ่มการอ้างอิงใน v2 ของไลบรารีภายนอกเดียวกัน เป้าหมาย A จะใช้งานไม่ได้เนื่องจากตอนนี้ขึ้นอยู่กับไลบรารีเดียวกัน 2 เวอร์ชันโดยนัย ในทางปฏิบัติ การเพิ่มการอ้างอิงใหม่จากเป้าหมายไปยังไลบรารีของบุคคลที่สามที่มีหลายเวอร์ชันไม่เคยปลอดภัย เนื่องจากผู้ใช้เป้าหมายรายใดรายหนึ่งอาจอ้างอิงเวอร์ชันอื่นอยู่แล้ว การปฏิบัติตามกฎเวอร์ชันเดียวจะทำให้ความขัดแย้งนี้เป็นไปไม่ได้ หากเป้าหมายเพิ่มการอ้างอิงไลบรารีของบุคคลที่สาม การอ้างอิงที่มีอยู่จะอยู่ในเวอร์ชันเดียวกันอยู่แล้ว จึงทำงานร่วมกันได้อย่างราบรื่น

การอ้างอิงภายนอกแบบทรานซิทีฟ

การจัดการกับทรัพยากร Dependency แบบทรานซิทีฟของทรัพยากร Dependency ภายนอกอาจเป็นเรื่องยากเป็นพิเศษ ที่เก็บอาร์ติแฟกต์หลายแห่ง เช่น Maven Central อนุญาตให้อาร์ติแฟกต์ระบุการขึ้นต่อกันในอาร์ติแฟกต์อื่นๆ ในที่เก็บเวอร์ชันใดเวอร์ชันหนึ่ง เครื่องมือบิลด์ เช่น Maven หรือ Gradle มักจะดาวน์โหลดการอ้างอิงแบบทรานซิทีฟแต่ละรายการแบบเรียกซ้ำโดยค่าเริ่มต้น ซึ่งหมายความว่าการเพิ่มการอ้างอิงรายการเดียวในโปรเจ็กต์อาจทำให้มีการดาวน์โหลดอาร์ติแฟกต์หลายสิบรายการโดยรวม

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

Bazel ไม่ได้ใช้เพื่อดาวน์โหลดการขึ้นต่อกันแบบทรานซิทีฟโดยอัตโนมัติ ก่อนหน้านี้จะใช้ไฟล์ WORKSPACE ซึ่งกำหนดให้ต้องแสดงรายการทรัพยากร Dependency แบบทรานซิทีฟทั้งหมด ซึ่งทำให้เกิดความยุ่งยากเป็นอย่างมากเมื่อจัดการทรัพยากร Dependency ภายนอก นับตั้งแต่นั้นมา Bazel ได้เพิ่มการรองรับการจัดการการอ้างอิงภายนอกแบบทรานซิทีฟโดยอัตโนมัติ ในรูปแบบของไฟล์ MODULE.bazel ดูรายละเอียดเพิ่มเติมได้ที่ภาพรวมของ การขึ้นต่อภายนอก

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

แคชผลลัพธ์การบิลด์โดยใช้ทรัพยากร Dependency ภายนอก

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

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

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

ความปลอดภัยและความน่าเชื่อถือของทรัพยากร Dependency ภายนอก

การใช้สิ่งประดิษฐ์จากแหล่งที่มาของบุคคลที่สามมีความเสี่ยงโดยธรรมชาติ มีความเสี่ยงด้านความพร้อมใช้งานหากแหล่งที่มาของบุคคลที่สาม (เช่น ที่เก็บที่สร้างขึ้น) หยุดทำงาน เนื่องจากบิลด์ทั้งหมดอาจหยุดชะงักหากดาวน์โหลด การอ้างอิงภายนอกไม่ได้ นอกจากนี้ ยังมีความเสี่ยงด้านความปลอดภัยด้วย หากระบบของบุคคลที่สามถูกผู้โจมตีบุกรุก ผู้โจมตีอาจแทนที่อาร์ติแฟกต์ที่อ้างอิงด้วยอาร์ติแฟกต์ที่ออกแบบเอง ซึ่งจะทำให้ผู้โจมตีแทรกโค้ดที่กำหนดเองลงในการสร้างของคุณได้ คุณสามารถลดปัญหาทั้ง 2 อย่างได้โดยการมิเรอร์อาร์ติแฟกต์ที่คุณใช้ไปยังเซิร์ฟเวอร์ที่คุณควบคุม และบล็อกระบบบิลด์ไม่ให้เข้าถึงที่เก็บอาร์ติแฟกต์ของบุคคลที่สาม เช่น Maven Central ข้อเสียคือการดูแลรักษามิเรอร์เหล่านี้ต้องใช้ความพยายามและทรัพยากร ดังนั้น การเลือกว่าจะใช้หรือไม่จึงขึ้นอยู่กับขนาดของโปรเจ็กต์ นอกจากนี้ คุณยังป้องกันปัญหาด้านความปลอดภัยได้อย่างสมบูรณ์โดยมีค่าใช้จ่ายเพียงเล็กน้อยด้วยการกำหนดให้ระบุแฮชของอาร์ติแฟกต์ของบุคคลที่สามแต่ละรายการในที่เก็บแหล่งที่มา ซึ่งจะทำให้การสร้างล้มเหลวหากมีการดัดแปลงอาร์ติแฟกต์ อีกทางเลือกหนึ่งที่หลีกเลี่ยงปัญหานี้ได้โดยสิ้นเชิงคือการจัดหาทรัพยากร Dependency ของโปรเจ็กต์จากผู้ให้บริการ เมื่อโปรเจ็กต์จัดจำหน่ายทรัพยากร Dependency ของตนเอง โปรเจ็กต์จะตรวจสอบทรัพยากรเหล่านั้นใน การควบคุมแหล่งที่มาควบคู่ไปกับซอร์สโค้ดของโปรเจ็กต์ ไม่ว่าจะเป็นซอร์สหรือเป็น ไบนารี ซึ่งหมายความว่าการอ้างอิงภายนอกทั้งหมดของโปรเจ็กต์ จะได้รับการแปลงเป็นการอ้างอิงภายใน Google ใช้วิธีนี้ภายในองค์กร โดยจะตรวจสอบไลบรารีของบุคคลที่สามทุกรายการที่อ้างอิงทั่วทั้ง Google ในไดเรกทอรี third_party ที่รูทของโครงสร้างแหล่งข้อมูลของ Google อย่างไรก็ตาม วิธีนี้ใช้ได้เฉพาะที่ Google เนื่องจากระบบควบคุมแหล่งที่มาของ Google สร้างขึ้นมาโดยเฉพาะเพื่อจัดการ Monorepo ขนาดใหญ่มาก ดังนั้นการใช้ Vendor อาจไม่ใช่ตัวเลือกสำหรับทุกองค์กร