# OpenAPI के साथ Contract-First — API Design और Versioning

Source: https://www.geekswithgeeks.com/hi/api-design/qg-openapi

> API को OpenAPI दस्तावेज़ में वर्णित करें और उसे अपने आप lint व diff करें।

## एक दस्तावेज़, कई उपयोग

**OpenAPI** दस्तावेज़ (YAML या JSON) paths, parameters, request व response schemas, errors और सुरक्षा को मशीन-पठनीय रूप में वर्णित करता है। इसे **पहले** डिज़ाइन करने (contract-first) से टीमें implementation से पहले API की समीक्षा और mock कर सकती हैं, और वही फ़ाइल फिर **interactive docs**, **client SDK और server stub generation**, **request/response validation**, **contract tests** और versions के बीच **तोड़ने वाले बदलाव की जाँच** चलाती है। `components` के अंतर्गत `$ref` से परिभाषाएँ दोबारा उपयोग करें, हर operation को अनोखा `operationId` दें, और error responses भी वर्णित करें। Spec को कोड के पास version control में रखें और implementation उससे भटके तो build विफल करें।

## वादे को कोड में रखें

मशीन-पठनीय अनुबंध, स्वचालित जाँचें और साझा मानक कई टीमों को एकरूप रखते हैं।

![तीन परतें: spec, tests, मानक।](assets/figures/api-design/section-7-map.svg) — चित्र 7.1 — Spec, tests और मानक।

## छोटा OpenAPI 3.1 दस्तावेज़

v2 की list और get endpoints को पुनः उपयोग योग्य `Item` schema, problem+json error और rate-limit response के साथ वर्णित करता है। मैंने इसे अगले snippet में PyYAML से parse किया।

```yaml
openapi: 3.1.0
info:
  title: Items API
  version: "2.0.0"
servers:
  - url: https://api.example.com
paths:
  /v2/items:
    get:
      operationId: listItems
      summary: List items (cursor pagination)
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: A page of items
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ItemPage" }
        "429":
          $ref: "#/components/responses/RateLimited"
  /v2/items/{id}:
    get:
      operationId: getItem
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: One item
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Item" }
        "404":
          description: Not found
          content:
            application/problem+json:
              schema: { $ref: "#/components/schemas/Problem" }
components:
  schemas:
    Item:
      type: object
      required: [id, first_name, last_name]
      properties:
        id: { type: integer }
        first_name: { type: string }
        last_name: { type: string }
        plan: { type: string, enum: [free, pro] }
    ItemPage:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items: { $ref: "#/components/schemas/Item" }
        next_cursor: { type: [string, "null"] }
    Problem:
      type: object
      required: [title, status]
      properties:
        type: { type: string }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
  responses:
    RateLimited:
      description: Too many requests
      headers:
        Retry-After: { schema: { type: integer } }

```

## Spec की संरचना जाँचना, चलाकर

मैंने यह सादा-Python उदाहरण चलाया। ऊपर के YAML को `openapi.yaml` नाम से सहेजें; यह script उसे लोड करती है, operations सूचीबद्ध करती है, `operationId` की अनोखापन जाँचती है और पुष्टि करती है कि सभी 5 `$ref` संकेतक दस्तावेज़ के भीतर resolve होते हैं। यह सिखाने की जाँच है; असली projects समर्पित validators और linters उपयोग करते हैं।

```python
import yaml
spec = yaml.safe_load(open("openapi.yaml"))
ops, refs = [], []
def walk(o):
    if isinstance(o, dict):
        if "$ref" in o: refs.append(o["$ref"])
        for v in o.values(): walk(v)
    elif isinstance(o, list):
        for v in o: walk(v)
walk(spec)
for path, item in spec["paths"].items():
    for method, op in item.items(): ops.append((method.upper(), path, op["operationId"]))
def resolve(ref):
    node = spec
    for part in ref.lstrip("#/").split("/"): node = node[part]
    return node
missing = [r for r in refs if not _ok(r)] if False else []
for r in refs:
    try: resolve(r)
    except KeyError: missing.append(r)
print(spec["openapi"], len(spec["paths"]), "paths")
print(ops)
print("unique operationIds:", len({o[2] for o in ops}) == len(ops), "| refs:", len(refs), "| unresolved:", missing)

```

Output:

```
3.1.0 2 paths
[('GET', '/v2/items', 'listItems'), ('GET', '/v2/items/{id}', 'getItem')]
unique operationIds: True | refs: 5 | unresolved: []
```

**Quiz:** पहले OpenAPI अनुबंध डिज़ाइन करने का लाभ क्या है?

- [x] टीमें implementation से पहले समीक्षा, mock और कोड generate कर सकती हैं
- [ ] यह किसी भी कोड की ज़रूरत हटाता है
- [ ] यह APIs को धीमा करता है
- [ ] यह सारे bugs रोकता है

*Answer:* टीमें implementation से पहले समीक्षा, mock और कोड generate कर सकती हैं. साझा, समीक्षा योग्य अनुबंध उत्पादकों और उपभोक्ताओं को जल्दी एकमत करता है।
