डिपेंडेंसी मैनेजमेंट

पिछले पेजों को देखने पर, एक थीम बार-बार दोहराई जाती है: अपना कोड मैनेज करना काफ़ी आसान है, लेकिन इसकी डिपेंडेंसी मैनेज करना ज़्यादा मुश्किल है. डिपेंडेंसी कई तरह की होती हैं: कभी-कभी किसी टास्क पर डिपेंडेंसी होती है. जैसे, "रिलीज़ को पूरा के तौर पर मार्क करने से पहले, दस्तावेज़ को पुश करें". वहीं, कभी-कभी किसी आर्टफ़ैक्ट पर डिपेंडेंसी होती है. जैसे, "मुझे अपना कोड बनाने के लिए, कंप्यूटर विज़न लाइब्रेरी का सबसे नया वर्शन चाहिए". कभी-कभी, आपके कोडबेस के किसी दूसरे हिस्से पर इंटरनल डिपेंडेंसी होती हैं. वहीं, कभी-कभी किसी दूसरी टीम (आपके संगठन या तीसरे पक्ष) के मालिकाना हक वाले कोड या डेटा पर एक्सटर्नल डिपेंडेंसी होती हैं. हालांकि, किसी भी मामले में, "मुझे यह चाहिए, तभी मुझे यह मिल सकता है" वाली बात, बिल्ड सिस्टम के डिज़ाइन में बार-बार सामने आती है. साथ ही, डिपेंडेंसी मैनेज करना, शायद बिल्ड सिस्टम का सबसे बुनियादी काम है.

मॉड्यूल और डिपेंडेंसी मैनेज करना

Bazel जैसे आर्टफ़ैक्ट पर आधारित बिल्ड सिस्टम का इस्तेमाल करने वाले प्रोजेक्ट को, मॉड्यूल के सेट में बांटा जाता है. इनमें, मॉड्यूल, BUILD फ़ाइलों के ज़रिए एक-दूसरे पर डिपेंडेंसी दिखाते हैं. इन मॉड्यूल और डिपेंडेंसी को सही तरीके से व्यवस्थित करने से, बिल्ड सिस्टम की परफ़ॉर्मेंस और इसे बनाए रखने में लगने वाले काम, दोनों पर काफ़ी असर पड़ सकता है.

फ़ाइन-ग्रेन्ड मॉड्यूल और 1:1:1 नियम का इस्तेमाल करना

आर्टफ़ैक्ट पर आधारित बिल्ड को स्ट्रक्चर करते समय, पहला सवाल यह होता है कि किसी मॉड्यूल में कितनी फ़ंक्शनैलिटी शामिल होनी चाहिए. Bazel में, एक मॉड्यूल को टारगेट के तौर पर दिखाया जाता है. यह टारगेट, java_library या go_binary जैसी बिल्ड की जा सकने वाली यूनिट तय करता है. एक मामले में, पूरे प्रोजेक्ट को एक मॉड्यूल में शामिल किया जा सकता है. इसके लिए, रूट में एक BUILD फ़ाइल डालकर, उस प्रोजेक्ट की सभी सोर्स फ़ाइलों को एक साथ ग्लोब किया जा सकता है. दूसरे मामले में, लगभग हर सोर्स फ़ाइल को उसके मॉड्यूल में बनाया जा सकता है. इसके लिए, हर फ़ाइल को BUILD फ़ाइल में, उन सभी फ़ाइलों की सूची शामिल करनी होगी जिन पर वह डिपेंड करती है.

ज़्यादातर प्रोजेक्ट, इन दोनों मामलों के बीच में कहीं होते हैं. साथ ही, इस मामले में परफ़ॉर्मेंस और रखरखाव के बीच समझौता करना पड़ता है. पूरे प्रोजेक्ट के लिए एक मॉड्यूल का इस्तेमाल करने का मतलब है कि आपको एक्सटर्नल डिपेंडेंसी जोड़ने के अलावा, BUILD फ़ाइल में कभी भी बदलाव करने की ज़रूरत नहीं पड़ेगी. हालांकि, इसका मतलब यह भी है कि बिल्ड सिस्टम को हमेशा पूरे प्रोजेक्ट को एक साथ बिल्ड करना होगा. इसका मतलब है कि यह बिल्ड के हिस्सों को पैरललाइज़ या डिस्ट्रिब्यूट नहीं कर पाएगा. साथ ही, यह उन हिस्सों को कैश नहीं कर पाएगा जिन्हें पहले ही बिल्ड किया जा चुका है. हर फ़ाइल के लिए एक मॉड्यूल, इसका उल्टा है: बिल्ड सिस्टम के पास, बिल्ड के चरणों को कैश करने और शेड्यूल करने में ज़्यादा से ज़्यादा फ़्लेक्सिबिलिटी होती है. हालांकि, इंजीनियरों को डिपेंडेंसी की सूची बनाए रखने के लिए ज़्यादा मेहनत करनी पड़ती है. ऐसा तब करना पड़ता है, जब वे यह बदलते हैं कि कौनसी फ़ाइलें, किन फ़ाइलों को रेफ़र करती हैं.

