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

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

การอ้างอิงภายนอกแบบทรานซิทีฟ

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

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

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

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

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

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

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

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

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

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