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

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

मॉड्यूल और डिपेंडेंसी से जुड़ी समस्या

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

सटीक मॉड्यूल और 1:1:1 नियम का इस्तेमाल करना

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

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

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

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

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

मॉड्यूल किसको दिखे

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

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

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

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

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

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

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

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

जहां तक बुनियादी टूल की बात है, तो इसमें कोई समस्या नहीं है. जब B और C, दोनों बन जाएंगे, तब उन्हें टारगेट A से लिंक कर दिया जाएगा. इसलिए, C में सेट किए गए चिह्नों को A कहा जाता है. Bazel ने कई सालों तक इसकी अनुमति दी, लेकिन जैसे-जैसे Google का विकास हुआ, हमें इसमें समस्याएं दिखने लगीं. मान लें कि B को इस तरह से रीफ़ैक्ट किया गया कि अब उसे C पर निर्भर रहने की ज़रूरत नहीं है. अगर C पर B की डिपेंडेंसी हटा दी जाती है, तो B पर डिपेंडेंसी के ज़रिए C का इस्तेमाल करने वाला A और कोई भी अन्य टारगेट काम नहीं करेगा. प्रभावी रूप से, टारगेट की स्वतंत्रता उसके सार्वजनिक समझौते का हिस्सा बन गई और उसे कभी भी सुरक्षित तरीके से नहीं बदला जा सका. इसका मतलब यह हुआ कि समय के साथ डिपेंडेंसी, 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 या Gradle जैसे बिल्ड टूल अक्सर डिफ़ॉल्ट रूप से, हर ट्रांज़िटिव डिपेंडेंसी को बार-बार डाउनलोड करते हैं. इसका मतलब है कि आपके प्रोजेक्ट में एक डिपेंडेंसी जोड़ने से, दर्जनों आर्टफ़ैक्ट डाउनलोड हो सकते हैं.

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

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

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

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

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

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

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

बाहरी डिपेंडेंसी की सुरक्षा और विश्वसनीयता

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