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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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