हालांकि, सटीक ग्रैन्युलैरिटी, भाषा के हिसाब से अलग-अलग होती है. साथ ही, अक्सर एक ही भाषा में भी अलग-अलग होती है. Google, टास्क पर आधारित बिल्ड सिस्टम में आम तौर पर लिखे जाने वाले मॉड्यूल की तुलना में, काफ़ी छोटे मॉड्यूल इस्तेमाल करता है. Google में, आम तौर पर प्रोडक्शन बाइनरी, अक्सर दसियों हज़ार टारगेट पर डिपेंड करती है. वहीं, सामान्य साइज़ वाली टीम भी अपने कोडबेस में, सैकड़ों टारगेट की मालिक हो सकती है. Java जैसी भाषाओं के लिए, जिनमें पैकेजिंग की सुविधा पहले से मौजूद होती है, हर डायरेक्ट्री में आम तौर पर एक पैकेज, टारगेट, और BUILD फ़ाइल होती है. Bazel पर आधारित एक अन्य बिल्ड सिस्टम, Pants इसे 1:1:1 नियम कहता है. कमज़ोर पैकेजिंग कन्वेंशन वाली भाषाएं, अक्सर हर BUILD फ़ाइल के लिए कई टारगेट तय करती हैं.

छोटे बिल्ड टारगेट के फ़ायदे, बड़े पैमाने पर दिखने लगते हैं. ऐसा इसलिए, क्योंकि इनसे डिस्ट्रिब्यूट किए गए बिल्ड तेज़ी से होते हैं और टारगेट को फिर से बिल्ड करने की ज़रूरत कम पड़ती है. टेस्टिंग शुरू होने के बाद, इसके फ़ायदे और भी ज़्यादा अहम हो जाते हैं. ऐसा इसलिए, क्योंकि फ़ाइनर-ग्रेन्ड टारगेट का मतलब है कि बिल्ड सिस्टम, सिर्फ़ टेस्ट के सीमित सबसेट को चलाने के बारे में ज़्यादा स्मार्ट हो सकता है. इन सबसेट पर, किसी भी बदलाव का असर पड़ सकता है. Google का मानना है कि छोटे टारगेट का इस्तेमाल करने से, सिस्टम को फ़ायदे मिलते हैं. इसलिए, हमने डेवलपर पर बोझ न डालने के लिए, BUILD फ़ाइलों को अपने-आप मैनेज करने वाले टूल में निवेश करके, इसके नुकसान को कम करने की कोशिश की है.

buildifier और buildozer जैसे इनमें से कुछ टूल, Bazel के साथ buildtoolsडायरेक्ट्री में उपलब्ध हैं.

मॉड्यूल की विज़िबिलिटी कम करना

Bazel और अन्य बिल्ड सिस्टम, हर टारगेट के लिए विज़िबिलिटी तय करने की अनुमति देते हैं. यह एक ऐसी प्रॉपर्टी है जिससे यह तय होता है कि अन्य कौनसे टारगेट इस पर डिपेंड कर सकते हैं. प्राइवेट टारगेट को सिर्फ़ उसकी BUILD फ़ाइल में रेफ़र किया जा सकता है. कोई टारगेट, BUILD फ़ाइलों की साफ़ तौर पर तय की गई सूची के टारगेट को ज़्यादा विज़िबिलिटी दे सकता है. वहीं, सार्वजनिक विज़िबिलिटी के मामले में, यह वर्कस्पेस के हर टारगेट को विज़िबिलिटी दे सकता है.

