---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# Chapitre 20 — Bonnes pratiques et synthèse

Ce chapitre de clôture rassemble les décisions récurrentes sous forme de checklists opérationnelles, identifie les erreurs les plus coûteuses à corriger en production, et propose une perspective sur ce que signifie traiter une API comme un produit. Il se termine par une synthèse du parcours de ce livre et des recommandations pour la suite.

## Checklist de conception

Avant d'écrire la première ligne de code, répondre à ces questions. Une réponse manquante est une dette de conception.

### Ressources et nommage

- Les ressources sont-elles identifiées par des noms (substantifs) et non par des verbes ?
- Les noms sont-ils en snake_case pluriels ? (`/orders`, `/line_items`, pas `/getOrder`)
- La hiérarchie n'excède-t-elle pas deux niveaux ? (`/orders/{id}/items` mais pas `/companies/{id}/departments/{id}/teams/{id}/members`)
- Les identifiants sont-ils opaques et non prédictibles ? (UUID plutôt que IDs séquentiels exposés)

### Authentification et autorisation

- Chaque endpoint a-t-il un mécanisme d'authentification documenté ?
- Les scopes OAuth sont-ils définis au niveau minimal nécessaire (principe de moindre privilège) ?
- Les permissions sont-elles vérifiées sur les données retournées, pas seulement sur le endpoint (`GET /users` retourne-t-il seulement les utilisateurs que le requérant a le droit de voir) ?

### Versioning et évolution

- La stratégie de versioning est-elle choisie (URI, header) et documentée ?
- Les champs de réponse sont-ils tous optionnels ou documentés comme pouvant être ajoutés ?
- `additionalProperties` est-il laissé à `true` dans les schémas de réponse ?

### Erreurs et idempotence

- Toutes les erreurs respectent-elles le format RFC 7807 (Problem Details) ?
- Les codes HTTP sont-ils corrects (`201` pour création, `204` pour suppression, `422` pour validation échouée) ?
- Les opérations de mutation supportent-elles l'idempotence via `Idempotency-Key` ?
- Les timeouts sont-ils définis côté serveur pour chaque dépendance externe ?

## Checklist de sécurité

### OWASP API Security Top 10 (synthèse)

1. **Broken Object Level Authorization (BOLA)** : vérifier que l'utilisateur A ne peut pas accéder aux données de l'utilisateur B en changeant un ID dans l'URL.
2. **Broken Authentication** : tokens non expirés, secrets dans les URLs, endpoints d'admin sans auth.
3. **Broken Object Property Level Authorization** : ne pas retourner les champs sensibles (`password_hash`, données bancaires) dans les réponses.
4. **Unrestricted Resource Consumption** : rate limiting, pagination obligatoire, limite de taille des payloads.
5. **Broken Function Level Authorization** : vérifier les rôles sur les opérations d'admin, pas seulement sur les endpoints de lecture.
6. **Unrestricted Access to Sensitive Business Flows** : protéger les flows critiques (création de compte, paiement) contre l'automatisation abusive.
7. **Server Side Request Forgery** : valider les URLs fournies par les clients avant de les appeler.
8. **Security Misconfiguration** : CORS strict, headers de sécurité, pas de TRACE activé.
9. **Improper Inventory Management** : documenter et désactiver les endpoints de test/legacy.
10. **Unsafe Consumption of APIs** : valider les réponses des APIs tierces avant de les transmettre.

### Headers de sécurité

```python
# Middleware FastAPI — headers de sécurité
@app.middleware("http")
async def add_security_headers(request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"]  = "nosniff"
    response.headers["X-Frame-Options"]         = "DENY"
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    response.headers["Content-Security-Policy"] = "default-src 'none'"
    response.headers["Cache-Control"]           = "no-store"
    return response
```

### Rate limiting

Implémenter le rate limiting à plusieurs niveaux :
- **Par IP** : protection contre les attaques DDoS basiques
- **Par API key / token** : protection de l'utilisation abusive par client
- **Par endpoint** : les endpoints sensibles (login, reset password) ont des limites plus strictes

