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

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

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

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

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

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

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

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

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

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

การลดระดับการเข้าถึงโมดูล

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

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

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

โมดูลต้องอ้างอิงถึงกันได้ ข้อเสียของการแบ่งโค้ดเบสออกเป็นโมดูลแบบละเอียดคือคุณต้องจัดการทรัพยากร 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 จะล้มเหลวพร้อมกับข้อผิดพลาดและคำสั่ง Shell ที่ใช้เพื่อแทรกทรัพยากร Dependency โดยอัตโนมัติ การเปิดตัวการเปลี่ยนแปลงนี้ในฐานของโค้ดทั้งหมดของ Google และการเปลี่ยนโครงสร้างภายในโค้ดเป้าหมายบิลด์นับล้านรายการของเราแต่ละรายการเพื่อระบุทรัพยากร Dependency อย่างชัดเจนเป็นความพยายามที่ใช้เวลาหลายปี แต่ก็คุ้มค่า ตอนนี้การสร้างของเราเร็วขึ้นมากเนื่องจากเป้าหมายมีทรัพยากร Dependency ที่ไม่จำเป็นน้อยลง และวิศวกรก็มีสิทธิ์นำทรัพยากร Dependency ที่ไม่จำเป็นออกได้โดยไม่ต้องกังวลว่าจะทำให้เป้าหมายที่ขึ้นอยู่กับทรัพยากรเหล่านั้นเสียหาย

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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