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

รายงานปัญหา ดูซอร์สโค้ด รุ่น Nightly · 8.0 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

โปรเจ็กต์ที่ใช้ระบบบิลด์ที่อิงตามอาร์ติแฟกต์ เช่น Bazel จะแบ่งออกเป็นชุดของโมดูล โดยโมดูลจะแสดงความสัมพันธ์แบบ Dependency ผ่าน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) สมมติว่าเป้าหมาย ก ขึ้นอยู่กับเป้าหมาย ข ซึ่งขึ้นอยู่กับเป้าหมาย ค ในไลบรารีทั่วไป เป้าหมาย ก ควรใช้คลาสที่กําหนดไว้ในเป้าหมาย ค ได้ไหม

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

รูปที่ 1 Dependency แบบทรานซิทีฟ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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