```{admonition} Réponse rate limiting
:class: tip
Toujours retourner les headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` dans chaque réponse — pas seulement quand la limite est dépassée. Cela permet aux clients de s'adapter proactivement.
```

### Validation des entrées

- Valider la taille des payloads (`Content-Length` max)
- Valider les types et formats de chaque champ (pas confiance aux `Content-Type` déclarés)
- Rejeter les champs inconnus dans les requêtes (schéma strict en entrée, libéral en sortie)
- Sanitiser les chaînes utilisées dans des requêtes SQL, des commandes shell ou du XML

## Checklist d'observabilité

### Logs

- Logs en JSON structuré avec `timestamp`, `level`, `trace_id`, `request_id`, `user_id`, `path`, `status`, `duration_ms`
- Niveau `WARNING` pour les 4xx, `ERROR` pour les 5xx
- Pas de données sensibles dans les logs (tokens, mots de passe, PAN)
- Logs corrélés via `trace_id` propagé entre services

### Métriques

- Rate (req/s) par endpoint, version, status code
- Taux d'erreur 5xx et 4xx séparément
- Latence p50/p95/p99 par endpoint
- Métriques des dépendances (DB pool usage, cache hit ratio, appels d'APIs tierces)

### Traces

- OpenTelemetry configuré avec export vers un backend (Jaeger, Tempo, Datadog)
- W3C Trace Context propagé dans tous les appels sortants
- Spans manuels sur les opérations métier critiques

### SLO

- Au moins un SLO de disponibilité défini (ex : 99.9% des requêtes != 5xx)
- Au moins un SLO de latence défini (ex : p95 < 200 ms)
- Error budget calculé et suivi
- Alertes sur le burn rate de l'error budget

## Erreurs courantes

### CRUD direct sur la base de données

L'erreur la plus répandue : l'API est un proxy direct vers les tables SQL. Chaque table devient un endpoint, chaque colonne devient un champ.

**Problèmes :**
- Le schéma de BDD devient un contrat public — impossible de le refactoriser
- Pas de logique métier dans les handlers
- Les données sont exposées sans filtrage ni validation métier

**Solution :** modéliser les APIs autour des opérations métier et des agrégats de domaine, pas autour des tables. `POST /orders` déclenche un processus (validation, réservation de stock, notification) — pas un `INSERT INTO orders`.

### Endpoint "fourre-tout"

Un endpoint qui fait trop de choses selon les paramètres passés. Typiquement `POST /process` ou `GET /data` qui se comportent différemment selon un paramètre `action` ou `type`.

```{admonition} Anti-pattern god endpoint
:class: warning
Un endpoint qui prend 15 paramètres query et qui change complètement de comportement selon leur combinaison est un signal d'alarme. Chaque cas d'usage distinct mérite son propre endpoint avec son propre contrat documenté.
```

### Sur-versioning

Créer une nouvelle version MAJOR pour chaque changement, même non-breaking. Résultat : des dizaines de versions en production, un coût de maintenance prohibitif, des consommateurs qui ne migrent pas.

**Solution :** utiliser les changements additifs (ajout de champs optionnels) tant que possible. Réserver les versions MAJOR aux vrais breaking changes.

### Secrets dans les URLs

```
# NE JAMAIS FAIRE
GET /api/data?token=sk_live_abc123&user_id=42

# Les URLs apparaissent dans :
# - Les logs des proxys et load balancers
# - L'historique du navigateur
# - Les logs du serveur web
# - Les referrer headers lors des redirections
```

Les tokens d'authentification vont dans le header `Authorization`. Les clés API vont dans les headers, pas dans les query params.

### Timeout absent

Appeler une dépendance externe (BDD, API tierce, service interne) sans définir de timeout revient à laisser un handler potentiellement bloqué à l'infini. Un seul service lent peut épuiser le pool de connexions et mettre en cascade toute l'application.

```python
# Toujours définir un timeout
import httpx

async with httpx.AsyncClient(timeout=5.0) as client:
    response = await client.get("https://external-api.example.com/data")

