27 पॉइंट द्वारा GN⁺ 2025-04-24 | 4 टिप्पणियां | WhatsApp पर शेयर करें
  • Go भाषा पैकेजों के बीच circular reference को सख्ती से प्रतिबंधित करती है, इसलिए यह स्वाभाविक रूप से layered design को बढ़ावा देती है
  • यह लेख Go प्रोजेक्ट में अनिवार्य रूप से बनने वाली layered structure को समझाता है, और यह तर्क देता है कि उसके ऊपर अलग से कोई architecture थोपे बिना भी यह पर्याप्त रूप से वैध है
  • जब circular dependency पैदा होती है, तो उसे हल करने के लिए ठोस और व्यावहारिक refactoring strategies को चरणबद्ध तरीके से प्रस्तुत किया गया है
  • हर पैकेज को स्वतंत्र रूप से अर्थपूर्ण functional unit रखने के लिए डिज़ाइन किया जाता है, इसलिए यह testing, maintenance, और microservice separation के लिए भी फायदेमंद है
  • नतीजतन, यह तरीका वास्तविक code design में अक्सर होने वाली "केला चाहिए था, लेकिन पूरा जंगल उठा लाए" जैसी समस्या को रोकता है

Go में layered design approach

बुनियादी सिद्धांत

  • Go में पैकेजों के बीच circular reference प्रतिबंधित है
  • सभी Go programs के import relationships को directed acyclic graph (DAG) बनाना चाहिए
  • यह संरचना कोई विकल्प नहीं, बल्कि language level पर लागू किया गया design rule है

पैकेज layering का स्वचालित निर्माण

  • external packages को छोड़कर, प्रोजेक्ट के अंदर के packages को reference depth के आधार पर अपने-आप layer किया जा सकता है
  • नीचे दिए गए चित्र की तरह सबसे निचले स्तर पर metrics, logging, common data structures जैसे core utility packages होते हैं
  • उसके बाद ऊपर के packages धीरे-धीरे functionality को combine करते हुए ऊपर की ओर stack होने वाली structure बनाते हैं

इस design तरीके की विशेषताएँ

  • layers hierarchical abstraction नहीं, बल्कि reference direction पर आधारित होते हैं
  • एक package कई lower-level packages को reference कर सकता है
  • MVC, hexagonal architecture जैसी मौजूदा design approaches भी इस structure के ऊपर "apply" की जा सकती हैं
    → लेकिन Go की structural constraints को ज़रूर ध्यान में रखना होगा

circular reference हल करने की रणनीतियाँ

जब circular reference हो, तो नीचे दिए गए क्रम में refactoring करने की कोशिश करें:

1. functionality को move करना

  • सबसे अधिक recommended तरीका
  • circularity पैदा करने वाली functionality का सटीक विश्लेषण करके, उसे तार्किक रूप से उचित जगह पर move करें
  • यह अक्सर इस्तेमाल नहीं होता, लेकिन conceptual clarity को सबसे ज्यादा बेहतर बनाता है

2. common functionality को अलग package में split करना

  • दोनों तरफ से साझा रूप से इस्तेमाल होने वाले types या functions (Username आदि) को तीसरे package में move करें
  • package छोटा दिखे तब भी निडर होकर अलग करें
    → समय के साथ उसके बड़े होने की संभावना ज़्यादा है

3. ऊपरी composition package बनाना

  • circular dependency वाले दो packages को compose करने वाला तीसरा package बनाएं
  • उदाहरण: Category, BlogPost की bidirectional dependency को ऊपर के package में अलग करें
    → निचले packages को dumb struct की तरह रखें, और वास्तविक functionality को ऊपर के package में compose करें

4. interface लागू करना

  • struct या function की dependency को केवल ज़रूरी methods वाले interface से replace करें
  • अनावश्यक dependencies हटती हैं और testing की सुविधा मिलती है
  • लेकिन इसका अत्यधिक उपयोग design को उल्टा जटिल बना सकता है

5. copy करना

  • अगर dependency target बहुत छोटा है, तो सीधे copy करके इस्तेमाल करें
  • यह DRY के उल्लंघन जैसा लग सकता है, लेकिन वास्तव में design clarity बढ़ाने में अक्सर मदद करता है

6. एक ही package में merge करना

  • अगर ऊपर के सभी तरीके असंभव हों, तो दोनों packages को merge करें
  • अगर package बहुत बड़ा नहीं बनता, तो यह स्वीकार्य है
    → लेकिन बिना सोचे-समझे merge करने से बचें और सावधानी से निर्णय लें

