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.
idnumérique → chaîne), changement de format (date, devise), ou changement d’unités (secondes → millisecondes). - Changement de sémantique (ex. un champ
statusqui 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/123Avantages :
- Écoutez le fichier audio avec l'écran éteint.
- Obtenez un certificat à la fin du programme.
- Plus de 5000 cours à découvrir !
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=1Ou via un en-tête dédié :
GET /orders/123
Accept: application/json
X-API-Version: 1Avantages :
- 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(ouVary: 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=1Avantages :
- 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’URL | En-têtes (Accept / X-API-Version) | Paramètre |
|---|---|---|---|
| Simplicité d’usage | Très bonne | Moyenne | Bonne |
| Compatibilité cache/CDN | Très bonne | Bonne si Vary correct | Variable selon infra |
| Lisibilité logs/monitoring | Très bonne | Moyenne (dépend capture en-têtes) | Bonne |
| Évolutivité des représentations | Moyenne | Très bonne | Moyenne |
| Routage côté gateway | Très simple | Plus complexe | Simple |
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
nullsi 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 dateSunset.
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
isGiftest ajouté, préciser : absent ⇒false(ou “inconnu” si vous distinguez les deux). - Si une nouvelle enum apparaît, prévoir un cas
UNKNOWNcô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 clientsDé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.
totaldevientamount+currency). - 3) Exécuter une période de coexistence : maintenir
v1etv2en 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/Sunsetpour les appels env1.