# Pour les requêtes SQL avec SQLAlchemy
engine = create_engine(DATABASE_URL, connect_args={"connect_timeout": 5})
```

## API as a product

Traiter une API comme un produit signifie que ses consommateurs — internes ou externes — sont des clients. Leur expérience compte autant que les fonctionnalités.

### Documentation comme livrable

La documentation n'est pas une tâche en fin de sprint. Elle fait partie de la définition de "terminé". Une API non documentée est une API inutilisable pour un nouveau consommateur.

Documentation minimale pour chaque endpoint :
- Description de l'opération en une phrase
- Paramètres avec types, contraintes, valeurs par défaut
- Exemples de requête et réponse réalistes
- Codes d'erreur possibles avec messages explicatifs

### Developer experience

La DX (*Developer Experience*) se mesure principalement par le *time to first successful call* : combien de temps faut-il à un développeur qui découvre l'API pour obtenir sa première réponse réussie ?

Facteurs qui améliorent la DX :
- **Quickstart** en moins de 5 minutes avec copier-coller
- **Messages d'erreur actionnables** (`"Missing required field: email"` plutôt que `"Bad request"`)
- **SDK officiels** dans les langages populaires du public cible
- **Environnement sandbox** avec données de test réalistes
- **Changelog** à jour avec les dates et les migrations

### Feedback loop

Les APIs qui évoluent sans feedback des consommateurs accumulent de la dette d'expérience. Mécanismes de feedback :

- Suivi des endpoints les plus appelés et les plus en erreur (proxy vers les priorités)
- Canal direct avec les équipes consommatrices (Slack, Discord, forum)
- Issues GitHub pour les APIs open source
- NPS developer pour les APIs publiques

### Métriques d'adoption

```{admonition} Métriques produit vs métriques techniques
:class: note
Les métriques techniques (latence, taux d'erreur) mesurent la santé de l'API. Les métriques produit mesurent sa valeur : nombre de consommateurs actifs, rétention mensuelle, time to first call moyen, pourcentage de consommateurs sur la dernière version majeure.
```

## Gouvernance à l'échelle

Quand une organisation dépasse une dizaine d'équipes qui produisent des APIs, la cohérence entre ces APIs devient un enjeu majeur. Sans gouvernance, chaque équipe réinvente ses propres conventions.

### API registry

Un registre centralisé des APIs de l'organisation : URL de la spec OpenAPI, équipe responsable, version courante, statut (actif/déprécié/sunset), métriques d'utilisation. Outils : Backstage (Spotify OSS), Apigee, Kong Manager.

### Style guide partagé

Un style guide unique, maintenu par un centre d'excellence, appliqué via Spectral en CI sur toutes les repos. Les exceptions sont documentées et justifiées via ADR.

### Breaking change policy

Règles décisionnelles claires sur ce qui constitue un breaking change et quel processus appliquer :
- Pas de breaking change sans période de coexistence d'au moins 6 mois
- Notification des consommateurs au moins 3 mois avant le sunset
- Toute exception nécessite une approbation de l'architecte plateforme

### Center of excellence API

Un petit groupe transverse (2–4 personnes) qui :
- Maintient le style guide et les outils de linting
- Fait les design reviews pour les nouvelles APIs ou les MAJOR bumps
- Mesure la qualité des APIs de l'organisation (radar de maturité)
- Forme les nouvelles équipes aux pratiques

## Prochaines étapes

### Livres recommandés

**Designing Web APIs** (Brenda Jin, Saurabh Sahni, Amir Shevat, O'Reilly) : approche orientée product management, cas pratiques Slack/Stripe/Twilio.

**Build APIs You Won't Hate** (Phil Sturgeon, Leanpub) : approche pragmatique, exemples PHP mais principes universels, excellent sur les conventions REST.

**API Design Patterns** (JJ Geewax, Manning) : patterns avancés, ressources composites, méthodes personnalisées, design for scale.

**The Design of Web APIs** (Arnaud Lauret, Manning) : le plus complet sur le processus de design API-first, idéal pour construire un style guide.

### Certifications et communautés

- **API Academy** (API days) : certification API Design et Architecture
- **OpenAPI Initiative** (openapis.org) : suivi de la spec OpenAPI 3.x et 4.0
- **APIDays conference** : conférence annuelle sur les APIs (Paris, New York, Sydney)
- **Nordic APIs** : blog et podcasts de référence sur les pratiques API
- **IETF HTTP Working Group** : pour suivre l'évolution des RFCs (Problem Details, Deprecation, etc.)

## Résumé du parcours

Ce livre a couvert l'ensemble du spectre de conception d'une API web moderne.

**Fondations (chapitres 1–5)** : HTTP comme protocole de transport, authentification et autorisation (OAuth 2.0, JWT, API keys), sécurité des APIs, principes de design REST, REST avancé (HATEOAS, idempotence, caching).

**Outillage (chapitres 6–10)** : conventions REST, OpenAPI 3.1, tests des APIs, clients HTTP, architecture des APIs publiques vs internes.

**Patterns architecturaux (chapitres 11–15)** : GraphQL et ses trade-offs, gRPC et Protocol Buffers, WebSockets, API Gateway, microservices et communication entre services.

**Maturité (chapitres 16–20)** : versioning et évolution, observabilité (logs/métriques/traces/SLO), API-first et workflow de design, patterns avancés (long-running, PATCH, bulk, upload, streaming), bonnes pratiques et gouvernance.

### Récapitulatif des choix technologiques

| Besoin | Technologie recommandée |
|---|---|
| API CRUD standard | REST + JSON + OpenAPI |
| Requêtes flexibles, clients multiples | GraphQL |
| Communication inter-services | gRPC + Proto |
| Temps réel bidirectionnel | WebSocket |
| Événements serveur → client | SSE |
| Upload volumineux | Presigned URL S3 + TUS |
| Mises à jour partielles simples | JSON Merge Patch |
| Mises à jour complexes / arrays | JSON Patch |
| Opérations longues | Polling 202 + Location |
| Documentation et contrat | OpenAPI 3.1 |
| Mock en développement | Prism |
| Linting du style guide | Spectral |
| Observabilité | OpenTelemetry + Prometheus |

---

## Cellules exécutables

### Radar de maturité API

```{code-cell} python3
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.0)

