เมื่อดูหน้าก่อนหน้า คุณจะเห็นว่ามีธีมหนึ่งที่ซ้ำกันอยู่ตลอด นั่นคือ การจัดการโค้ดของคุณเองนั้นค่อนข้างตรงไปตรงมา แต่การจัดการทรัพยากรที่เกี่ยวข้องนั้นยากกว่ามาก Dependency มีหลายประเภท เช่น บางครั้งมี Dependency กับงาน (เช่น "พุชเอกสารประกอบก่อนที่จะทําเครื่องหมายรุ่นเป็น "เสร็จสมบูรณ์") และบางครั้งมี Dependency กับอาร์ติแฟกต์ (เช่น "ฉันต้องมีไลบรารี Computer Vision เวอร์ชันล่าสุดเพื่อสร้างโค้ด") บางครั้งคุณมี Dependency ภายในกับโค้ดเบสส่วนอื่น และบางครั้งคุณมี Dependency ภายนอกกับโค้ดหรือข้อมูลที่เป็นของทีมอื่น (ในองค์กรหรือบุคคลที่สาม) แต่ไม่ว่าในกรณีใด แนวคิดที่ว่า "ฉันต้องมีสิ่งนั้นก่อนจึงจะมีสิ่งนี้ได้" เป็นสิ่งที่เกิดขึ้นซ้ำๆ ในการออกแบบระบบบิลด์ และการจัดการทรัพยากรภายนอกอาจเป็นงานพื้นฐานที่สุดของระบบบิลด์
การจัดการกับโมดูลและการขึ้นต่อกัน
โปรเจ็กต์ที่ใช้ระบบบิลด์ที่อิงตามอาร์ติแฟกต์ เช่น Bazel จะแบ่งออกเป็นชุด
ของโมดูลต่างๆ โดยมีโมดูลที่แสดงทรัพยากร Dependency ของแต่ละโมดูลผ่าน BUILD
การจัดระเบียบโมดูลและข้อกำหนดเหล่านี้อย่างเหมาะสมจะส่งผลอย่างมากต่อทั้งประสิทธิภาพของระบบบิลด์และปริมาณงานที่ต้องใช้เพื่อบำรุงรักษา
การใช้ข้อบังคับแบบละเอียดและกฎ 1:1:1
คำถามแรกที่ปรากฏขึ้นเมื่อจัดโครงสร้างบิลด์ที่อิงตามอาร์ติแฟกต์คือ
ว่าแต่ละโมดูลควรมีฟังก์ชันการทำงานเท่าใด ใน Bazel โมดูลจะแสดงด้วยเป้าหมายที่ระบุหน่วยที่คอมไพล์ได้ เช่น java_library
หรือ go_binary
ทั้งโปรเจ็กต์อาจเป็นไปได้
ที่มีอยู่ในโมดูลเดียวโดยวางไฟล์ BUILD
1 ไฟล์ไว้ที่รูท
รวบรวมข้อมูลไฟล์ต้นฉบับของโครงการนั้นทั้งหมดซ้ำๆ ที่อื่น
มากเป็นพิเศษ ไฟล์ต้นฉบับเกือบทุกไฟล์สามารถสร้างเป็นโมดูลของตัวเองได้อย่างมีประสิทธิภาพ
กำหนดให้แต่ละไฟล์แสดงในไฟล์ BUILD
ทุกๆ ไฟล์ที่ต้องพึ่งพา
โครงการส่วนใหญ่จะอยู่ระหว่างปลายสุดเหล่านี้ และการเลือกนี้จะต้องมี
ข้อดีข้อเสียระหว่างประสิทธิภาพและความสามารถในการบำรุงรักษา การใช้โมดูลเดียวสําหรับโปรเจ็กต์ทั้งหมดอาจหมายความว่าคุณไม่จําเป็นต้องแก้ไขไฟล์ BUILD
เลย ยกเว้นเมื่อเพิ่มการพึ่งพาภายนอก แต่ระบบบิลด์จะต้องสร้างโปรเจ็กต์ทั้งหมดพร้อมกันเสมอ ซึ่งหมายความว่าจะไม่สามารถ
โหลดพร้อมกันหรือกระจายส่วนต่างๆ ของบิลด์ และไม่สามารถแคชส่วนต่างๆ ได้
ที่ตัวเองสร้างขึ้นอยู่แล้ว โดยหนึ่งโมดูลต่อไฟล์จะตรงกันข้ามคือระบบบิลด์
มีความยืดหยุ่นสูงสุดในการแคชและกำหนดเวลาขั้นตอนการสร้างบิลด์ แต่
วิศวกรต้องใช้ความพยายามมากขึ้นในการรักษารายการทรัพยากร Dependency
การเปลี่ยนไฟล์ที่จะอ้างอิง
แม้ว่ารายละเอียดที่แน่นอนจะแตกต่างกันไปตามภาษา (และมักอ่านภายใน
) Google มักจะสนับสนุนโมดูลที่มีขนาดเล็กกว่า
มักจะเขียนในระบบบิลด์ที่อิงตามงาน โดยทั่วไปแล้วไบนารีเวอร์ชันที่ใช้งานจริงของ Google มักใช้เป้าหมายหลายหมื่นรายการ และแม้แต่ทีมขนาดกลางก็อาจมีเป้าหมายหลายร้อยรายการในโค้ดเบส สำหรับภาษาอย่าง Java ที่มีแนวคิดการแพ็กเกจในตัวที่ชัดเจน แต่ละไดเรกทอรีมักจะมีแพ็กเกจ เป้าหมาย และไฟล์ BUILD
ไฟล์เดียว (Pants ซึ่งเป็นระบบการสร้างอีกระบบหนึ่งที่ใช้ Bazel เรียกสิ่งนี้ว่ากฎ 1:1:1) ภาษาที่มีรูปแบบการบรรจุที่อ่อนแอกว่ามักจะกำหนดเป้าหมายหลายรายการต่อไฟล์ BUILD
ประโยชน์ของเป้าหมายการสร้างที่เล็กลงจะเริ่มแสดงผลเมื่อสร้างจำนวนมาก เนื่องจากจะทําให้การสร้างแบบกระจายเร็วขึ้นและลดความจำเป็นในการสร้างเป้าหมายใหม่ได้
ข้อดีจะชัดเจนยิ่งขึ้นเมื่อเริ่มการทดสอบ เนื่องจากเป้าหมายที่ละเอียดยิ่งขึ้นช่วยให้ระบบบิลด์ทำงานได้ชาญฉลาดมากขึ้นในการเรียกใช้ชุดทดสอบแบบจำกัดซึ่งอาจได้รับผลกระทบจากการเปลี่ยนแปลงหนึ่งๆ เนื่องจาก Google เชื่อในประโยชน์ที่เป็นระบบของการใช้ขนาดเล็กลง
เราก็ได้สร้างความก้าวหน้าในการลดผลเสียโดยการลงทุนใน
จัดการไฟล์ BUILD
โดยอัตโนมัติเพื่อไม่ให้เป็นภาระกับนักพัฒนาซอฟต์แวร์
เครื่องมือเหล่านี้บางรายการ เช่น buildifier
และ buildozer
มีให้ใช้งานกับ Bazel ในไดเรกทอรี buildtools
การลดการเปิดเผยโมดูล
Bazel และระบบการสร้างอื่นๆ อนุญาตให้แต่ละเป้าหมายระบุระดับการเข้าถึง ซึ่งเป็นพร็อพเพอร์ตี้ที่กําหนดว่าเป้าหมายอื่นๆ ใดบ้างที่อาจใช้เป้าหมายนั้น เป้าหมายส่วนตัว
อ้างอิงได้ในไฟล์ BUILD
ของตัวเองเท่านั้น เป้าหมายอาจให้สิทธิ์ที่กว้างขึ้น
การมองเห็นเป้าหมายของรายการ BUILD
ไฟล์ที่กำหนดไว้อย่างชัดเจน หรือใน
เปิดเผยต่อทุกเป้าหมายในพื้นที่ทำงาน
เช่นเดียวกับภาษาโปรแกรมส่วนใหญ่ วิธีที่ดีที่สุดคือการลดการแสดงผลเนื่องจาก
ให้ได้มากที่สุด โดยทั่วไป ทีมที่ Google จะทำให้เป้าหมายเป็นแบบสาธารณะก็ต่อเมื่อ
เป้าหมายเหล่านี้แสดงถึงห้องสมุดที่ใช้กันอย่างแพร่หลายซึ่งพร้อมให้บริการแก่ทีมงานของ Google
ทีมที่กําหนดให้ผู้อื่นต้องประสานงานกับตนก่อนใช้โค้ดจะรักษารายการที่อนุญาตของเป้าหมายลูกค้าไว้เป็นระดับการเข้าถึงของเป้าหมาย เป้าหมายการติดตั้งใช้งานภายในของทีมแต่ละทีมจะจํากัดไว้เฉพาะไดเรกทอรีที่ทีมเป็นเจ้าของ และไฟล์ BUILD
ส่วนใหญ่จะมีเป้าหมายเพียงรายการเดียวที่ไม่ใช่แบบส่วนตัว
การจัดการ Dependency
โมดูลต้องสามารถอ้างอิงกันและกันได้ ข้อเสียของการทำลาย
Codebase ต่างๆ ลงในโมดูลแบบละเอียดก็คือคุณต้องจัดการทรัพยากร Dependency
ในโมดูลเหล่านั้น (แต่เครื่องมือจะช่วยให้การทำงานเป็นไปโดยอัตโนมัติได้) การแสดงออกถึงความเกี่ยวข้องเหล่านี้มักจะกลายเป็นเนื้อหาส่วนใหญ่ในไฟล์ BUILD
ทรัพยากร Dependency ภายใน
ในโปรเจ็กต์ขนาดใหญ่ที่แบ่งออกเป็นโมดูลแบบละเอียด ไลบรารีส่วนใหญ่มีแนวโน้มที่จะเป็นแบบภายใน กล่าวคือ อยู่ในเป้าหมายอื่นที่กําหนดไว้และสร้างขึ้นในที่เก็บแหล่งที่มาเดียวกัน ทรัพยากร Dependency ภายในแตกต่างจากทรัพยากร Dependency ภายนอกใน สร้างขึ้นจากต้นฉบับ ไม่ใช่ดาวน์โหลดเป็นอาร์ติแฟกต์ที่สร้างไว้ล่วงหน้า ขณะใช้งานบิลด์ นั่นหมายความว่าเราไม่มีคำว่า "เวอร์ชัน" ทรัพยากร Dependency ภายในเป้าหมายและการอ้างอิงภายในทั้งหมดเสมอ ที่สร้างขึ้นที่คอมมิต/การแก้ไขเดียวกันในที่เก็บ ปัญหาหนึ่งที่ควรจะเป็น มีการจัดการอย่างรอบคอบเกี่ยวกับทรัพยากร Dependency ภายใน ทรัพยากร Dependency แบบสับเปลี่ยน (รูปที่ 1) สมมติว่าเป้าหมาย ก ขึ้นอยู่กับเป้าหมาย ข ซึ่งขึ้นอยู่กับเป้าหมาย ค ในไลบรารีทั่วไป ควรกำหนดเป้าหมาย A ให้ใช้คลาสได้ กำหนดไว้ในเป้าหมาย C หรือไม่
รูปที่ 1 Dependency แบบทรานซิทีฟ
เครื่องมือพื้นฐานไม่มีปัญหานี้ ทั้ง B และ C จะลิงก์กับเป้าหมาย A เมื่อสร้าง ดังนั้น A จะรู้จักสัญลักษณ์ที่กําหนดไว้ใน C Bazel ทำสิ่งนี้มานานหลายปี แต่เมื่อ Google เติบโตขึ้น เริ่มพบปัญหา สมมติว่า B ได้รับการรีแฟกทอริงเพื่อไม่ให้ต้องอาศัย C อีกต่อไป หากนําการพึ่งพา C ของ B ออก A และเป้าหมายอื่นๆ ที่ใช้ C ผ่านการพึ่งพา B จะใช้งานไม่ได้ การกำหนดเป้าหมาย ทรัพยากร Dependency ได้กลายเป็นส่วนหนึ่งของสัญญาสาธารณะ และจะไม่มีทางปลอดภัย มีการเปลี่ยนแปลง ซึ่งหมายความว่าทรัพยากร Dependency ที่สะสมขึ้นเมื่อเวลาผ่านไปและสร้างขึ้นที่ Google เริ่มช้าลง
ในที่สุด Google ก็แก้ปัญหานี้ได้ด้วยการเปิดตัว "โหมดการพึ่งพาแบบโยงอย่างเข้มงวด" ใน Bazel ในโหมดนี้ Bazel จะตรวจจับว่าเป้าหมายพยายามอ้างอิงสัญลักษณ์โดยไม่ใช้สัญลักษณ์นั้นโดยตรงหรือไม่ หากใช่ ระบบจะแสดงข้อผิดพลาดและคำสั่งเชลล์ที่ใช้แทรกข้อกำหนดโดยอัตโนมัติ การนำการเปลี่ยนแปลงนี้ไปใช้กับโค้ดทั้งหมดของ Google และการจัดระเบียบเป้าหมายการสร้างหลายล้านรายการของเราใหม่เพื่อแสดงรายการสิ่งที่ต้องพึ่งพาอย่างชัดเจนเป็นงานที่ใช้เวลาหลายปี แต่คุ้มค่ามาก ตอนนี้บิลด์ของเราเร็วขึ้นมากเนื่องจากเป้าหมายมี Dependency ที่ไม่จำเป็นน้อยลง และวิศวกรสามารถนํา Dependency ที่ไม่จําเป็นออกได้โดยไม่ต้องกังวลว่าจะทําให้เป้าหมายที่ต้องใช้ Dependency เหล่านั้นใช้งานไม่ได้
ดังเช่นเคย การบังคับใช้ข้อกำหนดแบบทรานซิทีฟที่เข้มงวดนั้นต้องแลกมาด้วยข้อเสียบางอย่าง ไฟล์บิลด์จึงมีรายละเอียดมากขึ้น เนื่องจากตอนนี้ต้องระบุไลบรารีที่ใช้บ่อยอย่างชัดเจนในหลายๆ ที่แทนที่จะดึงเข้ามาโดยไม่ตั้งใจ และวิศวกรต้องทุ่มเทมากขึ้นในการเพิ่มข้อกำหนดไว้ในไฟล์ BUILD
ตั้งแต่นั้นมา เราได้พัฒนาเครื่องมือที่ช่วยลดความยุ่งยากนี้ด้วยการตรวจหาข้อกำหนดเบื้องต้นที่ขาดหายไปจำนวนมากโดยอัตโนมัติและเพิ่มลงในไฟล์ BUILD
โดยไม่ต้องมีการดำเนินการใดๆ จากนักพัฒนาซอฟต์แวร์ แต่ถึงแม้จะไม่มีเครื่องมือดังกล่าว เราพบว่าข้อเสียนั้นคุ้มค่าเมื่อโค้ดเบสมีขนาดใหญ่ขึ้น การเพิ่มการพึ่งพาในไฟล์ BUILD
อย่างชัดแจ้งเป็นค่าใช้จ่ายแบบครั้งเดียว แต่การจัดการกับการพึ่งพาแบบทรานซิทีฟโดยนัยอาจทำให้เกิดปัญหาอย่างต่อเนื่องตราบใดที่มีเป้าหมายการสร้างอยู่ Bazel จะบังคับใช้ Dependency แบบทรานซิทีฟที่เข้มงวดในโค้ด Java โดยค่าเริ่มต้น
ทรัพยากร Dependency ภายนอก
หากความเกี่ยวข้องไม่ใช่ภายใน จะต้องเป็นภายนอก ทรัพยากร Dependency ภายนอก ผู้ที่อยู่ในอาร์ติแฟกต์ที่สร้างขึ้นและจัดเก็บไว้นอกระบบบิลด์ ทรัพยากร Dependency ถูกนำเข้าโดยตรงจากที่เก็บอาร์ติแฟกต์ (โดยทั่วไปจะมีการเข้าถึง) บนอินเทอร์เน็ต) และใช้งานตามที่มีอยู่ แทนการสร้างจากต้นทาง หนึ่งใน ความแตกต่างที่ยิ่งใหญ่ที่สุดระหว่างทรัพยากร Dependency ภายนอกและภายในก็คือ ทรัพยากร Dependency ภายนอกมีเวอร์ชันต่างๆ และเวอร์ชันเหล่านั้นมีอยู่อย่างอิสระจาก ซอร์สโค้ดของโครงการ
การจัดการทรัพยากร Dependency อัตโนมัติเทียบกับการจัดการทรัพยากรด้วยตนเอง
ระบบบิลด์อนุญาตให้จัดการเวอร์ชันทรัพยากร Dependency ภายนอกได้
ด้วยตนเองหรือโดยอัตโนมัติ เมื่อจัดการด้วยตนเอง ไฟล์บิลด์
ระบุเวอร์ชันที่ต้องการดาวน์โหลดจากที่เก็บอาร์ติแฟกต์อย่างชัดเจน
มักใช้สตริงเวอร์ชันเชิงความหมาย เช่น
ด้วยชื่อ 1.1.4
เมื่อจัดการโดยอัตโนมัติ ไฟล์แหล่งที่มาจะระบุช่วงของ
เวอร์ชันที่ยอมรับได้ และระบบบิลด์จะดาวน์โหลดเวอร์ชันล่าสุดเสมอ ตัวอย่างเช่น Gradle อนุญาตให้ประกาศเวอร์ชันของ Dependency เป็น "1.+" เพื่อระบุว่ายอมรับ Dependency เวอร์ชันย่อยหรือเวอร์ชันแพตช์ใดก็ได้ ตราบใดที่เวอร์ชันหลักคือ 1
Dependency ที่มีการจัดการโดยอัตโนมัติอาจสะดวกสำหรับโปรเจ็กต์ขนาดเล็ก แต่มักเป็นสาเหตุของปัญหาในโปรเจ็กต์ขนาดใหญ่หรือโปรเจ็กต์ที่มีวิศวกรมากกว่า 1 คนดำเนินการ ปัญหาเกี่ยวกับ โดยอัตโนมัติ ทรัพยากร Dependency ที่จัดการคือ คุณไม่สามารถควบคุมได้ว่าเมื่อใดที่จะ อัปเดตแล้ว ไม่มีวิธีใดรับประกันได้ว่าบุคคลภายนอกจะไม่ทำการละเมิด (แม้ว่าพวกเขาจะอ้างว่าใช้การกำหนดเวอร์ชันทางอรรถศาสตร์ก็ตาม) ดังนั้นบิลด์ที่ ทำงานวันหนึ่งอาจเสียหายในวันถัดไป โดยไม่มีวิธีง่ายที่จะตรวจสอบสิ่งที่เปลี่ยนแปลงไป หรือเพื่อย้อนกลับไปยังสถานะใช้งานได้ แม้ว่าบิลด์จะไม่ขัดข้อง แต่ลักษณะการทำงานหรือการเปลี่ยนแปลงประสิทธิภาพเล็กๆ น้อยๆ ก็อาจเกิดขึ้นได้ ซึ่งจะติดตามได้ยาก
ในทางตรงกันข้าม เนื่องจากต้องเปลี่ยนแปลงตัวควบคุมแหล่งที่มาจึงจะค้นพบและเปลี่ยนกลับได้ง่าย และสามารถตรวจสอบที่เก็บเวอร์ชันเก่าเพื่อสร้างโดยใช้ข้อกำหนดเบื้องต้นเวอร์ชันเก่าได้ Bazel กำหนดให้ต้องระบุเวอร์ชันของทรัพยากร Dependency ทั้งหมดด้วยตนเอง ที่เวลา ระดับปานกลาง ค่าใช้จ่ายในการจัดการเวอร์ชันด้วยตนเองที่คุ้มค่า ความเสถียรที่มีให้
กฎเวอร์ชันเดียว
ไลบรารีเวอร์ชันต่างๆ มักจะแสดงด้วยอาร์ติแฟกต์ที่แตกต่างกัน ดังนั้น ตามทฤษฎีแล้ว จึงไม่มีเหตุผลที่ทำให้เวอร์ชันต่างๆ ของเนื้อหาภายนอก ไม่สามารถประกาศ Dependency ทั้ง 2 อย่างในระบบบิลด์ด้วยชื่อที่ต่างกันได้ วิธีนี้จะช่วยให้เป้าหมายแต่ละรายการเลือกเวอร์ชันของทรัพยากร Dependency ที่ต้องการ ซึ่งทำให้เกิดปัญหามากมายในทางปฏิบัติ Google จึงบังคับใช้กฎเวอร์ชันเดียวอย่างเข้มงวดสำหรับไลบรารีของบุคคลที่สามทั้งหมดในโค้ดเบสของเรา
ปัญหาที่ใหญ่ที่สุดในการอนุญาตให้มีหลายเวอร์ชันคือทรัพยากร Dependency แบบ Diamond ปัญหา สมมติว่าเป้าหมาย A ขึ้นอยู่กับเป้าหมาย B และ v1 ของเป้าหมายภายนอก ไลบรารี หากเป้าหมาย B มีการเปลี่ยนโครงสร้างภายในโค้ดในภายหลังเพื่อเพิ่มทรัพยากร Dependency ใน v2 ของรายการเดียวกัน ไลบรารีภายนอก เป้าหมาย A จะใช้ไม่ได้ เนื่องจากตอนนี้ขึ้นอยู่กับ 2 สิ่งนี้โดยปริยาย เวอร์ชันต่างๆ ของไลบรารีเดียวกัน อย่างมีประสิทธิภาพ การใช้ ทรัพยากร Dependency ใหม่จากเป้าหมายไปยังไลบรารี ของบุคคลที่สามที่มีหลายเวอร์ชัน เนื่องจากผู้ใช้ของเป้าหมายเหล่านั้นอาจพึ่งพา เวอร์ชัน การปฏิบัติตามกฎเวอร์ชันเดียวจะทำให้ความขัดแย้งนี้ไม่เกิดขึ้น หากเป้าหมายเพิ่มการพึ่งพาไลบรารีของบุคคลที่สาม การพึ่งพาที่มีอยู่จะอยู่ในเวอร์ชันเดียวกันอยู่แล้ว ดังนั้นจึงอยู่ร่วมกันได้อย่างราบรื่น
ทรัพยากร Dependency ภายนอกแบบทรานซิทีฟ
การจัดการกับ Dependency แบบทรานซิทีฟของ Dependency ภายนอกอาจเป็นเรื่องยากเป็นพิเศษ ที่เก็บอาร์ติแฟกต์จำนวนมาก เช่น Maven Central อนุญาตให้อาร์ติแฟกต์ระบุการพึ่งพาอาร์ติแฟกต์อื่นๆ บางเวอร์ชันในที่เก็บ สร้างเครื่องมืออย่างเช่น Maven หรือ Gradle ที่ต้องทำการดาวน์โหลดซ้ำๆ การขึ้นต่อกันแบบทรานซิทีฟโดยค่าเริ่มต้น ซึ่งหมายความว่าการเพิ่มการขึ้นต่อกันเดี่ยวลงใน โปรเจ็กต์ของคุณอาจทำให้มีการดาวน์โหลดอาร์ติแฟกต์ ทั้งหมด
ซึ่งสะดวกมาก เมื่อเพิ่มการพึ่งพาในไลบรารีใหม่ คุณจะต้องติดตามการพึ่งพาแบบเปลี่ยนผ่านของไลบรารีแต่ละรายการและเพิ่มทั้งหมดด้วยตนเอง แต่ก็มีข้อเสียสำคัญอีกเช่นกัน เพราะ อาจขึ้นอยู่กับเวอร์ชันต่างๆ ของไลบรารีเดียวกันของบุคคลที่สาม จำเป็นต้องละเมิดกฎเวอร์ชันเดียวและนำไปสู่ไดมอนด์ ปัญหาการพึ่งพา หากเป้าหมายใช้ไลบรารีภายนอก 2 รายการที่ใช้ทรัพยากรเดียวกันในเวอร์ชันที่แตกต่างกัน คุณจะไม่สามารถคาดเดาได้ว่าจะได้รับเวอร์ชันใด ซึ่งหมายความว่าการอัปเดตทรัพยากร Dependency ภายนอกอาจดูเหมือน ความล้มเหลวที่ไม่เกี่ยวข้องทั่วทั้งฐานของโค้ดหากเวอร์ชันใหม่เริ่มดึงข้อมูล เวอร์ชันที่ขัดแย้งกันของทรัพยากร Dependency บางอย่าง
ด้วยเหตุนี้ Bazel จึงไม่ดาวน์โหลดข้อกำหนดเบื้องต้นแบบทรานซิทีฟโดยอัตโนมัติ
และน่าเสียดายที่ไม่มีสูตรสำเร็จ ทางเลือกของ Bazel ก็คือการมี
ไฟล์ส่วนกลางที่แสดงไฟล์ภายนอกของที่เก็บทุกรายการ
ทรัพยากร Dependency และเวอร์ชันที่ชัดเจนซึ่งใช้สำหรับทรัพยากร Dependency นั้นตลอดช่วงเวลา
ที่เก็บได้ แต่โชคดีที่ Bazel มีเครื่องมือที่สามารถสร้างไฟล์ดังกล่าวซึ่งมีทรัพยากร Maven ชุดหนึ่งๆ ที่ต้องพึ่งพากันโดยอ้อมโดยอัตโนมัติ เครื่องมือนี้สามารถเรียกใช้ได้เพียงครั้งเดียวเพื่อสร้างไฟล์ WORKSPACE
เริ่มต้นสําหรับโปรเจ็กต์ จากนั้นอัปเดตไฟล์ด้วยตนเองเพื่อปรับเวอร์ชันของ Dependency แต่ละรายการได้
อีกครั้ง ตัวเลือกที่นี่คือความสะดวกและความสามารถในการปรับขนาด เล็ก โปรเจ็กต์อาจไม่ต้องกังวลเกี่ยวกับการจัดการทรัพยากร Dependency แบบทรานซิทีฟ และอาจหลีกเลี่ยงการใช้ทรานซิทีฟอัตโนมัติได้ ทรัพยากร Dependency กลยุทธ์นี้ดึงดูดใจน้อยลงเรื่อยๆ เมื่อองค์กรและโค้ดเบสเติบโตขึ้น และความขัดแย้งและผลลัพธ์ที่ไม่คาดคิดก็เกิดขึ้นบ่อยขึ้น สำหรับขนาดใหญ่ การจัดการทรัพยากร Dependency ด้วยตนเองนั้นมีค่าใช้จ่ายอย่างมาก ซึ่งน้อยกว่าค่าใช้จ่ายในการจัดการกับปัญหาที่เกิดจากทรัพยากร Dependency อัตโนมัติ การจัดการ
การแคชผลการบิลด์โดยใช้ทรัพยากรภายนอก
ทรัพยากร Dependency ภายนอกมักจะให้บริการโดยบุคคลที่สามที่ปล่อย เวอร์ชันเสถียรของไลบรารี อาจจะไม่มีการให้ซอร์สโค้ด องค์กรบางแห่งอาจเลือกทำให้โค้ดบางส่วนของตนเองพร้อมใช้งานเป็นอาร์ติแฟกต์ด้วย ซึ่งจะช่วยให้โค้ดส่วนอื่นๆ สามารถใช้โค้ดเหล่านั้นเป็นบุคคลที่สามแทนการพึ่งพาภายในได้ ในทางทฤษฎีแล้วการทำเช่นนี้ช่วยเพิ่มความเร็วในการสร้างได้หากมีอาร์ติแฟกต์ สร้างได้ช้าแต่ดาวน์โหลดได้เร็ว
แต่วิธีนี้ก็ทำให้เกิดค่าใช้จ่ายและความซับซ้อนเพิ่มขึ้น กล่าวคือ ต้องมีคนทำงาน มีหน้าที่สร้างอาร์ติแฟกต์เหล่านี้ และอัปโหลดไปยัง ที่เก็บอาร์ติแฟกต์ และลูกค้าต้องดูแลให้พวกเขาติดตามข้อมูลล่าสุดอยู่เสมอ เวอร์ชันล่าสุด การแก้ไขข้อบกพร่องก็ยากขึ้นมากด้วย เนื่องจากระบบจะสร้างส่วนต่างๆ จากจุดต่างๆ ในที่เก็บ และจะไม่มีมุมมองที่สอดคล้องกันของซอร์สทรีอีกต่อไป
วิธีที่ดีกว่าในการแก้ปัญหาอาร์ติแฟกต์ที่ใช้เวลานานในการสร้างคือ ใช้ระบบรุ่นที่รองรับการแคชระยะไกลดังที่อธิบายไว้ก่อนหน้านี้ เช่น ระบบบิลด์จะบันทึกอาร์ติแฟกต์ที่ได้จากบิลด์ทั้งหมดไปยังตำแหน่งที่ตั้ง ซึ่งแชร์กับวิศวกร ดังนั้นหากนักพัฒนาซอฟต์แวร์ต้องใช้อาร์ติแฟกต์ที่ เพิ่งสร้างขึ้นโดยบุคคลอื่น ระบบบิลด์จะดาวน์โหลดโดยอัตโนมัติ แทนที่จะต้องสร้างเอง วิธีนี้ให้ประโยชน์ด้านประสิทธิภาพทั้งหมดของการใช้อาร์ติแฟกต์โดยตรง ในขณะที่ยังคงทำให้บิลด์สอดคล้องกันราวกับว่าบิลด์มาจากแหล่งที่มาเดียวกันเสมอ ซึ่งเป็นกลยุทธ์ที่ Google ใช้ภายใน และสามารถกำหนดค่า Bazel ให้ใช้แคชระยะไกลได้
ความปลอดภัยและความน่าเชื่อถือของทรัพยากร Dependency ภายนอก
ทั้งนี้ขึ้นอยู่กับอาร์ติแฟกต์จากแหล่งที่มาของบุคคลที่สามซึ่งมีความเสี่ยงอยู่แล้วโดยธรรมชาติ แหล่งที่มาของบุคคลที่สาม (เช่น ที่เก็บอาร์ติแฟกต์) อาจหยุดทำงานได้ ซึ่งจะทำให้การบิลด์ทั้งหมดหยุดชะงักหากดาวน์โหลดทรัพยากรภายนอกไม่ได้ นอกจากนี้ ยังอาจมีความเสี่ยงด้านความปลอดภัยด้วย หากระบบของบุคคลที่สามถูกผู้โจมตีบุกรุก ผู้โจมตีอาจแทนที่อาร์ติแฟกต์ที่อ้างอิงด้วยการออกแบบของตนเอง ซึ่งทำให้สามารถแทรกโค้ดที่ต้องการลงในบิลด์ได้ ปัญหาทั้ง 2 ข้อนี้สามารถบรรเทาได้โดยการทำมิเรอร์อาร์ติแฟกต์ที่คุณใช้อยู่ในเซิร์ฟเวอร์ที่คุณควบคุม และบล็อกไม่ให้ระบบบิลด์เข้าถึงที่เก็บอาร์ติแฟกต์ของบุคคลที่สาม เช่น Maven Central ข้อเสียคือ การดูแลรักษามิเรอร์เหล่านี้ต้องใช้ความพยายามและทรัพยากร ดังนั้น การเลือกว่าจะใช้หรือไม่จึงมักขึ้นอยู่กับขนาดของโปรเจ็กต์ นอกจากนี้ คุณยังป้องกันปัญหาด้านความปลอดภัยได้ทั้งหมดโดยแทบไม่สิ้นเปลืองทรัพยากรใดๆ โดยกำหนดให้ระบุแฮชของอาร์ติแฟกต์ของบุคคลที่สามแต่ละรายการในที่เก็บข้อมูลต้นทาง ซึ่งจะทำให้การบิลด์ล้มเหลวหากมีการดัดแปลงอาร์ติแฟกต์ อีกทางเลือกหนึ่งที่จะหลีกเลี่ยงปัญหานี้ได้อย่างสมบูรณ์คือการทำให้ทรัพยากรของโปรเจ็กต์เป็นเวอร์ชันที่จำหน่าย เมื่อโปรเจ็กต์ใช้ไลบรารีจากผู้ให้บริการ ระบบจะตรวจสอบไลบรารีเหล่านั้นในระบบควบคุมแหล่งที่มาควบคู่ไปกับซอร์สโค้ดของโปรเจ็กต์ ไม่ว่าจะเป็นแหล่งที่มาหรือเป็นไบนารี ซึ่งหมายความว่า
ทรัพยากร Dependency ภายนอกทั้งหมดของโปรเจ็กต์ได้แปลงเป็นภายใน
ทรัพยากร Dependency Google ใช้แนวทางนี้เป็นการภายใน โดยตรวจสอบบุคคลที่สามทุกราย
ไลบรารีที่อ้างอิงทั่วทั้ง Google ไปยังไดเรกทอรี third_party
ที่รูท
โครงสร้างแหล่งที่มาของ Google อย่างไรก็ตาม วิธีนี้ได้ผลที่ Google เพียงเพราะว่า
ระบบควบคุมแหล่งที่มานั้นสร้างขึ้นเอง เพื่อรองรับการผูกขาดที่มีขนาดใหญ่มาก
ผู้ให้บริการอาจไม่ใช่ตัวเลือกสำหรับองค์กรบางแห่ง