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

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

ทรัพยากร Dependency แบบข้ามผ่าน

รูปที่ 1 ทรัพยากร Dependency แบบข้ามผ่าน

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

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

ตามปกติแล้ว การบังคับใช้ทรัพยากร Dependency แบบทรานซิทีฟแบบเข้มงวดก็จะต้องลดปัญหาลง ทำให้สร้างไฟล์ได้ละเอียดขึ้น เนื่องจากตอนนี้ไลบรารีที่ใช้บ่อยต้องแสดงอย่างชัดเจนในหลายตำแหน่ง แทนที่จะดึงขึ้นมาเอง และวิศวกรก็ต้องใช้ความพยายามมากขึ้นในการเพิ่มทรัพยากร Dependency ไปยังไฟล์ BUILD เราได้พัฒนาเครื่องมือที่ช่วยลดการทำงานหนักนี้โดยการตรวจหาทรัพยากร Dependency ที่ขาดหายไปโดยอัตโนมัติและเพิ่มการพึ่งพิงดังกล่าวลงในไฟล์ BUILD โดยไม่ต้องให้นักพัฒนาซอฟต์แวร์เข้าไปแก้ไขเลย แต่ถึงแม้จะไม่มีเครื่องมือดังกล่าว แต่เราก็พบว่าข้อดีข้อเสียนั้นเหมาะสมเนื่องจาก Codebase สามารถปรับขนาดได้ กล่าวคือ การเพิ่มทรัพยากร 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 ภายนอกเดียวกันทั้งสองเวอร์ชันในระบบบิลด์ภายใต้ชื่อที่แตกต่างกันได้ วิธีนี้จะช่วยให้เป้าหมายแต่ละรายการเลือกเวอร์ชันของทรัพยากร Dependency ที่ต้องการใช้ เรื่องนี้ทำให้เกิดปัญหาอย่างมากในทางปฏิบัติ Google จึงบังคับใช้กฎเวอร์ชันเดียวที่เข้มงวดสำหรับทรัพยากร Dependency ของบุคคลที่สามทั้งหมดในฐานของโค้ด

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

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

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

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

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