dimensions = [
    "REST &\nHTTP",
    "Sécurité",
    "Versioning",
    "Tests",
    "Observabilité",
    "Documentation",
    "Developer\nExperience",
    "Gouvernance"
]

# Trois profils d'équipes
profiles = {
    "Équipe débutante":      [2, 2, 1, 2, 1, 2, 1, 1],
    "Équipe intermédiaire":  [4, 3, 3, 4, 3, 3, 3, 2],
    "Équipe avancée":        [5, 5, 4, 5, 5, 5, 4, 4],
}
colors = ["#dd8452", "#4c72b0", "#55a868"]

N = len(dimensions)
angles = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist()
angles += angles[:1]  # fermer le polygone

fig, ax = plt.subplots(figsize=(9, 9), subplot_kw={"polar": True})

for (label, values), color in zip(profiles.items(), colors):
    v = values + values[:1]
    ax.plot(angles, v, "o-", linewidth=2, color=color, label=label)
    ax.fill(angles, v, alpha=0.12, color=color)

# Axes
ax.set_xticks(angles[:-1])
ax.set_xticklabels(dimensions, fontsize=9)
ax.set_ylim(0, 5)
ax.set_yticks([1, 2, 3, 4, 5])
ax.set_yticklabels(["1", "2", "3", "4", "5"], fontsize=7, color="gray")
ax.set_rlabel_position(30)

ax.legend(loc="upper right", bbox_to_anchor=(1.3, 1.1), fontsize=9)
ax.set_title("Radar de maturité API — 8 dimensions", fontsize=12,
             fontweight="bold", pad=20)

plt.show()
```

### Decision tree — quelle technologie API

```{code-cell} python3
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", font_scale=0.9)