ज़्यादातर प्रोग्रामिंग भाषाओं की तरह, आम तौर पर विज़िबिलिटी को जितना हो सके उतना कम रखना सबसे अच्छा होता है. आम तौर पर, Google में टीमें, टारगेट को सार्वजनिक तब ही बनाती हैं, जब वे टारगेट, Google की किसी भी टीम के लिए उपलब्ध, बड़े पैमाने पर इस्तेमाल की जाने वाली लाइब्रेरी को दिखाते हैं. जिन टीमों को अपने कोड का इस्तेमाल करने से पहले, दूसरों के साथ कोऑर्डिनेट करने की ज़रूरत होती है वे ग्राहक टारगेट की अनुमति वाली सूची को, अपने टारगेट की विज़िबिलिटी के तौर पर बनाए रखेंगी. हर टीम के इंटरनल इंप्लीमेंटेशन टारगेट, सिर्फ़ उन डायरेक्ट्री तक सीमित रहेंगे जिनका मालिकाना हक टीम के पास है. साथ ही, ज़्यादातर BUILD फ़ाइलों में सिर्फ़ एक ऐसा टारगेट होगा जो प्राइवेट नहीं है.

डिपेंडेंसी मैनेज करना

मॉड्यूल एक-दूसरे को रेफ़र कर सकते हैं. कोडबेस को फ़ाइन-ग्रेन्ड मॉड्यूल में बांटने का नुकसान यह है कि आपको उन मॉड्यूल के बीच डिपेंडेंसी मैनेज करनी पड़ती है. हालांकि, टूल की मदद से इसे ऑटोमेट किया जा सकता है. इन डिपेंडेंसी को दिखाने के लिए, आम तौर पर BUILD फ़ाइल में ज़्यादातर कॉन्टेंट शामिल होता है.

इंटरनल डिपेंडेंसी

फ़ाइन-ग्रेन्ड मॉड्यूल में बांटे गए बड़े प्रोजेक्ट में, ज़्यादातर डिपेंडेंसी इंटरनल होती हैं. इसका मतलब है कि ये डिपेंडेंसी, उसी सोर्स रिपॉज़िटरी में तय और बिल्ड किए गए किसी अन्य टारगेट पर होती हैं. इंटरनल डिपेंडेंसी, एक्सटर्नल डिपेंडेंसी से इस मामले में अलग होती हैं कि इन्हें सोर्स से बिल्ड किया जाता है. वहीं, बिल्ड को रन करते समय, एक्सटर्नल डिपेंडेंसी को पहले से बिल्ड किए गए आर्टफ़ैक्ट के तौर पर डाउनलोड किया जाता है. इसका मतलब यह भी है कि इंटरनल डिपेंडेंसी के लिए "वर्शन" का कोई कॉन्सेप्ट नहीं है. रिपॉज़िटरी में, किसी टारगेट और उसकी सभी इंटरनल डिपेंडेंसी को हमेशा एक ही कमिट/रिविज़न पर बिल्ड किया जाता है. इंटरनल डिपेंडेंसी के मामले में, जिस समस्या को ध्यान से हैंडल किया जाना चाहिए वह यह है कि ट्रांज़िटिव डिपेंडेंसी को कैसे मैनेज किया जाए (पहली इमेज). मान लें कि टारगेट A, टारगेट B पर डिपेंड करता है. वहीं, टारगेट B, सामान्य लाइब्रेरी टारगेट C पर डिपेंड करता है. क्या टारगेट A, टारगेट C में तय की गई क्लास का इस्तेमाल कर पाएगा?

ट्रांज़िटिव डिपेंडेंसी

पहली इमेज. ट्रांज़िटिव डिपेंडेंसी

अंडरलाइन टूल के मामले में, इसमें कोई समस्या नहीं है. टारगेट A को बिल्ड करने पर, B और C, दोनों को टारगेट A से लिंक किया जाएगा. इसलिए, C में तय किए गए सभी सिंबल, A को पता होते हैं. Bazel ने कई सालों तक इसकी अनुमति दी. हालांकि, Google के बढ़ने के साथ-साथ, हमें समस्याएं दिखने लगीं. मान लें कि B को इस तरह से रीफ़ैक्टर किया गया कि अब उसे C पर डिपेंड करने की ज़रूरत नहीं है. अगर B की C पर डिपेंडेंसी हटा दी जाती है, तो A और कोई अन्य टारगेट जो B पर डिपेंडेंसी के ज़रिए C का इस्तेमाल करता है वह काम नहीं करेगा. असल में, किसी टारगेट की डिपेंडेंसी, उसके सार्वजनिक कॉन्ट्रैक्ट का हिस्सा बन जाती हैं और उन्हें कभी भी सुरक्षित तरीके से बदला नहीं जा सकता. इसका मतलब है कि समय के साथ-साथ डिपेंडेंसी बढ़ती गईं और Google में बिल्ड की स्पीड कम होने लगी.

