API REST back-end : versioning et compatibilité dans le temps

Capítulo 7

Temps de lecture estimé : 8 minutes

+ Exercice

Pourquoi versionner une API et ce qu’on appelle une “rupture”

Le versioning sert à faire évoluer une API sans casser les clients existants. Une version représente un contrat : structure des requêtes/réponses, sémantique des champs, formats, comportements attendus. On versionne quand on ne peut pas garantir que les clients actuels continueront à fonctionner sans modification.

Ce qui constitue une rupture de compatibilité (breaking change)

Une rupture n’est pas seulement un changement de JSON : c’est tout changement qui peut provoquer une erreur, une mauvaise interprétation ou un comportement inattendu côté client.

  • Suppression d’un champ, d’un endpoint, d’une valeur d’énumération, ou d’un comportement.
  • Renommage d’un champ ou d’un paramètre.
  • Changement de type (ex. id numérique → chaîne), changement de format (date, devise), ou changement d’unités (secondes → millisecondes).
  • Changement de sémantique (ex. un champ status qui ne veut plus dire la même chose, ou une règle de calcul modifiée).
  • Changement de contraintes rendant des requêtes auparavant valides invalides (ex. un champ devient obligatoire, ou une limite plus stricte).
  • Changement d’ordre si des clients dépendent à tort de l’ordre (ex. tri implicite modifié) : même si “théoriquement” l’ordre ne devrait pas être utilisé, en pratique cela peut casser.

Changements généralement non cassants (si bien gérés)

  • Ajout d’un champ dans une réponse, à condition que les clients tolèrent des champs inconnus.
  • Ajout d’un endpoint ou d’un paramètre optionnel.
  • Ajout d’une valeur d’énumération si les clients gèrent le cas “inconnu” (sinon, cela peut casser).
  • Ajout d’un en-tête ou métadonnée non requise.

Approches de versioning : URL, en-têtes, paramètres

Il existe trois approches courantes. Le choix dépend de critères opérationnels (cache, observabilité, routage), de l’expérience développeur, et de la stratégie d’évolution.

1) Version dans l’URL (préfixe /v1)

Exemple :

GET /v1/orders/123
GET /v2/orders/123

Avantages :

Continuez dans notre application.
  • Écoutez le fichier audio avec l'écran éteint.
  • Obtenez un certificat à la fin du programme.
  • Plus de 5000 cours à découvrir !
Ou poursuivez votre lecture ci-dessous...
Download App

Téléchargez l'application

  • Simplicité : visible, facile à tester, facile à documenter.
  • Cache/CDN : la version fait partie de l’URL, donc les caches HTTP et reverse proxies distinguent naturellement les versions.
  • Routage : simple à router côté gateway/serveur (mapping par préfixe).

Inconvénients :

  • Peut encourager des “forks” complets d’API, même pour de petites évolutions.
  • Si vous avez beaucoup de versions, l’espace d’URL se fragmente.

Quand la choisir : si vous privilégiez l’opérationnel (cache, logs, routage), et une adoption facile par des clients variés.

2) Version via en-têtes (content negotiation)

Exemple avec un media type versionné :

GET /orders/123
Accept: application/vnd.mycompany.orders+json;version=1

Ou via un en-tête dédié :

GET /orders/123
Accept: application/json
X-API-Version: 1

Avantages :

  • URL stable : l’identifiant de ressource ne change pas.
  • Évolutivité fine : possibilité de faire varier la représentation sans multiplier les routes.

Inconvénients :

  • Cache : nécessite une gestion correcte de Vary: Accept (ou Vary: X-API-Version) pour éviter de servir une mauvaise version depuis un cache partagé.
  • Débogage : moins visible dans les logs si les en-têtes ne sont pas capturés.
  • Outils : certains clients/outils gèrent moins bien les media types personnalisés.

Quand la choisir : si vous voulez garder des URL pérennes et versionner surtout les représentations, avec une équipe à l’aise avec la négociation de contenu et le cache.