इस design तरीके के व्यावहारिक फायदे

  • हर package अपने-आप में एक अर्थपूर्ण functional unit रखता है और उसका स्वतंत्र testing किया जा सकता है
  • package के अंदर references सीमित होते हैं, इसलिए पूरा codebase समझे बिना भी अलग package को समझा जा सकता है
  • अनचाहे global dependency links (=जंगल समस्या) से बचते हुए, केवल ज़रूरत की चीज़ें इस्तेमाल करने वाला code लिखने के लिए प्रेरित करता है
  • microservice separation के समय भी इसे आसानी से extract किया जा सकता है
    → क्योंकि ज़्यादातर dependencies पहले से स्पष्ट रूप से परिभाषित होती हैं

निष्कर्ष

  • Go की package design constraints कोई झुंझलाहट भरी पाबंदी नहीं, बल्कि अच्छे design को दिशा देने वाला तंत्र हैं
  • किसी खास architecture के बिना भी सिर्फ package reference structure के आधार पर मजबूत design लागू किया जा सकता है
  • circular reference पर सूक्ष्म विश्लेषण और refactoring strategy सिर्फ Go ही नहीं, दूसरी भाषाओं में भी उपयोगी है

4 टिप्पणियां

 
bus710 2025-04-25

शुरुआत में जल्दी-जल्दी लिखकर जब चीज़ चलने लगती है तो मज़ा आता है
लेकिन जैसे ही टेस्ट जोड़ना शुरू करते हैं
तब सोचने लगते हैं कि मैंने उस समय ऐसा क्यों किया था।

 
bungker 2025-04-24

"केला चाहिए था, लेकिन पूरा जंगल उठा लाए" — यह बात वाकई बहुत मज़ेदार है।

 
iwanhae 2025-04-24

Spring में development करते समय सबसे मुश्किल चीज़ों में से एक शायद dependency cycle थी.. एक-दूसरे को अनंत बार initialize करते हुए memory leak से crash हो जाने वाली वह घुटन...

 
GN⁺ 2025-04-24
Hacker News राय
  • बड़े प्रोग्राम बनाते समय circular dependency की अनुमति न देना एक बेहतरीन डिज़ाइन विकल्प है

    • यह concerns को ठीक से अलग रखने के लिए मजबूर करता है
    • अगर circular dependency बनती है, तो डिज़ाइन में समस्या है, और लेख इसे हल करने का तरीका अच्छी तरह समझाता है
    • कभी-कभी circular dependency को हल करने के लिए ऐसे function pointers का उपयोग किया जाता है जिन्हें कोई दूसरा package redefine करता है
    • अच्छा होता अगर Go compiler circular dependency बनते समय और उपयोगी output देता
    • अभी यह loop में शामिल सभी packages की सूची देता है, जो काफ़ी लंबी हो सकती है, जबकि आमतौर पर समस्या वही होती है जिसे आख़िरी बार बदला गया हो
  • बेहतरीन ब्लॉग पोस्ट है

    • इस वेबसाइट पर बहुत शानदार पोस्ट हैं, और अगर आपको functional programming के बारे में सीखना पसंद है, तो इसे ज़रूर देखें
    • लिंक
  • "तीसरे package में ले जाएँ" वाली सलाह से जुड़ी एक बोनस तकनीक

    • अगर आप बहुत सारे model structures (SQL, Protobuf, GraphQL आदि) generate करते हैं, तो generated layers के बीच स्पष्ट directionality तय की जा सकती है
    • सारे generated code को application code के लिए एक "base package" के रूप में दिया जाता है, जो सब कुछ साथ में compose करता है
    • इस तकनीक को अपनाने से पहले "models का models को circularly import करना" जैसी समस्या थी, लेकिन एक अतिरिक्त structural layer लाने से यह पूरी तरह गायब हो गई
  • लगता है यह Yourdon structural method पर किसी किताब को पढ़ रहा है

  • packages एक-दूसरे को circularly reference नहीं कर सकते

    • दरअसल Go में go:linkname का उपयोग करके यह संभव है
  • यह randomizer की ठोस अवधारणा की याद दिलाता है

  • Golang की एक दिलचस्प बात यह है कि package स्तर पर circular dependency नहीं हो सकती, लेकिन go.mod में हो सकती है

    • संक्षेप में, वह भी नहीं करना चाहिए
  • Jerf packages के बारे में कैसे सोचते हैं और circular dependency को कैसे संभालते हैं, इसकी बढ़िया व्याख्या है