Google ने Bazel में "स्ट्रिक्ट ट्रांज़िटिव डिपेंडेंसी मोड" जोड़कर, इस समस्या को हल किया. इस मोड में, Bazel यह पता लगाता है कि कोई टारगेट, सीधे तौर पर उस पर डिपेंड किए बिना किसी सिंबल को रेफ़र करने की कोशिश करता है या नहीं. अगर ऐसा होता है, तो यह गड़बड़ी और शेल कमांड के साथ फ़ेल हो जाता है. इस कमांड का इस्तेमाल करके, डिपेंडेंसी को अपने-आप जोड़ा जा सकता है. Google के पूरे कोडबेस में इस बदलाव को रोल आउट करना और बिल्ड के लाखों टारगेट में से हर एक को रीफ़ैक्टर करके, उनकी डिपेंडेंसी को साफ़ तौर पर लिस्ट करना, कई सालों की मेहनत का नतीजा है. हालांकि, यह मेहनत रंग लाई. अब हमारे बिल्ड की स्पीड काफ़ी बढ़ गई है, क्योंकि टारगेट में गैर-ज़रूरी डिपेंडेंसी कम हैं. साथ ही, इंजीनियर उन डिपेंडेंसी को हटा सकते हैं जिनकी उन्हें ज़रूरत नहीं है. इसके लिए, उन्हें उन टारगेट के काम न करने की चिंता करने की ज़रूरत नहीं है जो उन पर डिपेंड करते हैं.

हमेशा की तरह, स्ट्रिक्ट ट्रांज़िटिव डिपेंडेंसी लागू करने में, समझौता करना पड़ा. इससे बिल्ड फ़ाइलें ज़्यादा वर्बोस हो गईं, क्योंकि अक्सर इस्तेमाल की जाने वाली लाइब्रेरी को अब कई जगहों पर साफ़ तौर पर लिस्ट करना पड़ता है. वहीं, इंजीनियरों को BUILD फ़ाइलों में डिपेंडेंसी जोड़ने के लिए ज़्यादा मेहनत करनी पड़ी. इसके बाद से, हमने ऐसे टूल डेवलप किए हैं जिनसे इस मेहनत को कम किया जा सकता है. ये टूल, डेवलपर के किसी भी इंटरवेंशन के बिना, कई डिपेंडेंसी को अपने-आप ढूंढकर, उन्हें BUILD फ़ाइलों में जोड़ देते हैं. हालांकि, ऐसे टूल के बिना भी, हमने पाया है कि कोडबेस के स्केल होने पर, यह समझौता फ़ायदेमंद है. BUILD फ़ाइल में साफ़ तौर पर डिपेंडेंसी जोड़ना, एक बार की लागत है. वहीं, इंप्लिसिट ट्रांज़िटिव डिपेंडेंसी से जुड़ी समस्याओं से, बिल्ड टारगेट के मौजूद रहने तक लगातार समस्याएं हो सकती हैं. Bazel डिफ़ॉल्ट रूप से Java कोड पर स्ट्रिक्ट ट्रांज़िटिव डिपेंडेंसी लागू करता है.

एक्सटर्नल डिपेंडेंसी

अगर कोई डिपेंडेंसी इंटरनल नहीं है, तो वह एक्सटर्नल होनी चाहिए. एक्सटर्नल डिपेंडेंसी, उन आर्टफ़ैक्ट पर होती हैं जिन्हें बिल्ड सिस्टम के बाहर बिल्ड और सेव किया जाता है. डिपेंडेंसी को सीधे तौर पर आर्टफ़ैक्ट रिपॉज़िटरी (आम तौर पर इंटरनेट से ऐक्सेस की जाती है) से इंपोर्ट किया जाता है. साथ ही, इसे सोर्स से बिल्ड करने के बजाय, उसी तरह इस्तेमाल किया जाता है. एक्सटर्नल और इंटरनल डिपेंडेंसी के बीच सबसे बड़े अंतरों में से एक यह है कि एक्सटर्नल डिपेंडेंसी के वर्शन होते हैं. साथ ही, ये वर्शन, प्रोजेक्ट के सोर्स कोड से अलग होते हैं.