3) Version via paramètre (query string)

Exemple :

GET /orders/123?api-version=1

Avantages :

  • Facile à expérimenter, pratique pour certains environnements.

Inconvénients :

  • Moins standard : mélange versioning et paramètres métier.
  • Cache : selon l’infrastructure, les caches peuvent être mal configurés vis-à-vis des query strings.
  • Risque de propagation involontaire (copier-coller d’URL avec une version figée).

Quand la choisir : plutôt en dernier recours, ou pour des APIs internes/transitionnelles où l’URL ne peut pas changer et où les en-têtes sont difficiles à imposer.

Critères de choix (checklist)

Critère/v1 dans l’URLEn-têtes (Accept / X-API-Version)Paramètre
Simplicité d’usageTrès bonneMoyenneBonne
Compatibilité cache/CDNTrès bonneBonne si Vary correctVariable selon infra
Lisibilité logs/monitoringTrès bonneMoyenne (dépend capture en-têtes)Bonne
Évolutivité des représentationsMoyenneTrès bonneMoyenne
Routage côté gatewayTrès simplePlus complexeSimple

Stratégie d’évolution dans le temps

Une stratégie robuste vise à minimiser les versions majeures, en privilégiant des changements rétrocompatibles, puis en orchestrant la dépréciation et la suppression progressive.

Règle d’or : privilégier les ajouts non cassants

Exemple : vous voulez exposer une nouvelle information deliveryEta dans une réponse.

// Avant (v1)
{
  "id": "o_123",
  "total": 42.50
}

// Après (toujours v1, ajout non cassant)
{
  "id": "o_123",
  "total": 42.50,
  "deliveryEta": "2026-02-01T10:00:00Z"
}

Conditions :

  • Les clients doivent ignorer les champs inconnus.
  • Le nouveau champ doit être optionnel au début (peut être absent, ou null si votre contrat l’autorise).

Déprécier avant de supprimer

La dépréciation consiste à annoncer qu’un élément va disparaître, tout en le maintenant pendant une période de transition.

Étapes pratiques :

  • Étape 1 — Marquer comme déprécié : documenter l’élément, et si possible le signaler dans les réponses.
  • Étape 2 — Proposer une alternative : nouveau champ, nouveau endpoint, ou nouvelle représentation.
  • Étape 3 — Mesurer l’usage : instrumentation (logs/metrics) pour savoir quels clients utilisent encore l’ancien contrat.
  • Étape 4 — Communiquer une date : annoncer une date de fin de support.
  • Étape 5 — Retirer progressivement : d’abord sur des environnements non prod, puis prod avec garde-fous (feature flags, canary).

Communication via en-têtes : Deprecation et Sunset

Quand c’est pertinent, vous pouvez informer les clients directement dans les réponses HTTP. Deux en-têtes sont souvent utilisés :

  • Deprecation : indique que la ressource/feature est dépréciée (souvent une date ou un booléen selon conventions internes).
  • Sunset : indique la date à laquelle la ressource/feature ne sera plus disponible.

Exemple :

HTTP/1.1 200 OK
Deprecation: true
Sunset: Wed, 01 Apr 2026 00:00:00 GMT
Link: <https://api.example.com/docs/migrations/orders-v2>; rel="deprecation"

Bonnes pratiques :

  • Ne pas spammer tous les endpoints : cibler ceux réellement concernés.
  • Fournir un lien de migration (via Link) vers des instructions concrètes.
  • Conserver une période de transition réaliste (semaines/mois selon criticité et nombre de clients).

Suppression progressive : compatibilité “douce” avant la rupture

Pour éviter une coupure brutale :

  • Phase 1 : l’ancien champ/endpoint continue de fonctionner, mais est marqué déprécié.
  • Phase 2 : l’ancien champ est toujours présent mais peut devenir vide/figé (si acceptable), tout en gardant l’alternative fiable.
  • Phase 3 : suppression dans une nouvelle version majeure (ex. /v2), ou retrait définitif à la date Sunset.

