क्योंकि जीवन आरेखों को पुनः बनाने के लिए बहुत छोटा है
मैंने हाल ही में एक नई कंपनी में सॉफ्टवेयर इंजीनियर के तौर पर काम शुरू किया है। जैसा कि हमेशा होता है, मुझे शुरुआत से ही शुरुआत करनी पड़ी। जैसे कि: ऐप के लिए कोड कहाँ रहता है? इसे कैसे तैनात किया जाता है? कॉन्फ़िगरेशन कहाँ से आते हैं? शुक्र है, मेरे सहकर्मियों ने सब कुछ 'कोड के रूप में बुनियादी ढाँचा' बनाने का शानदार काम किया। इसलिए मैंने खुद को यह सोचते हुए पाया: अगर सब कुछ कोड में है, तो सभी बिंदुओं को जोड़ने के लिए कोई उपकरण क्यों नहीं है?
यह उपकरण कोडबेस की समीक्षा करेगा और मुख्य पहलुओं पर प्रकाश डालते हुए एक एप्लिकेशन आर्किटेक्चर आरेख बनाएगा। एक नया इंजीनियर आरेख को देखकर कह सकता है, "आह, ठीक है, यह इस तरह काम करता है।"
चाहे मैंने कितनी भी मेहनत की हो, मुझे ऐसा कुछ नहीं मिला। मुझे सबसे करीबी मिलान ऐसी सेवाएँ मिलीं जो एक बुनियादी ढाँचा आरेख बनाती हैं। मैंने उनमें से कुछ को इस समीक्षा में शामिल किया है ताकि आप उन्हें करीब से देख सकें। आखिरकार, मैंने गूगल करना छोड़ दिया और कुछ नए शानदार सामान विकसित करने में अपने हाथ आजमाने का फैसला किया।
सबसे पहले, मैंने Gradle, Docker और Terraform के साथ एक सैंपल Java ऐप बनाया। GitHub एक्शन पाइपलाइन ऐप को Amazon Elastic Container Service पर तैनात करती है। यह रेपो उस टूल का स्रोत होगा जिसे मैं बनाऊँगा (कोड यहाँ है)।
दूसरा, मैंने एक बहुत ही उच्च-स्तरीय आरेख बनाया जो मैं परिणाम के रूप में देखना चाहता था:
मैंने निर्णय लिया कि दो प्रकार के संसाधन होंगे:
मुझे आर्टिफैक्ट शब्द बहुत ज़्यादा भारी लगा, इसलिए मैंने रेलिक चुना। तो रेलिक क्या है? यह 90% ऐसी चीज़ है जिसे आप देखना चाहते हैं। इसमें शामिल हैं, लेकिन इन्हीं तक सीमित नहीं:
हर रेलिक का एक नाम होता है (जैसे, my-shiny-app), वैकल्पिक प्रकार (जैसे, Jar), और कुंजी → मान युग्मों का एक सेट (जैसे, पथ → /build/libs/my-shiny-app.jar) जो रेलिक का पूरी तरह से वर्णन करता है। उन्हें परिभाषाएँ कहा जाता है। रेलिक में जितनी अधिक परिभाषाएँ होंगी - उतना ही बेहतर होगा।
दूसरा प्रकार स्रोत है। स्रोत अवशेषों को परिभाषित, निर्मित या प्रावधानित करते हैं (उदाहरण के लिए, ऊपर पीले रंग के बक्से)। एक स्रोत किसी स्थान पर अवशेष का वर्णन करता है और यह बताता है कि यह कहाँ से आता है। जबकि स्रोत वे घटक हैं जिनसे हमें सबसे अधिक जानकारी मिलती है, वे आमतौर पर आरेख पर द्वितीयक अर्थ रखते हैं। आपको शायद टेराफ़ॉर्म या ग्रेडल से हर दूसरे अवशेष तक जाने के लिए बहुत सारे तीरों की आवश्यकता नहीं है।
अवशेष और स्रोत का संबंध अनेक-से-अनेक है।
कोड के हर हिस्से को कवर करना असंभव है। आधुनिक ऐप्स में कई फ्रेमवर्क, टूल या क्लाउड घटक हो सकते हैं। अकेले AWS के पास टेराफ़ॉर्म के लिए लगभग 950 संसाधन और डेटा स्रोत हैं! टूल को आसानी से विस्तार योग्य और डिज़ाइन द्वारा अलग किया जाना चाहिए ताकि अन्य लोग या कंपनियाँ योगदान दे सकें।
जबकि मैं अविश्वसनीय रूप से प्लग करने योग्य टेराफॉर्म प्रदाताओं की वास्तुकला का बहुत बड़ा प्रशंसक हूं, मैंने इसे सरलीकृत करने के बावजूद इसे बनाने का फैसला किया:
प्रदाता की एक स्पष्ट जिम्मेदारी है: अनुरोधित स्रोत फ़ाइलों के आधार पर अवशेष बनाना। उदाहरण के लिए, GradleProvider *.gradle फ़ाइलों को पढ़ता है और Jar , War , या Gz अवशेष लौटाता है। प्रत्येक प्रदाता उन प्रकारों के अवशेष बनाता है जिनके बारे में वे जानते हैं। प्रदाता अवशेषों के बीच बातचीत की परवाह नहीं करते हैं। वे अवशेषों को घोषणात्मक रूप से बनाते हैं, एक दूसरे से पूरी तरह से अलग।
इस दृष्टिकोण के साथ, आप जितना चाहें उतना गहराई से जा सकते हैं। GitHub Actions इसका एक अच्छा उदाहरण है। एक सामान्य वर्कफ़्लो YAML फ़ाइल में दर्जनों चरण होते हैं, जो शिथिल युग्मित घटकों और सेवाओं का उपयोग करते हैं। एक वर्कफ़्लो एक JAR, फिर एक Docker छवि बना सकता है, और इसे पर्यावरण में तैनात कर सकता है। वर्कफ़्लो में हर एक चरण को उसके प्रदाता द्वारा कवर किया जा सकता है। इसलिए, मान लीजिए, Docker Actions के डेवलपर्स केवल उन चरणों से संबंधित एक प्रदाता बनाते हैं जिनकी उन्हें परवाह है।
यह दृष्टिकोण किसी भी संख्या में लोगों को समानांतर रूप से काम करने की अनुमति देता है, जिससे टूल में अधिक तर्क जुड़ जाता है। अंतिम उपयोगकर्ता अपने प्रदाताओं को भी जल्दी से लागू कर सकते हैं (कुछ मालिकाना तकनीक के मामले में)। नीचे अनुकूलन के अंतर्गत अधिक देखें।
सबसे रोचक भाग में जाने से पहले आइए अगले जाल पर नज़र डालें। दो प्रदाता, जिनमें से प्रत्येक एक अवशेष बनाता है। यह ठीक है। लेकिन क्या होगा अगर इनमें से दो अवशेष दो स्थानों पर परिभाषित एक ही घटक का प्रतिनिधित्व करते हैं? यहाँ एक उदाहरण है।
AmazonECSProvider टास्क डेफ़िनेशन JSON को पार्स करता है और AmazonECSTask प्रकार के साथ एक Relic तैयार करता है। GitHub एक्शन वर्कफ़्लो में ECS से संबंधित चरण भी है, इसलिए दूसरा प्रदाता AmazonECSTaskDeployment Relic बनाता है। अब, हमारे पास डुप्लिकेट हैं क्योंकि दोनों प्रदाता एक दूसरे के बारे में कुछ नहीं जानते हैं। इसके अलावा, उनमें से किसी के लिए यह मान लेना गलत है कि दूसरे ने पहले ही एक Relic बना लिया है। फिर क्या?
हम किसी भी डुप्लिकेट को नहीं हटा सकते क्योंकि उनमें से प्रत्येक की अपनी परिभाषाएँ (विशेषताएँ) हैं। एकमात्र तरीका उन्हें मर्ज करना है। डिफ़ॉल्ट रूप से, अगला तर्क मर्जिंग निर्णय को परिभाषित करता है:
relic1.name() == relic2.name() && relic1.source() != relic2.source()
हम दो अवशेषों को मर्ज करते हैं यदि उनके नाम समान हैं, लेकिन वे विभिन्न स्रोतों में परिभाषित हैं (जैसे हमारे उदाहरण में, रिपो में JSON और कार्य परिभाषा संदर्भ गिटहब क्रियाओं में है)।
जब हम विलय करते हैं, तो हम:
मैंने जानबूझकर रेलिक के एक महत्वपूर्ण पहलू को छोड़ दिया। इसमें एक मैचर हो सकता है - और इसका होना बेहतर है! मैचर एक बूलियन फ़ंक्शन है जो एक तर्क लेता है और उसका परीक्षण करता है। मैचर लिंकिंग प्रक्रिया के महत्वपूर्ण हिस्से हैं। यदि कोई रेलिक किसी अन्य के रेलिक की किसी भी परिभाषा से मेल खाता है, तो उन्हें एक साथ जोड़ा जाएगा।
याद है जब मैंने कहा था कि प्रदाताओं को अन्य प्रदाताओं द्वारा बनाए गए अवशेषों के बारे में कोई जानकारी नहीं है? यह अभी भी सच है। हालाँकि, एक प्रदाता एक अवशेष के लिए एक मिलानकर्ता को परिभाषित करता है। दूसरे शब्दों में, यह परिणामी आरेख पर दो बक्सों के बीच एक तीर के एक तरफ का प्रतिनिधित्व करता है।
उदाहरण. Dockerfile में एक ENTRYPOINT निर्देश है.
ENTRYPOINT java -jar /app/arch-diagram-sample.jar
कुछ हद तक निश्चितता के साथ, हम कह सकते हैं कि Docker ENTRYPOINT के अंतर्गत निर्दिष्ट की गई हर चीज़ को कंटेनराइज़ करता है । इसलिए, Dockerfile Relic में एक सरल Matcher फ़ंक्शन है: entrypointInstruction.contains(anotherRelicsDefinition)
. सबसे अधिक संभावना है कि परिभाषाओं में arch-diagram-sample.jar
वाले कुछ Jar Relics इससे मेल खाएँगे। यदि हाँ, तो Dockerfile और Jar Relics के बीच एक तीर दिखाई देता है।
मैचर परिभाषित होने के साथ, लिंकिंग प्रक्रिया बहुत सरल लगती है। लिंकिंग सेवा सभी अवशेषों पर पुनरावृत्ति करती है और उनके मैचर के फ़ंक्शन को कॉल करती है। क्या अवशेष A, अवशेष B की किसी परिभाषा से मेल खाता है? हाँ? परिणामी ग्राफ़ में उन अवशेषों के बीच एक किनारा जोड़ें। किनारे को नाम भी दिया जा सकता है।
अंतिम चरण पिछले चरण के हमारे अंतिम ग्राफ को विज़ुअलाइज़ करना है। स्पष्ट PNG के अलावा, उपकरण अतिरिक्त प्रारूपों का समर्थन करता है, जैसे कि मरमेड , प्लांट UML और DOT । ये टेक्स्ट प्रारूप कम आकर्षक लग सकते हैं, लेकिन बड़ा फायदा यह है कि आप उन टेक्स्ट को लगभग किसी भी विकी पेज में एम्बेड कर सकते हैं (
नमूना रिपो का अंतिम आरेख इस प्रकार दिखता है:
कस्टम घटकों को प्लग इन करने या मौजूदा तर्क को बदलने की क्षमता आवश्यक है, खासकर जब कोई उपकरण अपने प्रारंभिक चरण में हो। अवशेष और स्रोत डिफ़ॉल्ट रूप से काफी लचीले होते हैं; आप उनमें जो चाहें डाल सकते हैं। हर दूसरा घटक अनुकूलन योग्य है। मौजूदा प्रदाता आपके लिए आवश्यक संसाधनों को कवर नहीं करते हैं? आसानी से अपना खुद का कार्यान्वयन करें। ऊपर वर्णित मर्जिंग या लिंकिंग लॉजिक से संतुष्ट नहीं हैं? कोई समस्या नहीं; अपना खुद का LinkStrategy या MergeStrategy जोड़ें। सब कुछ एक JAR फ़ाइल में पैक करें और इसे स्टार्टअप पर जोड़ें। यहाँ और पढ़ें।
स्रोत कोड के आधार पर आरेख तैयार करने से संभवतः गति मिलेगी। और विशेष रूप से NoReDraw टूल (हाँ, यह उस टूल का नाम है जिसके बारे में मैं बात कर रहा था)। योगदानकर्ताओं का स्वागत है !
सबसे उल्लेखनीय लाभ (जो नाम से आता है) यह है कि घटकों के बदलने पर आरेख को फिर से बनाने की आवश्यकता नहीं होती है। इंजीनियरिंग ध्यान की कमी के कारण सामान्य रूप से दस्तावेज़ीकरण (और विशेष रूप से आरेख) पुराना हो जाता है। NoReDraw जैसे उपकरणों के साथ, यह अब कोई समस्या नहीं होनी चाहिए क्योंकि यह किसी भी PR/CI पाइपलाइन में आसानी से प्लग करने योग्य है। याद रखें, आरेखों को फिर से बनाने के लिए जीवन बहुत छोटा है 😉