डिपेंडेंसी मैनेजमेंट को ऑटोमेट करना बनाम मैन्युअल तरीके से मैनेज करना

बिल्ड सिस्टम, एक्सटर्नल डिपेंडेंसी के वर्शन को मैन्युअल या ऑटोमेट तरीके से मैनेज करने की अनुमति दे सकते हैं. मैन्युअल तरीके से मैनेज करने पर, बिल्डफ़ाइल में साफ़ तौर पर वह वर्शन लिस्ट किया जाता है जिसे आर्टफ़ैक्ट रिपॉज़िटरी से डाउनलोड करना है, इसके लिए, अक्सर सिमेंटिक वर्शन स्ट्रिंग का इस्तेमाल किया जाता है. जैसे 1.1.4. ऑटोमेट तरीके से मैनेज करने पर, सोर्स फ़ाइल में स्वीकार किए जा सकने वाले वर्शन की रेंज तय की जाती है. साथ ही, बिल्ड सिस्टम हमेशा सबसे नया वर्शन डाउनलोड करता है. उदाहरण के लिए, Gradle, डिपेंडेंसी वर्शन को "1.+" के तौर पर तय करने की अनुमति देता है. इससे यह तय होता है कि डिपेंडेंसी का कोई भी माइनर या पैच वर्शन स्वीकार किया जा सकता है, बशर्ते कि मेजर वर्शन 1 हो.

ऑटोमेट तरीके से मैनेज की गई डिपेंडेंसी, छोटे प्रोजेक्ट के लिए सुविधाजनक हो सकती हैं. हालांकि, ये आम तौर पर उन प्रोजेक्ट के लिए मुसीबत बन जाती हैं जिनका साइज़ बड़ा होता है या जिन पर एक से ज़्यादा इंजीनियर काम कर रहे होते हैं. ऑटोमेट तरीके से मैनेज की गई डिपेंडेंसी की समस्या यह है कि आपके पास यह कंट्रोल नहीं होता कि वर्शन कब अपडेट होगा. इस बात की कोई गारंटी नहीं है कि बाहरी पक्ष, ब्रेक करने वाले अपडेट नहीं करेंगे. भले ही, वे सिमेंटिक वर्शनिंग का इस्तेमाल करने का दावा करें. इसलिए, एक दिन काम करने वाला बिल्ड, अगले दिन काम नहीं कर सकता. साथ ही, यह पता लगाना मुश्किल होता है कि क्या बदला है या इसे वापस काम करने वाली स्थिति में कैसे लाया जाए. भले ही, बिल्ड काम करना बंद न करे, लेकिन इसमें मामूली बदलाव या परफ़ॉर्मेंस में बदलाव हो सकते हैं जिन्हें ट्रैक करना मुश्किल होता है.

इसके उलट, मैन्युअल तरीके से मैनेज की गई डिपेंडेंसी के लिए, सोर्स कंट्रोल में बदलाव की ज़रूरत होती है. इसलिए, इन्हें आसानी से खोजा और वापस लाया जा सकता है. साथ ही, पुरानी डिपेंडेंसी के साथ बिल्ड करने के लिए, रिपॉज़िटरी का पुराना वर्शन चेक आउट किया जा सकता है. Bazel के लिए ज़रूरी है कि सभी डिपेंडेंसी के वर्शन को मैन्युअल तरीके से तय किया जाए. सामान्य स्केल पर भी, मैन्युअल तरीके से वर्शन मैनेजमेंट का ओवरहेड, इससे मिलने वाली स्थिरता के लिए फ़ायदेमंद है.

एक वर्शन का नियम

किसी लाइब्रेरी के अलग-अलग वर्शन को आम तौर पर अलग-अलग आर्टफ़ैक्ट के तौर पर दिखाया जाता है. इसलिए, सिद्धांत के तौर पर, एक ही एक्सटर्नल डिपेंडेंसी के अलग-अलग वर्शन को, बिल्ड सिस्टम में अलग-अलग नामों से तय करने की कोई वजह नहीं है. इस तरह, हर टारगेट यह चुन सकता है कि उसे डिपेंडेंसी का कौनसा वर्शन इस्तेमाल करना है. इससे असल में कई समस्याएं होती हैं. इसलिए, Google अपने कोडबेस में, तीसरे पक्ष की सभी डिपेंडेंसी के लिए, एक सख्त वर्शन का नियम लागू करता है.

