पाठ 16 / 27

तोड़ने वाला बदलाव क्या है

Backward-compatible जोड़ को उन बदलावों से अलग करें जो मौजूदा clients तोड़ते हैं, और उन्हें अपने आप पहचानें।

सुरक्षित बनाम तोड़ने वाला

आम तौर पर सुरक्षित (जोड़ने वाले): नया वैकल्पिक request field, नया response field, नया endpoint, नया वैकल्पिक query parameter, और response में नया enum मान, केवल तब जब clients को अज्ञात मानों की अपेक्षा रखने को कहा गया हो। तोड़ने वाले: field हटाना या नाम बदलना, field का type या अर्थ बदलना, वैकल्पिक input को अनिवार्य करना, अनुमत मान घटाना, status codes या error formats बदलना, authentication बदलना, या डिफ़ॉल्ट व्यवहार (जैसे डिफ़ॉल्ट पन्ना आकार या sort क्रम) बदलना। Clients को tolerant reader सिद्धांत (अज्ञात fields अनदेखे करना) और servers को inputs के लिए robustness सिद्धांत अपनाना चाहिए। CI में स्वचालित contract diff चलाएँ (पुरानी और नई OpenAPI spec की तुलना) ताकि कोई तोड़ने वाला बदलाव अनजाने में शिप न हो।

तोड़े बिना बदलें

ज़्यादातर बदलाव जोड़ने वाले हो सकते हैं। जब बदलाव को तोड़ना ही पड़े, तब version पुराने और नए clients को साथ रहने देता है।

चार विचार: जोड़ने वाला, पहचान, version, सह-अस्तित्व।
चित्र 5.1 — जोड़ने वाला, पहचान, version और सह-अस्तित्व।

तोड़ने वाले बदलाव का detector, चलाकर

मैंने यह सादा-Python उदाहरण चलाया। वैकल्पिक nickname field जोड़ना compatible है। दूसरा बदलाव-समूह चार कारणों से चिह्नित होता है: id int से string हुआ, एक enum मान हटा, name हटा, और नया अनिवार्य field आया।

import hmac, hashlib, json, time, bisect

def breaking_changes(old, new):
    out = []
    for name, spec in old["fields"].items():
        if name not in new["fields"]: out.append(f"removed field '{name}'")
        else:
            n = new["fields"][name]
            if n["type"] != spec["type"]: out.append(f"type of '{name}' changed {spec['type']} -> {n['type']}")
            if set(spec.get("enum", [])) - set(n.get("enum", spec.get("enum", []))): out.append(f"enum value removed from '{name}'")
    for name, spec in new["fields"].items():
        if name not in old["fields"] and spec.get("required"): out.append(f"new required field '{name}'")
    return out or ["compatible"]
v1 = {"fields": {"id": {"type": "int"}, "plan": {"type": "string", "enum": ["free", "pro"]}, "name": {"type": "string"}}}
v2a = {"fields": {**v1["fields"], "nickname": {"type": "string"}}}
v2b = {"fields": {"id": {"type": "string"}, "plan": {"type": "string", "enum": ["pro"]}, "tier": {"type": "string", "required": True}}}
print(breaking_changes(v1, v2a)); print(breaking_changes(v1, v2b))

Output:

['compatible']
["type of 'id' changed int -> string", "enum value removed from 'plan'", "removed field 'name'", "new required field 'tier'"]

अर्थ को अनुबंध का हिस्सा मानें

"amount" को रुपये से पैसे में बदलने पर type वही रहता है पर हर client टूटता है। व्यवहार और इकाइयाँ भी अनुबंध हैं, इसलिए बदलावों में उनकी समीक्षा field नामों की तरह करें।

त्वरित जाँच: कौन-सा बदलाव तोड़ने वाला है?

  • नया endpoint जोड़ना
  • Response में नया वैकल्पिक field जोड़ना
  • Response से कोई field हटाना
  • वैकल्पिक query parameter जोड़ना
Answer

Response से कोई field हटाना — मौजूदा clients हटाए गए field को पढ़ सकते हैं, इसलिए उसका हटना उन्हें तोड़ता है।