Gérer la compatibilité côté clients (contrat consommateur)

Une API “compatible dans le temps” suppose des clients tolérants et des contrats explicites.

Champs optionnels et tolérance aux champs inconnus

  • Côté serveur : introduire de nouveaux champs comme optionnels, et ne pas supposer que les clients les liront.
  • Côté client : ignorer les champs inconnus, et ne pas échouer si un champ optionnel est absent.

Exemple de règle de lecture côté client : “si deliveryEta est absent, afficher ‘non disponible’”.

Valeurs par défaut et sémantique stable

Quand un nouveau champ est ajouté, définissez une valeur par défaut logique ou un comportement de repli.

  • Si un champ booléen isGift est ajouté, préciser : absent ⇒ false (ou “inconnu” si vous distinguez les deux).
  • Si une nouvelle enum apparaît, prévoir un cas UNKNOWN côté client.

Évitez de changer la signification d’un champ existant : si la sémantique change, créez un nouveau champ (ou une nouvelle version).

Tests contractuels (contract testing) pour éviter les régressions

Les tests contractuels vérifient automatiquement que le serveur respecte ce que les clients attendent, et que les clients tolèrent les évolutions prévues.

Mise en place pas à pas :

  • Étape 1 — Définir un contrat : pour chaque endpoint critique, lister champs requis/optionnels, types, contraintes, et exemples.
  • Étape 2 — Ajouter des tests côté serveur : valider que la réponse contient au minimum les champs requis et respecte les types.
  • Étape 3 — Ajouter des tests de non-régression : vérifier que les champs existants ne changent pas de type/format.
  • Étape 4 — Tester la tolérance : simuler l’ajout de champs et vérifier que les clients ne cassent pas (si vous contrôlez certains clients).
  • Étape 5 — Automatiser dans la CI : exécuter ces tests à chaque changement d’API.

Exemple de “contrat minimal” (pseudo-spécification) :

GET /v1/orders/{id}
Response 200 (application/json)
- id: string (required)
- total: number (required)
- deliveryEta: string date-time (optional)
Rule: unknown fields may appear and must be ignored by clients

Décider quand créer une nouvelle version majeure

Créez une nouvelle version majeure quand vous devez introduire une rupture inévitable (suppression/renommage/changement de type ou de sémantique) et que les mécanismes de transition (dépréciation, double écriture/lecture, champs parallèles) ne suffisent pas.

Procédure recommandée :

  • 1) Isoler le changement cassant : lister précisément ce qui casse et pourquoi.
  • 2) Proposer un chemin de migration : mapping ancien → nouveau (ex. total devient amount + currency).
  • 3) Exécuter une période de coexistence : maintenir v1 et v2 en parallèle.
  • 4) Instrumenter l’adoption : mesurer le trafic par version pour décider de la date de retrait.
  • 5) Annoncer la fin de vie : via documentation + en-têtes Deprecation/Sunset pour les appels en v1.

Répondez maintenant à l’exercice sur le contenu :

Dans quelle situation est-il le plus pertinent de créer une nouvelle version majeure d’une API REST ?

Tu as raison! Félicitations, passez maintenant à la page suivante

Vous avez raté! Essayer à nouveau.

Une version majeure est justifiée quand l’évolution introduit une rupture de compatibilité inévitable (suppression/renommage/changement de type ou de sens) et que les stratégies de transition (dépréciation, coexistence, champs parallèles) ne permettent pas de préserver les clients existants.

Chapitre suivant

API REST back-end : documentation claire et contrat d’API

Arrow Right Icon
Couverture de livre électronique gratuite Développement back-end : concevoir une API REST propre
88%

Développement back-end : concevoir une API REST propre

Nouveau cours

8 pages

Téléchargez l'application pour obtenir une certification gratuite et écouter des cours en arrière-plan, même avec l'écran éteint.