कई वर्शन की अनुमति देने की सबसे बड़ी समस्या, डायमंड डिपेंडेंसी की समस्या है. मान लें कि टारगेट A, टारगेट B और किसी एक्सटर्नल लाइब्रेरी के v1 पर डिपेंड करता है. अगर बाद में टारगेट B को रीफ़ैक्टर करके, उसी एक्सटर्नल लाइब्रेरी के v2 पर डिपेंडेंसी जोड़ी जाती है, तो टारगेट A काम नहीं करेगा. ऐसा इसलिए, क्योंकि अब यह उसी लाइब्रेरी के दो अलग-अलग वर्शन पर इंप्लिसिट तौर पर डिपेंड करता है. असल में, किसी टारगेट से, कई वर्शन वाली किसी भी तीसरे पक्ष की लाइब्रेरी में नई डिपेंडेंसी जोड़ना कभी भी सुरक्षित नहीं होता. ऐसा इसलिए, क्योंकि उस टारगेट के किसी भी उपयोगकर्ता के पास, पहले से ही कोई दूसरा वर्शन हो सकता है. एक वर्शन के नियम का पालन करने से, यह टकराव नहीं हो सकता. अगर कोई टारगेट, तीसरे पक्ष की लाइब्रेरी पर डिपेंडेंसी जोड़ता है, तो मौजूदा सभी डिपेंडेंसी पहले से ही उसी वर्शन पर होंगी. इसलिए, वे एक साथ काम कर सकती हैं.

ट्रांज़िटिव एक्सटर्नल डिपेंडेंसी

किसी एक्सटर्नल डिपेंडेंसी की ट्रांज़िटिव डिपेंडेंसी को मैनेज करना, खास तौर पर मुश्किल हो सकता है. Maven Central जैसी कई आर्टफ़ैक्ट रिपॉज़िटरी, आर्टफ़ैक्ट को रिपॉज़िटरी में मौजूद अन्य आर्टफ़ैक्ट के खास वर्शन पर डिपेंडेंसी तय करने की अनुमति देती हैं. Maven या Gradle जैसे बिल्ड टूल, अक्सर डिफ़ॉल्ट रूप से हर ट्रांज़िटिव डिपेंडेंसी को बार-बार डाउनलोड करते हैं. इसका मतलब है कि आपके प्रोजेक्ट में एक डिपेंडेंसी जोड़ने से, कुल मिलाकर दर्जनों आर्टफ़ैक्ट डाउनलोड हो सकते हैं.

यह बहुत सुविधाजनक है: नई लाइब्रेरी पर डिपेंडेंसी जोड़ते समय, उस लाइब्रेरी की हर ट्रांज़िटिव डिपेंडेंसी को ट्रैक करना और उन सभी को मैन्युअल तरीके से जोड़ना, बहुत मुश्किल होगा. हालांकि, इसका एक बड़ा नुकसान भी है: अलग-अलग लाइब्रेरी, तीसरे पक्ष की एक ही लाइब्रेरी के अलग-अलग वर्शन पर डिपेंड कर सकती हैं. इसलिए, यह रणनीति, एक वर्शन के नियम का उल्लंघन करती है और इससे डायमंड डिपेंडेंसी की समस्या होती है. अगर आपका टारगेट, दो एक्सटर्नल लाइब्रेरी पर डिपेंड करता है जो एक ही डिपेंडेंसी के अलग-अलग वर्शन का इस्तेमाल करती हैं, तो यह नहीं कहा जा सकता कि आपको कौनसा वर्शन मिलेगा. इसका मतलब यह भी है कि एक्सटर्नल डिपेंडेंसी को अपडेट करने से, पूरे कोडबेस में ऐसी गड़बड़ियां हो सकती हैं जो देखने में एक-दूसरे से जुड़ी नहीं होतीं. ऐसा तब होता है, जब नया वर्शन, अपनी कुछ डिपेंडेंसी के टकराव वाले वर्शन को पुल करना शुरू कर देता है.