fig, ax = plt.subplots(figsize=(14, 9))
ax.axis("off")
ax.set_xlim(0, 14)
ax.set_ylim(0, 9.5)

def box(ax, x, y, label, color, width=2.2, height=0.7):
    rect = mpatches.FancyBboxPatch(
        (x - width / 2, y - height / 2), width, height,
        boxstyle="round,pad=0.08",
        facecolor=color, edgecolor="#555555", linewidth=1.5
    )
    ax.add_patch(rect)
    ax.text(x, y, label, ha="center", va="center", fontsize=8.5, fontweight="bold",
            wrap=True)

def diamond(ax, x, y, label, color="#fff2cc"):
    pts = np.array([[x, y + 0.55], [x + 1.4, y], [x, y - 0.55], [x - 1.4, y]])
    poly = plt.Polygon(pts, facecolor=color, edgecolor="#555555", linewidth=1.5)
    ax.add_patch(poly)
    ax.text(x, y, label, ha="center", va="center", fontsize=8, fontweight="bold")

def arrow(ax, x1, y1, x2, y2, label="", color="#555555"):
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle="->", color=color, lw=1.4))
    if label:
        mx = (x1 + x2) / 2
        my = (y1 + y2) / 2
        ax.text(mx + 0.1, my, label, fontsize=7.5, color=color, va="center")

import numpy as np

# Départ
box(ax, 7, 9, "Nouvelle API à\nconstruire ?", "#d9d9d9", width=2.4)
diamond(ax, 7, 7.8, "Temps réel\nbidirectionnel ?")
box(ax, 11.5, 7.8, "WebSocket", "#aec7e8", width=2.0)
diamond(ax, 7, 6.5, "Communication\ninter-services ?")
box(ax, 11.5, 6.5, "gRPC + Proto", "#ffbb78", width=2.0)
diamond(ax, 7, 5.2, "Clients multiples\nbesoins différents ?")
box(ax, 11.5, 5.2, "GraphQL", "#c5b0d5", width=2.0)
diamond(ax, 7, 3.9, "Streaming\nde données ?")
box(ax, 11.5, 3.9, "SSE ou\nStreaming REST", "#98df8a", width=2.0)
box(ax, 7, 2.6, "REST + JSON\n+ OpenAPI", "#f7b6d2", width=2.4, height=0.9)
box(ax, 2.5, 7.8, "Chat, gaming,\ncollab temps réel", "#e8e8e8", width=2.4)
box(ax, 2.5, 6.5, "Microservices,\nbasse latence", "#e8e8e8", width=2.4)
box(ax, 2.5, 5.2, "BFF mobile/web,\nagrégation", "#e8e8e8", width=2.4)
box(ax, 2.5, 3.9, "Rapports, export,\nIA inference", "#e8e8e8", width=2.4)

# Flèches décision
arrow(ax, 7, 8.65, 7, 8.1)
arrow(ax, 7, 7.5, 7, 6.8, "Non")
arrow(ax, 8.4, 7.8, 10.4, 7.8, "Oui")
arrow(ax, 7, 6.2, 7, 5.5, "Non")
arrow(ax, 8.4, 6.5, 10.4, 6.5, "Oui")
arrow(ax, 7, 4.9, 7, 4.2, "Non")
arrow(ax, 8.4, 5.2, 10.4, 5.2, "Oui")
arrow(ax, 7, 3.6, 7, 3.1, "Non")
arrow(ax, 8.4, 3.9, 10.4, 3.9, "Oui")
arrow(ax, 5.6, 7.8, 3.7, 7.8, "Exemples →")
arrow(ax, 5.6, 6.5, 3.7, 6.5, "Exemples →")
arrow(ax, 5.6, 5.2, 3.7, 5.2, "Exemples →")
arrow(ax, 5.6, 3.9, 3.7, 3.9, "Exemples →")

ax.set_title("Decision tree — quelle technologie API choisir ?",
             fontsize=12, fontweight="bold", pad=8)
plt.show()
```

### Score de qualité d'une API

```{code-cell} python3
from typing import NamedTuple

