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

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

การจัดการกับโมดูลและการขึ้นต่อกัน

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

การใช้ข้อบังคับแบบละเอียดและกฎ 1:1:1

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

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

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

Dependency แบบทรานซิทีฟ

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

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

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

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

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

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

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

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

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

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

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

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

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

ทรัพยากร Dependency ภายนอกแบบทรานซิทีฟ

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

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

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

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

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

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

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

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

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

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