Bazel, ट्रांज़िटिव डिपेंडेंसी को अपने-आप डाउनलोड नहीं करता था. यह WORKSPACE फ़ाइल का इस्तेमाल करता था. इसके लिए, सभी ट्रांज़िटिव डिपेंडेंसी को लिस्ट करना ज़रूरी था. इससे, एक्सटर्नल डिपेंडेंसी मैनेज करते समय काफ़ी परेशानी होती थी. Bazel ने अब MODULE.bazel फ़ाइल के तौर पर, ट्रांज़िटिव एक्सटर्नल डिपेंडेंसी को ऑटोमेट तरीके से मैनेज करने की सुविधा जोड़ दी है. ज़्यादा जानकारी के लिए, एक्सटर्नल डिपेंडेंसी की खास जानकारी देखें.

यहां भी, सुविधा और स्केलेबिलिटी के बीच चुनाव करना होता है. छोटे प्रोजेक्ट, ट्रांज़िटिव डिपेंडेंसी को खुद मैनेज करने की चिंता नहीं करना चाहेंगे. साथ ही, वे ट्रांज़िटिव डिपेंडेंसी को ऑटोमेट तरीके से मैनेज करने की सुविधा का इस्तेमाल कर सकते हैं. संगठन और कोडबेस के बढ़ने के साथ-साथ, यह रणनीति कम और कम आकर्षक होती जाती है. साथ ही, टकराव और अनचाहे नतीजे ज़्यादा और ज़्यादा बार सामने आते हैं. बड़े स्केल पर, डिपेंडेंसी को मैन्युअल तरीके से मैनेज करने की लागत, डिपेंडेंसी को ऑटोमेट तरीके से मैनेज करने की वजह से होने वाली समस्याओं को मैनेज करने की लागत से काफ़ी कम होती है.

एक्सटर्नल डिपेंडेंसी का इस्तेमाल करके, बिल्ड के नतीजों को कैश करना

एक्सटर्नल डिपेंडेंसी, अक्सर तीसरे पक्ष से मिलती हैं. ये तीसरे पक्ष, लाइब्रेरी के स्टेबल वर्शन रिलीज़ करते हैं. हो सकता है कि ये सोर्स कोड उपलब्ध न कराएं. कुछ संगठन, अपने कुछ कोड को आर्टफ़ैक्ट के तौर पर भी उपलब्ध करा सकते हैं. इससे, कोड के अन्य हिस्से, इंटरनल डिपेंडेंसी के बजाय, तीसरे पक्ष की डिपेंडेंसी के तौर पर उन पर डिपेंड कर सकते हैं. सैद्धांतिक तौर पर, इससे बिल्ड की स्पीड बढ़ सकती है. ऐसा तब होता है, जब आर्टफ़ैक्ट को बिल्ड करने में ज़्यादा समय लगता है, लेकिन उन्हें डाउनलोड करने में कम समय लगता है.

हालांकि, इससे काफ़ी ओवरहेड और जटिलता भी बढ़ती है: किसी को उन सभी आर्टफ़ैक्ट को बिल्ड करने और उन्हें आर्टफ़ैक्ट रिपॉज़िटरी में अपलोड करने की ज़िम्मेदारी लेनी होगी. साथ ही, क्लाइंट को यह पक्का करना होगा कि वे सबसे नए वर्शन के साथ अप-टू-डेट रहें. डीबग करना भी ज़्यादा मुश्किल हो जाता है, क्योंकि सिस्टम के अलग-अलग हिस्सों को रिपॉज़िटरी में अलग-अलग पॉइंट से बिल्ड किया गया होगा. साथ ही, सोर्स ट्री का कोई एक जैसा व्यू नहीं होगा.