class Criterion(NamedTuple):
    name: str
    weight: float    # Importance relative (somme = 1.0)
    description: str

CRITERIA = [
    Criterion("Design REST",         0.15, "Nommage, verbes HTTP, codes de retour"),
    Criterion("Sécurité",            0.20, "Auth, OWASP top 10, rate limiting"),
    Criterion("Documentation",       0.15, "OpenAPI, exemples, changelog"),
    Criterion("Tests",               0.10, "Couverture, tests de contrat, tests E2E"),
    Criterion("Observabilité",       0.15, "Logs structurés, métriques RED, traces, SLO"),
    Criterion("Versioning",          0.10, "Stratégie claire, backward compat, déprécation"),
    Criterion("Developer Experience",0.10, "Time to first call, messages d'erreur, sandbox"),
    Criterion("Performances",        0.05, "Latence p99, pagination, caching"),
]

def score_api(scores: dict[str, float]) -> dict:
    """
    Calcule un score de qualité pondéré.
    scores : {nom_critère: score_0_à_5}
    """
    total_score   = 0.0
    total_weight  = 0.0
    detailed      = []

    for criterion in CRITERIA:
        raw = scores.get(criterion.name, 0)
        weighted = raw * criterion.weight
        total_score  += weighted
        total_weight += criterion.weight
        detailed.append({
            "criterion":   criterion.name,
            "weight":      criterion.weight,
            "raw_score":   raw,
            "weighted":    weighted,
            "description": criterion.description,
        })

    normalized = (total_score / total_weight / 5) * 100  # 0–100

    if normalized >= 80:
        grade = "A — Production-ready"
    elif normalized >= 65:
        grade = "B — Bon niveau, quelques lacunes"
    elif normalized >= 50:
        grade = "C — Acceptable, améliorations nécessaires"
    elif normalized >= 35:
        grade = "D — Risques significatifs"
    else:
        grade = "F — Ne pas exposer en production"

    return {
        "total_score": round(normalized, 1),
        "grade": grade,
        "details": detailed
    }


# Évaluation d'une API fictive
api_scores = {
    "Design REST":          4.5,
    "Sécurité":             3.0,  # manque rate limiting
    "Documentation":        4.0,
    "Tests":                3.5,
    "Observabilité":        2.5,  # pas de traces, pas de SLO
    "Versioning":           4.0,
    "Developer Experience": 3.0,
    "Performances":         4.0,
}

result = score_api(api_scores)

print(f"=== Score de qualité API ===\n")
print(f"Score global : {result['total_score']}/100")
print(f"Niveau       : {result['grade']}\n")
print(f"{'Critère':<28} {'Poids':>6}  {'Note/5':>6}  {'Pondéré':>8}")
print("-" * 55)

for d in result["details"]:
    bar = "█" * int(d["raw_score"]) + "░" * (5 - int(d["raw_score"]))
    print(f"{d['criterion']:<28} {d['weight']:>5.0%}  {d['raw_score']:>5.1f}  {bar}  {d['weighted']:.3f}")

print("-" * 55)

# Points d'amélioration
print("\n=== Axes d'amélioration prioritaires ===")
sorted_by_gap = sorted(
    result["details"],
    key=lambda d: (5 - d["raw_score"]) * d["weight"],
    reverse=True
)
for d in sorted_by_gap[:3]:
    gap_impact = (5 - d["raw_score"]) * d["weight"]
    print(f"• {d['criterion']} (impact = {gap_impact:.3f}) : {d['description']}")
```

### Carte de l'écosystème des outils API

```{code-cell} python3
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", font_scale=0.9)

fig, ax = plt.subplots(figsize=(14, 9))
ax.axis("off")
ax.set_xlim(0, 14)
ax.set_ylim(0, 9.5)

