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

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

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

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

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

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

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

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

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

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

รูปที่ 1 ทรัพยากร Dependency แบบส่งผ่าน

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

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

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

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

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

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

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

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

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

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

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

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

ทรัพยากร Dependency ภายนอกแบบส่งผ่าน

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

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

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

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

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

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

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

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

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

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