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

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

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

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, दोनों को उससे लिंक किया जाएगा. इसलिए, 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, ट्रांज़िटिव डिपेंडेंसी को अपने-आप डाउनलोड नहीं करता. दुर्भाग्य से, इसका कोई आसान समाधान नहीं है. Bazel के विकल्प के तौर पर, एक ग्लोबल फ़ाइल की ज़रूरत होती है. इस फ़ाइल में, रिपॉज़िटरी की हर एक्सटर्नल डिपेंडेंसी और उस डिपेंडेंसी के लिए इस्तेमाल किया गया साफ़ तौर पर तय वर्शन शामिल होता है. Bazel, ऐसे टूल उपलब्ध कराता है जो Maven आर्टफ़ैक्ट के सेट की ट्रांज़िटिव डिपेंडेंसी वाली ऐसी फ़ाइल को अपने-आप जनरेट कर सकते हैं. किसी प्रोजेक्ट के लिए, शुरुआती WORKSPACE फ़ाइल जनरेट करने के लिए, इस टूल को एक बार चलाया जा सकता है. इसके बाद, हर डिपेंडेंसी के वर्शन को अडजस्ट करने के लिए, उस फ़ाइल को मैन्युअल तरीके से अपडेट किया जा सकता है.

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

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

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

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

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

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

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