बारबरा लिस्कोव के डेटा एब्स्ट्रैक्शन सिद्धांत सीखें ताकि आप स्थिर इंटरफ़ेस डिज़ाइन कर सकें, ब्रेकेज़ घटा सकें, और साफ़, रख-रखाव योग्य तथा विश्वसनीय API बना सकें।

बारबरा लिस्कोव एक कंप्यूटर वैज्ञानिक हैं जिनके काम ने चुपचाप आधुनिक सॉफ्टवेयर टीमों के उस तरीके को आकार दिया कि वे ऐसी चीज़ें बनाते हैं जो बिखरती नहीं हैं। उनके शोध—डेटा एब्स्ट्रैक्शन, सूचना छुपाना, और बाद में लिस्कोव सब्स्टीट्यूशन प्रिंसिपल (LSP)—ने प्रोग्रामिंग भाषाओं से लेकर रोज़मर्रा के API‑सोच तक सब कुछ प्रभावित किया: स्पष्ट व्यवहार परिभाषित करें, आंतरिक हिस्सों को सुरक्षित रखें, और यह सुनिश्चित करें कि दूसरे आपके इंटरफ़ेस पर भरोसा कर सकें।
एक विश्वसनीय API केवल सैद्धांतिक रूप से "सही" नहीं होता। यह ऐसा इंटरफ़ेस होता है जो उत्पाद को तेज़ी से आगे बढ़ने में मदद करता है:
यह विश्वसनीयता एक अनुभव है: आपके API को कॉल करने वाले डेवलपर के लिए, उसे मेन्टेन करने वाली टीम के लिए, और उन यूज़र्स के लिए जो अप्रत्यक्ष रूप से उस पर निर्भर हैं।
डेटा एब्स्ट्रैक्शन यह विचार है कि कॉल करने वाले को किसी कॉनसेप्ट (एक अकाउंट, एक कतार, एक सब्सक्रिप्शन) के साथ एक छोटे सेट ऑपरेशन्स के जरिए इंटरैक्ट करना चाहिए—न कि इसके स्टोर या कैलकुलेशन के गंदे विवरणों के जरिए।
जब आप रिप्रेजेंटेशन विवरण छुपाते हैं, तो आप पूरी श्रेणियों की गलतियों को दूर कर देते हैं: कोई भी अनजाने में किसी डेटाबेस फ़ील्ड पर निर्भर नहीं कर सकता जो सार्वजनिक नहीं था, या साझा स्टेट को ऐसे बदल नहीं सकता जिसका सिस्टम प्रबंधन नहीं कर सकता। उतना ही महत्वपूर्ण, एब्स्ट्रैक्शन समन्वय ओवरहेड घटाती है: टीमें इंटर्नल्स को रिफैक्टर करने के लिए अनुमति मांगने की ज़रूरत नहीं समझतीं जब तक कि पब्लिक बिहेवियर सुसंगत है।
इस लेख के अंत तक, आपके पास व्यावहारिक तरीके होंगे ताकि आप:
यदि आप बाद में एक त्वरित सार चाहते हैं, तो /blog/a-practical-checklist-for-designing-reliable-apis पर जाएँ।
डेटा एब्स्ट्रैक्शन एक सरल विचार है: आप किसी चीज़ के साथ उसके "क्या करता है" के द्वारा इंटरैक्ट करते हैं, न कि यह कैसे बनी है के द्वारा।
वेंडिंग मशीन की कल्पना करें। आपको यह जानने की ज़रूरत नहीं कि मोटर्स कैसे घूमते हैं या सिक्के कैसे गिने जाते हैं। आपको केवल कंट्रोल चाहिए ("आइटम चुनें", "भुगतान करें", "आइटम प्राप्त करें") और नियम ("यदि आपने पर्याप्त भुगतान किया तो आपको आइटम मिलेगा; यदि यह बिक चुका है तो आपको रिफंड मिलेगा")। यही एब्स्ट्रैक्शन है।
सॉफ़्टवेयर में, इंटरफ़ेस "यह क्या करता है" है: ऑपरेशन्स के नाम, वे किस इनपुट को स्वीकार करते हैं, क्या आउटपुट देते हैं, और किस तरह की एरर की उम्मीद है। इम्प्लिमेंटेशन "यह कैसे काम करता है" है: डेटाबेस टेबल्स, कैशिंग रणनीति, आंतरिक क्लासेस, और परफॉरमेंस ट्रिक्स।
इनको अलग रखना ही वह तरीका है जिससे आपको ऐसे API मिलते हैं जो सिस्टम के विकसित होने पर भी स्थिर रहते हैं। आप इंटर्नल्स को री‑राइट कर सकते हैं, लाइब्रेरियों को बदल सकते हैं, या स्टोरेज ऑप्टिमाइज़ कर सकते हैं—जबकि इंटरफ़ेस उपयोगकर्ताओं के लिए वही रहता है।
एक एब्स्ट्रैक्ट डेटा टाइप "कंटेनर + अलाउड ऑपरेशन्स + नियम" है, जिसे किसी विशेष आंतरिक स्ट्रक्चर के बिना वर्णित किया जाता है।
उदाहरण: एक Stack (last in, first out).
कुंजी वादा है: pop() आखिरी push() को रिटर्न करता है। स्टैक आरे (array), लिंक्ड लिस्ट, या कुछ और का उपयोग करे—यह निजी है।
यह अलगाव हर जगह लागू होता है:
POST /payments इंटरफ़ेस है; फ्रॉड चेक, रिट्राई, और डेटाबेस लिखना इम्प्लिमेंटेशन है।client.upload(file) इंटरफ़ेस है; चंकिंग, कंप्रेशन, और पैरेलल रिक्वेस्ट्स इम्प्लिमेंटेशन है।जब आप एब्स्ट्रैक्शन के साथ डिज़ाइन करते हैं, आप उस कॉन्ट्रैक्ट पर ध्यान देते हैं जिस पर उपयोगकर्ता भरोसा करते हैं—और पर्दे के पीछे सब कुछ बदलने की आज़ादी खरीदते हैं बिना उन्हें तोड़े।
एक इनवेरिएंट वह नियम है जो किसी एब्स्ट्रैक्शन के अंदर हमेशा सच होना चाहिए। यदि आप API डिज़ाइन कर रहे हैं, तो इनवेरिएंट्स वे गार्डरेल्स हैं जो आपके डेटा को असंभव स्थितियों में जाने से रोकते हैं—जैसे एक बैंक अकाउंट में दो मुद्राएँ एक साथ होना, या एक "कम्प्लीट" ऑर्डर जिसमें कोई आइटम न हो।
एक इनवेरिएंट को अपने प्रकार की "हकीकत की शक्ल" समझें:
Cart नकारात्मक मात्रा नहीं रख सकता।UserEmail हमेशा वैध ईमेल होता है ("बाद में वैलिडेट" नहीं)।Reservation में start < end होता है, और दोनों समय एक ही टाइमज़ोन में होते हैं।यदि ये कथन सच होना बंद हो जाएं, तो आपका सिस्टम अप्रत्याश्य बन जाता है, क्योंकि हर फीचर अब अनुमान लगाएगा कि "खराब" डेटा का क्या अर्थ है।
अच्छे API बॉर्डर्स पर इनवेरिएंट्स लागू करते हैं:
यह स्वाभाविक रूप से एरर हैंडलिंग सुधारता है: अस्पष्ट फेल्योर (“कुछ गलत हुआ”) की बजाय, API बता सकता है कौन सा नियम टूट गया (“end को start के बाद होना चाहिए”)।
कॉलर्स को आंतरिक नियमों को याद रखने की ज़रूरत नहीं होनी चाहिए जैसे "यह मेथड normalize() कॉल करने के बाद ही काम करता है।" अगर कोई इनवेरिएंट किसी विशेष रिचुअल पर निर्भर है, तो वह इनवेरिएंट नहीं—यह एक फूटगन है।
इंटरफ़ेस इस तरह डिज़ाइन करें कि:
API प्रकार दस्तावेज़ करते समय लिखें:
एक अच्छा API केवल फ़ंक्शंस का सेट नहीं है—यह एक वादा है। कॉन्ट्रैक्ट्स उस वादे को स्पष्ट करते हैं, ताकि कॉलर्स व्यवहार पर भरोसा कर सकें और मेंटेनर्स इंटर्नल बदल सकें बिना किसी को चौंकाए।
कम से कम, दस्तावेज़ करें:
यह स्पष्टता व्यवहार को पूर्वानुमेय बनाती है: कॉलर्स जानते हैं कौन से इनपुट सुरक्षित हैं और किन परिणामों को हैंडल करना है, और टेस्ट्स इरादा चेक कर सकती हैं बजाय अनुमान लगाने के।
बिना कॉन्ट्रैक्ट्स के, टीमें मेमोरी और अनौपचारिक नियमों पर निर्भर करती हैं: "वहां null न पास करें," "वह कॉल कभी‑कभी रिट्राई करता है," "यह एरर पर खाली लौटता है।" ये नियम ऑनबोर्डिंग, रिफैक्टर्स, या घटनाओं के दौरान खो जाते हैं।
लिखित कॉन्ट्रैक्ट उन छिपे नियमों को साझा ज्ञान में बदल देता है। यह कोड रिव्यू के लिए भी स्थिर लक्ष्य बनाता है: चर्चाएँ बनती हैं "क्या यह परिवर्तन अभी भी कॉन्ट्रैक्ट पूरा करता है?" बजाय "मुझे काम कर रहा दिखता है।"
अस्पष्ट: "Creates a user."
बेहतर: "Creates a user with a unique email.
email वैध होना चाहिए; कॉलर के पास users:create परमिशन होना चाहिए.userId लौटता है; यूज़र पर्सिस्ट हो गया है और तुरंत रिक्ट्रीवबल है.409 लौटेगा; अवैध फील्ड्स पर 400 लौटेगा; कोई आंशिक यूज़र नहीं बनाया जाएगा."अस्पष्ट: "Gets items quickly."
बेहतर: "Returns up to limit items sorted by createdAt descending.
nextCursor का उपयोग करें; कर्सर 15 मिनट के बाद एक्सपायर हो जाते हैं."सूचना छुपाना डेटा एब्स्ट्रैक्शन का व्यावहारिक हिस्सा है: कॉलर्स को क्या API करता है पर भरोसा करना चाहिए, कैसे यह करता है पर नहीं। यदि यूज़र्स आपके इंटर्नल्स नहीं देख पाते, तो आप उन्हें बदले बिना हर रिलीज़ को ब्रेकिंग चेंज में नहीं बदलेंगे।
एक अच्छा इंटरफ़ेस क्रियाओं का छोटा सेट प्रकाशित करता है (create, fetch, update, list, validate) और प्रतिनिधित्व—टेबल्स, कैशेस, क्व्यूज़, फ़ाइल लेआउट्स, सर्विस बॉउन्ड्रीज़—को निजी रखता है।
उदाहरण के लिए, "कार्ट में आइटम जोड़ें" एक ऑपरेशन है। आपके डेटाबेस का "CartRowId" इम्प्लिमेंटेशन का डिटेल है। जब आप डिटेल एक्सपोज़ करते हैं, आप उपयोगकर्ताओं को उस पर अपना लॉजिक बनाने के लिए आमंत्रित करते हैं, जो आपके बदलाव की क्षमता को जमे हुए कर देता है।
जब क्लाइंट्स केवल स्थिर व्यवहार पर निर्भर करते हैं, तो आप कर सकते हैं:
…और API संगत रहता है क्योंकि कॉन्ट्रैक्ट नहीं बदलता। यही असली लाभ है: उपयोगकर्ताओं के लिए स्थिरता, मेंटेनर्स के लिए स्वतंत्रता।
कुछ तरीके जिनसे इंटर्नल्स गलती से बाहर निकल जाते हैं:
status=3 स्वीकार करना बजाय स्पष्ट नाम या समर्पित ऑपरेशन के।अर्थ बताने वाले रिस्पॉन्स पसंद करें, मैकेनिक बताने वालों की बजाय:
"userId": "usr_…") न कि डेटाबेस रो नंबर।यदि कोई डिटेल बदल सकती है, तो उसे प्रकाशित न करें। यदि यूज़र्स को इसकी ज़रूरत है, तो उसे इंटरफ़ेस वादे का जानबूझकर, दस्तावेजीकृत हिस्सा बनाकर प्रमोट करें।
LSP एक वाक्य में: यदि किसी कोड का काम किसी इंटरफ़ेस के साथ होता है, तो किसी भी वैध इम्प्लिमेंटेशन को स्वैप करने पर वह बिना स्पेशल‑केस के काम करता रहना चाहिए।
LSP वारसा (inheritance) के बारे में कम और भरोसे के बारे में ज़्यादा है। जब आप एक इंटरफ़ेस प्रकाशित करते हैं, तो आप व्यवहार के बारे में एक वादा करते हैं। LSP कहता है कि हर इम्प्लिमेंटेशन को वह वादा रखना चाहिए, भले ही वह बहुत अलग आंतरिक तरीका अपनाता हो।
कॉलर्स आपके API के बताए हुए पर भरोसा करते हैं—न कि आज यह जो करता है उस पर। यदि इंटरफ़ेस कहता है "आप किसी भी वैध रिकॉर्ड के साथ save() कर सकते हैं," तो हर इम्प्लिमेंटेशन को उन वैध रिकॉर्ड्स को स्वीकार करना चाहिए। यदि इंटरफ़ेस कहता है "get() एक वैल्यू या स्पष्ट 'not found' परिणाम लौटाता है," तो इम्प्लिमेंटेशन उसी स्थिति में अचानक नया एरर नहीं फेंक सकता।
सुरक्षित विस्तार का मतलब है कि आप नए इम्प्लिमेंटेशन्स जोड़ सकें (या प्रोवाइडर बदल सकें) बिना यूज़र्स को कोड फिर से लिखने के लिए मजबूर किए—यही LSP का व्यावहारिक लाभ है: यह इंटरफ़ेस को स्वैपेबल रखता है।
दो सामान्य तरीके जिनसे API वादा तोड़ते हैं:
नैरो इनपुट्स (कठोर प्रीकॉन्डिशन): नया इम्प्लिमेंटेशन उन इनपुट्स को रिजेक्ट करता है जिन्हें इंटरफ़ेस ने अनुमति दी थी। उदाहरण: बेस इंटरफ़ेस किसी UTF‑8 स्ट्रिंग को ID के रूप में स्वीकार करता है, पर एक इम्प्लिमेंटेशन केवल न्यूमेरिक IDs स्वीकार करता है।
कमज़ोर आउटपुट (ढीले पोस्टकंडीशन्स): नया इम्प्लिमेंटेशन वादा किये गए से कम रिटर्न करता है। उदाहरण: इंटरफ़ेस कहता है कि रिज़ल्ट्स सॉर्टेड, यूनिक, या संपूर्ण होंगे—फिर एक इम्प्लिमेंटेशन असॉर्टेड डेटा, डुप्लिकेट, या चुपके से ड्रॉप किए गए आइटम लौटाता है।
एक तीसरा, सूक्ष्म उल्लंघन है फेल्योर बिहेवियर को बदलना: अगर एक इम्प्लिमेंटेशन "not found" लौटाता है जबकि दूसरा उसी हालत में एक्सेप्शन फेंकता है, तो कॉलर्स सुरक्षित रूप से एक को दूसरे से बदल नहीं पाएंगे।
"प्लग‑इन्स" (कई इम्प्लिमेंटेशन्स) का समर्थन करने के लिए, इंटरफ़ेस को कॉन्ट्रैक्ट की तरह लिखें:
यदि किसी इम्प्लिमेंटेशन को वास्तव में कड़ा नियम चाहिए, तो उसी इंटरफ़ेस के पीछे उसे छुपाएँ मत। या तो (1) अलग इंटरफ़ेस परिभाषित करें, या (2) उस प्रतिबंध को एक क्षमता के रूप में स्पष्ट करें (उदा., supportsNumericIds()), ताकि क्लाइंट जानबूझकर ऑप्ट‑इन कर सकें।
एक अच्छी तरह डिज़ाइन किया गया इंटरफ़ेस उपयोग करने में "सपष्ट" महसूस होता है क्योंकि यह केवल वही एक्सपोज़ करता है जिसकी कॉलर को ज़रूरत है—और उससे कहीं अधिक नहीं। लिस्कोव की डेटा एब्स्ट्रैक्शन की दृष्टि आपको ऐसे इंटरफ़ेस की ओर धकेलती है जो संकीर्ण, स्थिर, और पठनीय हों, ताकि उपयोगकर्ता बिना आंतरिक विवरण सीखे उन पर भरोसा कर सकें।
बड़े APIs अक्सर असंबंधित जिम्मेदारियाँ मिला देते हैं: कॉन्फ़िगरेशन, स्टेट चेंज, रिपोर्टिंग, और ट्रबलशूटिंग एक ही जगह। इससे यह समझना कठिन हो जाता है कि क्या सुरक्षित रूप से कब कॉल करना है।
एक कोहेसिव इंटरफ़ेस वे ऑपरेशन्स ग्रुप करता है जो एक ही एब्स्ट्रैक्शन के लिए हों। यदि आपका API एक कतार को दर्शाता है, तो कतार के व्यवहार पर ध्यान दें (enqueue/dequeue/peek/size), न कि सामान्य‑उद्देश्य यूटिलिटीज़ पर। कम कॉन्सेप्ट्स का मतलब कम गलत उपयोग के रास्ते।
"लचीला" अक्सर मतलब होता है "अनस्पष्ट"। ऐसे पैरामीटर्स जैसे options: any, mode: string, या कई बूलियन (force, skipCache, silent) अस्पष्ट संयोजन पैदा करते हैं।
पसंद करें:
publish() बनाम publishDraft()), यायदि किसी पैरामीटर के प्रभाव जानने के लिए कॉलर को स्रोत देखना पड़ता है, तो वह अच्छा एब्स्ट्रैक्शन नहीं है।
नाम अनुबंध संप्रेषित करते हैं। ऐसे क्रियाओं वाले शब्द चुनें जो ऑब्ज़रवेबल बिहेवियर बताएं: reserve, release, validate, list, get। चतुर रूपक और ओवरलोडेड टर्म्स से बचें। यदि दो मेथड मिलते‑जुलते नाम वाले हैं, तो कॉलर्स मान लेंगे कि वे समान व्यवहार करते हैं—इसलिए उसे सच रखें।
API को विभाजित करें जब आप देखें:
अलग मॉड्यूल आपको इंटर्नल्स विकसित करने देते हैं जबकि कोर वादा स्थिर रहता है। यदि आप वृद्धि की योजना बना रहे हैं, तो एक पतला "कोर" पैकेज और ऐड‑ऑन्स पर विचार करें; देखें /blog/evolving-apis-without-breaking-users।
APIs शायद ही स्थिर रहते हैं। नए फीचर आते हैं, एज केस मिलते हैं, और "छोटे सुधार" असल ऐप्लिकेशन्स को चुपके से तोड़ सकते हैं। लक्ष्य इंटरफ़ेस को फ्रीज़ करना नहीं है—बल्कि इसे यूज़र्स के भरोसे को तोड़े बिना विकसित करना है।
सेमेंटिक वर्ज़निंग एक संचार उपकरण है:
सीमितता: आपको फिर भी जजमेंट चाहिए। यदि एक "बग फिक्स" ऐसा व्यवहार बदल दे जिस पर कॉलर्स निर्भर थे, तो व्यवहार में यह ब्रेकिंग होगा—भले ही पुराना व्यवहार आकस्मिक था।
कई ब्रेकिंग चेंजेज़ कंपाइलर में दिखाई नहीं देते:
सोचें preconditions और postconditions के संदर्भ में: कॉलर्स को क्या देना चाहिए, और वे क्या वापस पाने की गारंटी कर सकते हैं।
डिप्रेक्शन तब काम करता है जब यह स्पष्ट और समय‑बद्ध हो:
लिस्कोव‑स्टाइल डेटा एब्स्ट्रैक्शन इसलिए मदद करती है क्योंकि यह सीमित करती है जिस पर यूज़र्स निर्भर कर सकते हैं। अगर कॉलर्स केवल इंटरफ़ेस कॉन्ट्रैक्ट पर भरोसा करते हैं—न कि आंतरिक संरचना पर—तो आप स्टोरेज फ़ॉर्मैट्स, एल्गोरिद्म, और ऑप्टिमाइज़ेशन स्वतंत्र रूप से बदल सकते हैं।
व्यावहारिक रूप से, यह वहीं जगह है जहाँ मजबूत टूलिंग मदद करती है। उदाहरण के लिए, यदि आप आंतरिक API पर तेजी से काम कर रहे हैं जबकि आप एक React वेब ऐप या Go + PostgreSQL बैकएंड बना रहे हैं, तो एक वाइब‑कोडिंग वर्कफ़्लो जैसे Koder.ai कार्यान्वयन को तेज़ कर सकता है बिना कोर अनुशासन को बदले: आपको अभी भी स्पष्ट कॉन्ट्रैक्ट्स, स्थिर पहचानकर्ता, और बैकवर्ड‑कम्पेटिबल विकास चाहिए। गति एक गुणक है—इसलिए सही इंटरफ़ेस आदतों को गुणा करना सार्थक है।
एक विश्वसनीय API वह नहीं है जो कभी फेल न हो—बल्कि वह है जो ऐसे तरीके से फेल हो जिसे कॉलर्स समझ सकें, हैंडल कर सकें, और टेस्ट कर सकें। एरर हैंडलिंग एब्स्ट्रैक्शन का हिस्सा है: यह परिभाषित करता है कि "सही उपयोग" क्या है, और जब दुनिया (नेटवर्क, डिस्क, परमिशन, समय) असहमत हो तो क्या होता है।
शुरूवात दो श्रेणियों को अलग करके करें:
यह अंतर आपके इंटरफ़ेस को ईमानदार रखता है: कॉलर्स सीखते हैं कि किसे वे कोड में ठीक कर सकते हैं बनाम किसे रनटाइम पर संभालना होगा।
आपका कॉन्ट्रैक्ट इशारा करे कि किस मैकेनिज़्म का उपयोग करें:
Ok | Error) जब विफलताएँ अपेक्षित हों और आप चाहें कि कॉलर्स उन्हें स्पष्ट रूप से हैंडल करें।जो भी चुनें, API में सुसंगत रहें ताकि उपयोगकर्ता अनुमान न लगाएँ।
प्रति ऑपरेशन संभावित विफलताओं को अर्थ के हिसाब से सूचीबद्ध करें, न कि इम्प्लिमेंटेशन डिटेल्स के रूप में: "कन्फ्लिक्ट क्योंकि वर्शन स्टेल है", "नॉट फाउंड", "अनुमति अस्वीकृत", "रेट लिमिटेड"। स्थिर एरर कोड और संरचित फील्ड दें ताकि टेस्ट्स व्यवहार को स्ट्रिंग‑मैचिंग पर बिना निर्भर रहे परीक्षण कर सकें।
यह दस्तावेज़ करें कि कोई ऑपरेशन सेफ टू रिट्राई है या नहीं, किन परिस्थितियों में, और आइडेमपोटेंसी कैसे प्राप्त करें (आइडेम्पोटेंसी की/प्राकृतिक रिक्वेस्ट IDs)। यदि पार्टियल सक्सेस संभव है (बैच ऑपरेशन्स), तो परिभाषित करें कि सक्सेस/फेल्योर कैसे रिपोर्ट होंगे, और टाइमआउट के बाद कॉलर्स को किस स्थिति का अनुमान होना चाहिए।
एक एब्स्ट्रैक्शन एक वादा है: "यदि आप वैध इनपुट्स के साथ ये ऑपरेशन्स कॉल करते हैं, तो आपको ये परिणाम मिलेंगे, और ये नियम हमेशा लागू होंगे।" परीक्षण वही तरीका है जिससे आप यह वादा कोड के बदलने पर ईमानदार रखते हैं।
कॉन्ट्रैक्ट को ऐसे चेक्स में बदल दें जिन्हें आप ऑटोमेटेड रूप से चला सकें।
यूनिट टेस्ट्स हर ऑपरेशन के पोस्टकंडीशन्स और एज‑केसेस की जाँच करें: रिटर्न वैल्यू, स्टेट चेंज, और एरर बिहेवियर। अगर आपका इंटरफ़ेस कहता है "नॉन‑एक्ज़िस्टेंट आइटम हटाने पर false लौटता है और कुछ नहीं बदलता", तो वही टेस्ट लिखें।
इंटीग्रेशन टेस्ट्स कॉन्ट्रैक्ट को असली बॉर्डर्स पर वैरिफ़ाइ करें: डेटाबेस, नेटवर्क, सीरियलाइज़ेशन, और ऑथ। कई "कॉन्ट्रैक्ट उल्लंघन" केवल तब प्रकट होते हैं जब टाइप्स एन्कोड/डिकोड होते हैं या रिट्राई/टाइमआउट्स होते हैं।
इनवेरिएंट्स वे नियम हैं जो किसी भी वैध ऑपरेशन्स की अनुक्रम में हमेशा सच रहना चाहिए (उदा., "बैलेंस कभी नेगेटिव न हो", "IDs यूनिक हों", "list() द्वारा लौटाए आइटम get(id) से प्राप्त किए जा सकें").
प्रॉपर्टी‑आधारित टेस्टिंग इन नियमों को बहुत सारे रैंडम‑पर वैध इनपुट और ऑपरेशन सीक्वेंस जनरेट करके जांचती है, और काउंटर‑एक्जांपल खोजती है। बुनियादी रूप से, आप कह रहे हैं: "चाहे उपयोगकर्ता मेथड किसी भी क्रम में कॉल करे, इनवेरिएंट बना रहे।" यह उन अजीब कोनों को ढूँढने में ख़ास काम आती है जिनके लिए मनुष्य टेस्ट लिखना भूल जाते हैं।
साझा या पब्लिक APIs के लिए, कंज्यूमर उन अनुरोधों के उदाहरण प्रकाशित करें जो वे करते हैं और उन उत्तरों पर निर्भर हैं। प्रोवाइडर फिर CI में उन कॉन्ट्रैक्ट्स को चलाते हैं ताकि यह पक्का हो सके कि बदलाव असली उपयोग को नहीं तोड़ रहे—भले ही प्रोवाइडर टीम ने वह उपयोग अनपेक्षित मन में नहीं रखा था।
टेस्ट्स सब कुछ नहीं कवर कर सकते, इसलिए उन संकेतों की निगरानी करें जो सुझाते हैं कि कॉन्ट्रैक्ट बदल रहा है: रिस्पॉन्स शेप बदलना, 4xx/5xx रेट्स में वृद्धि, नए एरर कोड, लेटेंसी स्पाइक्स, और "अनजान फील्ड" या डीसिरियलाइज़ेशन फेल्योर। इन्हें एन्डपॉइंट और वर्ज़न के हिसाब से ट्रैक करें ताकि आप ड्रिफ्ट जल्दी पकड़कर सुरक्षित रूप से रोलबैक कर सकें।
यदि आप अपने डिलीवरी पाइपलाइन में स्नैपशॉट्स या रोलबैक सपोर्ट करते हैं, तो वे इस सोच के साथ नेचुरली जोड़ते हैं: ड्रिफ्ट जल्दी पकड़ें, फिर क्लाइंट्स को मिड‑इन्सिडेंट एडॉप्ट कराने के बिना वापस लौटाएँ। (उदाहरण के रूप में, Koder.ai स्नैपशॉट्स और रोलबैक को वर्कफ़्लो का हिस्सा बनाता है, जो "कॉन्ट्रैक्ट्स पहले, बदलाव बाद में" दृष्टिकोण के साथ मेल खाता है।)
एब्स्ट्रैक्शन को महत्व देने वाली टीमें भी उन पैटर्न्स में फिसल जाती हैं जो पल में "प्रायोगिक" लगते हैं पर धीरे‑धीरे API को स्पेशल‑केस का बंडल बना देते हैं। यहाँ कुछ आवर्ती जाल और उनके विकल्प हैं।
फ़ीचर फ्लैग्स रोलआउट के लिए अच्छे हैं, पर समस्या तब शुरू होती है जब फ्लैग सार्वजनिक, लंबे जीवन वाले पैरामीटर बन जाते हैं: ?useNewPricing=true, mode=legacy, v2=true। समय के साथ, कॉलर्स इन्हें अनपेक्षित तरीकों से जोड़ते हैं, और आप अंततः कई व्यवहारों का समर्थन करते हुए फंस जाते हैं।
सुरक्षित दृष्टिकोण:
ऐसे APIs जो टेबल IDs, जॉइन कीज़, या "SQL‑आकार" फिल्टर्स (उदा., where=...) एक्सपोज़ करते हैं, क्लाइंट्स को आपके स्टोरेज मॉडल सीखने के लिए मजबूर करते हैं। इससे रिफैक्टर्स दर्दनाक हो जाते हैं: स्कीमा चेंज एक API ब्रेक बन जाता है।
इसके बजाय, इंटरफ़ेस को डोमेन कॉन्सेप्ट्स और स्थिर पहचानकर्ताओं के चारों ओर मॉडल करें। क्लाइंट्स को वह पूछने दें जो वे मतलब रखते हैं ("किस ग्राहक के ऑर्डर एक तारीख रेंज में"), न कि यह कि आप उसे कैसे स्टोर करते हैं।
एक फील्ड जोड़ना हानिरहित लगता है, पर बार‑बार "एक और फील्ड" परिवर्तनों से जिम्मेदारियाँ धुंधली हो सकती हैं और इनवेरिएंट कमजोर हो सकते हैं। क्लाइंट्स आकस्मिक विवरणों पर निर्भर होने लगते हैं और प्रकार एक मिश्रित‑बैग बन जाता है।
दीर्घकालिक लागत से बचें:
अतिरिक्त एब्स्ट्रैक्शन वास्तविक ज़रूरतों को ब्लॉक कर सकती है—जैसे पेजिनेशन जो "इस कर्सर के बाद शुरू करें" व्यक्त नहीं कर सकती, या एक सर्च एन्डपॉइंट जो "एक्युरेट मैच" निर्दिष्ट नहीं कर सकता। क्लाइंट्स तब आपके चारों ओर वर्कअराउंड बनाते हैं (कई कॉल, लोकल फिल्टर), जिससे प्रदर्शन और त्रुटियाँ बिगड़ती हैं।
समाधान नियंत्रित लचीलापन है: ओपन‑एंडेड एस्केप हैच की बजाय कुछ स्पष्ट विस्तार बिंदु दें (उदा., समर्थित फिल्टर ऑपरेटर्स)।
सरल करना शक्ति छीनने का मतलब नहीं होना चाहिए। भ्रमित विकल्पों को डिप्रिकेट करें, पर मूल क्षमता को एक स्पष्ट रूप से पुनर्निर्धारित शेप के माध्यम से रखें: कई ओवरलैपिंग पैरामीटर्स को एक संरचित रिक्वेस्ट ऑब्जेक्ट से बदलें, या एक "सब कुछ करो" एन्डपॉइंट को दो कोहेसिव एन्डपॉइंट्स में विभाजित करें। फिर वर्ज़नड डॉक और स्पष्ट डिप्रेक्शन टाइमलाइन के साथ माइग्रेशन मार्गदर्शन दें (देखें /blog/evolving-apis-without-breaking-users)।
आप लिस्कोव के डेटा एब्स्ट्रैक्शन विचारों को एक सरल, दोहराने योग्य चेकलिस्ट से लागू कर सकते हैं। लक्ष्य पूर्णता नहीं—बल्कि API के वादों को स्पष्ट, टेस्टेबल, और सुरक्षित रूप से विकसित करने योग्य बनाना है।
छोटे, सुसंगत ब्लॉक्स का उपयोग करें:
transfer(from, to, amount)amount > 0 और अकाउंट्स मौजूद होंInsufficientFunds, AccountNotFound, Timeoutयदि आप गहराई में जाना चाहते हैं, तो देखें: Abstract Data Types (ADTs), Design by Contract, और Liskov Substitution Principle (LSP).
यदि आपकी टीम आंतरिक नोट्स रखती है, तो उन्हें /docs/api-guidelines जैसे पेज से लिंक करें ताकि रिव्यू वर्कफ़्लो आसानी से दोहराया जा सके—और यदि आप नए सर्विस तेज़ी से बनाते हैं (हाथ से या किसी चैट‑ड्रिवन बिल्डर के साथ जैसे Koder.ai), तो उन गाइडलाइन्स को "शीघ्रता से शिप करना" का अपरिहार्य हिस्सा समझें। विश्वसनीय इंटरफ़ेस वही हैं जिनसे गति गुणा बनती है न कि उल्टा।
उन्होंने डेटा एब्स्ट्रैक्शन और सूचना छुपाने को लोकप्रिय बनाया, जो आधुनिक API डिज़ाइन से सीधे जुड़ते हैं: एक छोटा, स्थिर कॉन्ट्रैक्ट प्रकाशित करें और इम्प्लिमेंटेशन को लचीला रखें। इसका व्यावहारिक लाभ है: कम ब्रेकिंग चेंजेज, सुरक्षित रिफैक्टर्स, और अधिक पूर्वानुमानयोग्य इंटीग्रेशन।
एक विश्वसनीय API वह है जिस पर कॉल करने वाले समय के साथ भरोसा कर सकें:
विश्वसनीयता का मतलब यह नहीं कि "कभी फेल न हो" बल्कि यह कि फेल होने पर यह पूर्वानुमेय तरीके से हो और कॉन्ट्रैक्ट का सम्मान करे।
बिहेवियर को एक कॉन्ट्रैक्ट के रूप में लिखें:
एज केस भी शामिल करें (खाली रिज़ल्ट, डुप्लिकेट, ऑर्डरिंग) ताकि कॉलर दावा कर सकें और टेस्ट कर सकें।
इनवेरिएंट वह नियम है जो किसी एब्स्ट्रैक्शन के अंदर हमेशा सच होना चाहिए (उदा., “quantity कभी नकारात्मक नहीं होगा”)। इन्हें बॉर्डर पर लागू करें:
यह डाउनस्ट्रीम बग्स घटाता है क्योंकि सिस्टम के बाकी हिस्सों को असंभव स्टेट्स से निपटना नहीं पड़ता।
सूचना छुपाना का मतलब है ऑपरेशन्स और अर्थ दिखाना, इम्प्लिमेंटेशन नहीं। कंज्यूमर को उन चीज़ों से कपल न करें जिन्हें आप बाद में बदल सकते हैं (टेबल्स, कैश, शार्ड कीज़, इंटरनल स्टेट)।
व्यावहारिक उपाय:
usr_...) का उपयोग करें, डेटाबेस रो नंबर की बजाय।status=3)।क्योंकि वे आपकी इम्प्लिमेंटेशन को 'फ्रीज़' कर देते हैं। अगर क्लाइंट्स डेटाबेस-आकृति फिल्टर्स, जॉइन कीज़, या इंटरनल IDs पर निर्भर करते हैं, तो स्कीमा रिफैक्टर करना API ब्रेकिंग चेंज बन जाता है.
इसके बजाय, स्टोरेज-आधारित सवालों की जगह डोमेन-क्वेरीज़ मोडल करें, जैसे “किस ग्राहक के ऑर्डर एक तारीख़ रेंज में” और स्टोरेज मॉडल को कॉन्ट्रैक्ट के पीछे रखें।
LSP का अर्थ: अगर कोई कोड किसी इंटरफ़ेस के साथ काम करता है, तो किसी भी वैध इम्प्लिमेंटेशन को डालने पर भी वही कोड बिना स्पेशल केस के काम करना चाहिए। API शर्तों में इसका मतलब है: कॉलर को आश्चर्य में न डालना।
स्थापित करने के लिए:
सामान्य LSP उल्लंघन देखें:
अगर किसी इम्प्लिमेंटेशन को अतिरिक्त कड़े नियम चाहिए, अलग इंटरफ़ेस या स्पष्ट क्षमता-फ़्लैग पब्लिश करें ताकि क्लाइंटस जानबूझकर ऑप्ट-इन करें।
इंटरफ़ेस को छोटा और कोहेसिव रखें:
options: any या कई बूलियन से बचें—ये अस्पष्ट संयोजन बनाते हैं।reserve, release, list, ).एरर हैंडलिंग को कॉन्ट्रैक्ट का हिस्सा बनाएं:
स्टेबल एरर कोड और स्ट्रक्चर्ड फील्ड्स दें ताकि टेस्ट संदेश-मैचिंग पर निर्भर न हों। रिट्राई सेफ्टी और आइडेमपोटेंसी (किसी ऑपरेशन को सुरक्षित रूप से दोहराया जा सकता है या नहीं) स्पष्ट करें।
validateअगर अलग रोल्स या अलग परिवर्तन-रेट हैं, तो मॉड्यूल/रिसोर्सेस को अलग करें।