categories = {
    "Design & Spec":        (1.3,  7.5, "#aec7e8", ["OpenAPI 3.1",  "Stoplight Studio", "SwaggerHub",   "Insomnia Design"]),
    "Linting & Validation": (1.3,  5.5, "#ffbb78", ["Spectral",     "Vacuum",            "oasdiff",      "openapi-diff"]),
    "Mock & Tests":         (1.3,  3.5, "#98df8a", ["Prism",        "Wiremock",          "Dredd",        "Schemathesis"]),
    "Documentation":        (5.2,  7.5, "#c5b0d5", ["Redoc",        "Swagger UI",        "Readme.io",    "Mintlify"]),
    "Clients & SDK":        (5.2,  5.5, "#f7b6d2", ["openapi-generator", "kiota",         "fern",         "stainless"]),
    "Observabilité":        (5.2,  3.5, "#aec7e8", ["OpenTelemetry","Prometheus",         "Grafana",      "Jaeger"]),
    "Gateway & Proxy":      (9.1,  7.5, "#ffbb78", ["Kong",         "Traefik",           "Nginx",        "AWS API GW"]),
    "Auth":                 (9.1,  5.5, "#98df8a", ["Keycloak",     "Auth0",             "Okta",         "Zitadel"]),
    "Monitoring SLO":       (9.1,  3.5, "#f7b6d2", ["Sloth",        "Pyrra",             "Nobl9",        "Datadog SLOs"]),
}

for cat_name, (cx, cy, color, tools) in categories.items():
    # Boîte principale
    main_rect = mpatches.FancyBboxPatch(
        (cx - 1.9, cy - 1.1), 3.8, 2.2,
        boxstyle="round,pad=0.1",
        facecolor=color, alpha=0.25, edgecolor=color, linewidth=2
    )
    ax.add_patch(main_rect)
    ax.text(cx, cy + 0.85, cat_name, ha="center", va="center",
            fontsize=9, fontweight="bold", color="#333333")

    # Outils
    for i, tool in enumerate(tools):
        row = i // 2
        col = i % 2
        tx = cx - 0.95 + col * 1.9
        ty = cy + 0.35 - row * 0.65
        tool_rect = mpatches.FancyBboxPatch(
            (tx - 0.85, ty - 0.22), 1.7, 0.44,
            boxstyle="round,pad=0.04",
            facecolor="white", edgecolor=color, linewidth=1
        )
        ax.add_patch(tool_rect)
        ax.text(tx, ty, tool, ha="center", va="center", fontsize=7.5)

ax.text(7, 9.1, "Écosystème des outils API — vue d'ensemble",
        ha="center", va="center", fontsize=13, fontweight="bold")
ax.text(7, 8.75, "Design → Linting → Mock → Doc → Client → Observabilité → Gateway",
        ha="center", va="center", fontsize=9, color="#555555", style="italic")

plt.show()
```

## Résumé

Ce dernier chapitre rassemble les pratiques essentielles sous forme d'instruments opérationnels.

**Avant de coder :** appliquer la checklist de conception — ressources, auth, versioning, erreurs, idempotence — évite les dettes les plus coûteuses à rembourser.

**Sécurité :** l'OWASP API Security Top 10 couvre les vulnérabilités les plus répandues. BOLA (accès non autorisé aux données d'autres utilisateurs) est la plus fréquente et la plus silencieuse.

**Erreurs à éviter :** l'API-comme-proxy-BDD, l'endpoint fourre-tout, les secrets dans les URLs, et l'absence de timeout sont les quatre antipatterns qui causent le plus de problèmes en production.

**API as a product :** la documentation est un livrable, pas une tâche optionnelle. Le *time to first successful call* est la métrique produit la plus importante pour une API exposée à des développeurs tiers.

**À grande échelle :** un API registry, un style guide partagé appliqué par Spectral en CI, une breaking change policy, et un center of excellence permettent de maintenir la cohérence entre des dizaines d'équipes.

Le parcours de ce livre couvre l'essentiel pour concevoir, implémenter, tester, documenter, sécuriser, et opérer des APIs modernes. La pratique — créer de vraies APIs, les exposer à de vrais consommateurs, opérer des incidents, gérer des migrations — est irremplaçable. Les outils et patterns présentés ici sont des accélérateurs, pas des substituts à l'expérience.
