13 वर्षों के बाद मैं Go में HTTP सेवाएँ कैसे लिखता हूँ
Go डेवलपर्स इस बात पर बहस कर रहे हैं कि HTTP सेवाओं को कैसे संरचित किया जाए ताकि वे परीक्षणयोग्य और अनुरक्षणीय दोनों हों, और उनका ध्यान `main` में लॉजिक कम रखने, हैंडलरों में निर्भरताएँ स्पष्ट रूप से पास करने, तथा बड़े “god” config या server structs से बचने जैसे पैटर्न पर है। कई लोग छोटे, composable फ़ंक्शनों और explicit dependency injection को पसंद करते हैं (कभी-कभी Uber की `fx` जैसी लाइब्रेरी के साथ), जबकि अन्य over-engineering के खिलाफ चेतावनी देते हैं और bespoke सेवाओं के लिए सरल, script-जैसे mains को प्राथमिकता देते हैं। एक बार-बार आने वाला विषय है invariants को encode करने और bugs कम करने के लिए types और parsing (“parse, don’t validate”) का उपयोग, बजाय इसके कि ad hoc validation को पूरे codebase में बिखेर दिया जाए।
main की संरचना और परीक्षणयोग्यता
- कई लोग एक छोटे
main()को पसंद करते हैं जोrun()फ़ंक्शन को काम सौंप देता है, ताकि त्रुटियाँ वापस की जा सकें और अधिकांश लॉजिक परीक्षणयोग्य और पुन: उपयोग योग्य बन जाए। - कुछ लोग आंतरिक सेवाओं के लिए “flat script” शैली वाले
main.goको पसंद करते हैं, जिसमें शीर्ष-स्तरीय कंट्रोल फ़्लो दिखाई देता रहे, बजाय इसके कि उसे हेल्पर्स में धकेला जाए। - सहमति: अनपरीक्षणयोग्य/बूटस्ट्रैप कोड को छोटा रखें, लेकिन पठनीयता की कीमत पर नहीं;
main()को स्वयं परीक्षणों से नहीं बुलाया जा सकता, इसलिए बीच का एक स्तर रखना प्रेरित करता है।
हैंडलर, निर्भरताएँ, और DI
- स्पष्ट निर्भरताओं के लिए मजबूत समर्थन है: हैंडलरों को जो चाहिए, वह आर्ग्युमेंट्स या प्रति-हैंडलर structs के माध्यम से मिलना चाहिए, न कि किसी बड़े सर्वर struct में छिपा हो।
- कुछ लोग “brutal clarity” चाहते हैं, भले ही इसके लिए बहुत सारे पैरामीटर हों; अन्य लोग विशाल सिग्नेचर से बचने के लिए निर्भरताओं को context/env structs या handler structs में पैक करते हैं।
- सरलता और कपलिंग के बीच तनाव है: बड़े “god” सर्वर structs और बहुत सारे आर्ग्युमेंट्स वाले constructors अत्यधिक कपलिंग का संकेत देते हैं।
कॉन्फ़िगरेशन और options पैटर्न
- एक mutable global config object को इधर-उधर पास करना व्यापक रूप से आलोचना का विषय है; यह subsystems को जोड़ देता है और सूक्ष्म ordering bugs पैदा कर सकता है।
- पसंदीदा पैटर्न: immutable या “frozen” config; config को internal fields में कॉपी करना; प्रति-package config structs; और constructors के लिए “functional options” या option structs।
- चिंता: बहुत चतुर options patterns boilerplate बढ़ाते हैं और discoverability को नुकसान पहुँचाते हैं; सरल config structs अक्सर लंबे समय में जीतते हैं।
Validation बनाम “parse, don’t validate” और types
- कई लोग primitives (जैसे,
Username) को dedicated types में लपेटने की वकालत करते हैं, जिन्हें parsing/validation के माध्यम से बनाया जाता है, ताकि अवैध अवस्थाएँ अभिव्यक्त न की जा सकें। - Go के zero values पर बहस है: जब structs constructors से गुज़रे बिना instantiate किए जा सकते हैं, तो वे गारंटी को कमजोर कर देते हैं।
- कुछ लोग इस पैटर्न को Go में overengineering या cumbersome मानते हैं; अन्य इसे “stringly typed” bugs से बचने के लिए आवश्यक मानते हैं।
परीक्षण रणनीतियाँ
- कई लोग end-to-end शैली के tests को पसंद करते हैं जो fake dependencies के साथ एक test server को spin up करते हैं, और HTTP तथा middleware paths दोनों का अभ्यास कराते हैं।
- अन्य लोग dependencies inject करके और छोटे helper functions का उपयोग करके handler logic की unit testing पर ध्यान केंद्रित करते हैं।
Spec-first और code generation
- कुछ प्रतिभागी OpenAPI-first workflows को generators (जैसे, ogen, oapi-codegen) के साथ पसंद करते हैं, ताकि repetitive validation और routing code से बचा जा सके।
- अन्य लोगों को OpenAPI authoring उबाऊ लगता है, लेकिन वे नोट करते हैं कि tooling और LLM-assisted authoring मदद करते हैं, और IDLs (gRPC/gateway सहित) उत्पादक हो सकते हैं।
Dependency injection frameworks
- हल्के DI frameworks (जैसे, fx) को complex initialization/shutdown graphs को संभालने के लिए सराहा जाता है।
- संदेहवादी तर्क देते हैं कि DI frameworks indirection जोड़ते हैं; वे
mainमें explicit construction को प्राथमिकता देते हैं, और दावा करते हैं कि acyclic graphs और manual teardown अभी भी प्रबंधनीय रहते हैं।