आर्टफ़ैक्ट को बिल्ड करने में ज़्यादा समय लगने की समस्या को हल करने का एक बेहतर तरीका है कि रिमोट कैशिंग की सुविधा देने वाले बिल्ड सिस्टम का इस्तेमाल किया जाए. इसके बारे में पहले बताया जा चुका है. ऐसा बिल्ड सिस्टम, हर बिल्ड से मिलने वाले आर्टफ़ैक्ट को ऐसी जगह पर सेव करता है जिसे इंजीनियरों के साथ शेयर किया जाता है. इसलिए, अगर कोई डेवलपर, किसी ऐसे आर्टफ़ैक्ट पर डिपेंड करता है जिसे हाल ही में किसी और ने बिल्ड किया है, तो बिल्ड सिस्टम उसे बिल्ड करने के बजाय, अपने-आप डाउनलोड कर लेता है. इससे, आर्टफ़ैक्ट पर सीधे तौर पर डिपेंड करने के सभी परफ़ॉर्मेंस फ़ायदे मिलते हैं. साथ ही, यह पक्का होता है कि बिल्ड, उसी सोर्स से हमेशा बिल्ड किए जाने की तरह ही एक जैसे हों. Google, इंटरनल तौर पर इस रणनीति का इस्तेमाल करता है. साथ ही, Bazel को रिमोट कैश का इस्तेमाल करने के लिए कॉन्फ़िगर किया जा सकता है.

एक्सटर्नल डिपेंडेंसी की सुरक्षा और भरोसेमंद होना

तीसरे पक्ष के सोर्स से मिलने वाले आर्टफ़ैक्ट पर डिपेंड करना, स्वाभाविक तौर पर जोखिम भरा होता है. अगर तीसरे पक्ष का सोर्स (जैसे, आर्टफ़ैक्ट रिपॉज़िटरी) काम करना बंद कर देता है, तो उपलब्धता का जोखिम होता है. ऐसा इसलिए, क्योंकि अगर आपका बिल्ड, एक्सटर्नल डिपेंडेंसी डाउनलोड नहीं कर पाता है, तो यह पूरी तरह से रुक सकता है. सुरक्षा का जोखिम भी होता है: अगर तीसरे पक्ष के सिस्टम से कोई हमलावर समझौता करता है, तो वह रेफ़रंस किए गए आर्टफ़ैक्ट को अपने डिज़ाइन के आर्टफ़ैक्ट से बदल सकता है. इससे, वह आपके बिल्ड में कोई भी कोड इंजेक्ट कर सकता है. इन दोनों समस्याओं को कम किया जा सकता है. इसके लिए, जिन आर्टफ़ैक्ट पर आप डिपेंड करते हैं उन्हें अपने कंट्रोल वाले सर्वर पर मिरर करें. साथ ही, अपने बिल्ड सिस्टम को Maven Central जैसी तीसरे पक्ष की आर्टफ़ैक्ट रिपॉज़िटरी को ऐक्सेस करने से रोकें. हालांकि, इन मिरर को बनाए रखने के लिए मेहनत और संसाधनों की ज़रूरत होती है. इसलिए, इनका इस्तेमाल करना है या नहीं, यह अक्सर प्रोजेक्ट के स्केल पर निर्भर करता है. सुरक्षा से जुड़ी समस्या को, कम ओवरहेड के साथ पूरी तरह से रोका जा सकता है. इसके लिए, सोर्स रिपॉज़िटरी में तीसरे पक्ष के हर आर्टफ़ैक्ट का हैश तय करना ज़रूरी है. इससे, आर्टफ़ैक्ट में छेड़छाड़ होने पर, बिल्ड फ़ेल हो जाता है. एक और विकल्प है, जिससे इस समस्या से पूरी तरह से बचा जा सकता है. इसके लिए, अपने प्रोजेक्ट की डिपेंडेंसी को वेंडर करें. जब कोई प्रोजेक्ट अपनी डिपेंडेंसी को वेंडर करता है, तो वह उन्हें प्रोजेक्ट के सोर्स कोड के साथ, सोर्स या बाइनरी के तौर पर सोर्स कंट्रोल में चेक इन करता है. इसका मतलब है कि प्रोजेक्ट की सभी एक्सटर्नल डिपेंडेंसी, इंटरनल डिपेंडेंसी में बदल जाती हैं. Google, इंटरनल तौर पर इस तरीके का इस्तेमाल करता है. इसके लिए, Google के सोर्स ट्री के रूट पर मौजूद third_party डायरेक्ट्री में, Google में रेफ़रंस की गई तीसरे पक्ष की हर लाइब्रेरी को चेक इन किया जाता है. हालांकि, Google में यह तरीका सिर्फ़ इसलिए काम करता है, क्योंकि Google का सोर्स कंट्रोल सिस्टम, बहुत बड़े मोनोरिपो को हैंडल करने के लिए कस्टम तौर पर बनाया गया है. इसलिए, हो सकता है कि वेंडरिंग, सभी संगठनों के लिए कोई विकल्प न हो.