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

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

การจัดการกับโมดูลและการอ้างอิง

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ทรัพยากร Dependency ชั่วคราว

รูปที่ 1 ทรัพยากร Dependency ชั่วคราว

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

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

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

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

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

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

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

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

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

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

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

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

ทรัพยากร Dependency ภายนอกทางอ้อม

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

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

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

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

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

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

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

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

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

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