La rétrocompatibilité est une fonctionnalité : le playbook d’un CTO pour des API sur 10 ans

Par Diogo Hudson Dias
Senior platform engineer reviewing API version dashboards and code on dual monitors in a modern São Paulo office, with afternoon light illuminating the workspace.

Microsoft vient d’annoncer que la prochaine Xbox ferait tourner tous les jeux Xbox jamais sortis. En parallèle, Microsoft amène aussi des titres Xbox 360 sur PC. La rétrocompatibilité est désormais une promesse de marque, pas une note de bas de page. Si un device grand public peut préserver un catalogue logiciel sur des décennies, votre SaaS n’a aucune excuse pour casser un client v1 parce qu’un product manager voulait un nom de champ plus propre.

La rétrocompatibilité est un avantage défensif. Elle empêche la résiliation de contrats enterprise, dissuade les partenaires de se construire discrètement un plan B, et épargne à votre support des « rustines » pyrrhiques. On ne l’obtient pas avec de bonnes intentions. On l’obtient avec des politiques, de la télémétrie et des harnais de test qui sanctionnent les ruptures de compatibilité comme un pipeline de CI sanctionne des tests unitaires en échec.

Le business case : la compatibilité, c’est du profit, pas de la charité

Sur les produits B2B que nous avons fait passer à l’échelle chez DHD Tech, 12–20 % du revenu implique au moins une intégration « à forte inertie » : un ERP legacy, une app mobile qui se met à jour lentement, ou un SDK partenaire embarqué dans des dizaines de déploiements clients. Ce sont aussi vos comptes à plus forte marge. Si vous les cassez, deux choses arrivent : votre TAM se réduit (les partenaires cessent d’intégrer) et votre CAC augmente (escalades support et ré-implémentations). La compatibilité n’est pas un nice-to-have ; c’est une stratégie de protection de marge.

Réalité du terrain sur le comportement de mise à jour des clients :

  • Longue traîne mobile : Même avec des mises à jour forcées, 10–20 % des utilisateurs actifs mensuels peuvent avoir 6 mois de retard ou plus ; 2–5 % auront 12–18 mois de retard (les apps de voyage et de livraison biaisent vers l’ancien ; la fintech vers le récent).
  • SDK embarqués : Dans les stacks partenaires B2B, des cycles de mise à niveau de 12–24 mois sont courants ; certains clients attendent un refresh matériel.
  • API dans des verticaux réglementés : Les acheteurs banque/santé négocient des fenêtres de dépréciation dans les MSAs — 12–36 mois est la norme.

Si vous maintenez la compatibilité de façon délibérée, vous absorbez 3–5 % de coût d’ingénierie supplémentaire et évitez 10–15 % de risque de revenu sur les comptes qui vous maintiennent réellement en vie en période de creux.

Décidez par écrit de votre horizon de compatibilité

Choisissez un chiffre et inscrivez-le dans vos contrats et votre documentation. Pas d’approximation.

  • 3 ans : B2C grand public avec peu de dépendances partenaires ; appétence au risque.
  • 5 ans : SaaS horizontal avec intégrations partenaires et SDK ; défaut pour la plupart des startups vendant aux PME + mid-market.
  • 10 ans : Fintech, santé, logistique, plateformes développeurs ; tout ce qui comporte des intégrations tierces devenant critiques pour les clients.

Votre horizon dicte la couverture de test, les shims sur la gateway et les budgets de dépréciation. Faites l’impasse sur cette décision et vous improviserez sous pression plus tard.

Inventoriez vos vecteurs de rupture

Dressez le catalogue de ce qui peut casser et qui paie quand cela arrive :

  • API publiques REST/gRPC utilisées par des partenaires
  • API internes consommées par vos apps web/mobile
  • Flux d’événements (Kafka/Kinesis/PubSub) consommés par des clients ou des partenaires
  • SDK mobiles et serveur embarqués dans les stacks clients

Taggez chacun avec : nombre de consommateurs actifs, criticité (P0–P3), schéma de versionnage actuel et date de dernière rupture de compatibilité. Si vous ne pouvez pas remplir cela, vous ne maîtrisez pas votre surface d’exposition.

Stratégie de versionnage par interface

REST/JSON

  • Privilégiez l’évolution additive. Ne réutilisez jamais un champ pour un autre sens. Ajoutez des champs ; ne supprimez jamais sans shim.
  • Versionnez dans le chemin (/v1/orders) ou via le type de média. Un versionnage uniquement par headers complique la télémétrie et le support.
  • Énumérations : Acceptez toujours les valeurs d’énum inconnues et ignorez-les. Votre client v1 doit survivre à l’ajout d’un nouveau statut en v3.
  • Nombres et montants : Ne passez pas d’entiers en cents à des flottants monétaires. Si vous devez le faire, ajoutez un nouveau champ et laissez l’ancien intact.
  • Dates : Restez sur RFC 3339 avec fuseau horaire. Les heures locales ambiguës vous mordront aux changements d’heure (DST).

gRPC/Protobuf

  • Ne réutilisez jamais les numéros de champ. Marquez les champs supprimés comme reserved pour toujours.
  • Ne changez pas les types (int32 vers string) ni la « requiredness ». Ajoutez de nouveaux champs optionnels ; les anciens clients doivent ignorer les champs inconnus.
  • Les extensions de oneof sont OK ; changer la sémantique ne l’est pas.
  • Attention aux valeurs par défaut. Protobuf v3 traite de façon similaire l’absence et la valeur par défaut ; rendez votre logique métier explicite.

Événements et streams

  • Enveloppes auto-descriptives avec ID de schéma et version : non négociables.
  • Registres de schémas (Avro/Protobuf/JSON Schema) avec des modes de compatibilité réglés sur BACKWARD (ou FULL) pour verrouiller les déploiements producteurs.
  • Contrats d’événements immuables : ne changez jamais le sens d’un champ. Ajoutez de nouveaux événements pour les changements sémantiques.

GraphQL

  • L’additif est sûr ; la suppression est cassante. Utilisez @deprecated religieusement et conservez les dépréciations jusqu’à ce que l’usage passe sous votre seuil pendant N semaines.
  • Les changements de résolveur par défaut sont cassants s’ils modifient la nullabilité ou les plages de valeurs. Traitez-les comme tels.

Shims : où placer la couche de traduction

Vous avez trois endroits pour gérer les anciens clients :

  1. Conserver l’ancien code service (v1 reste en vie). Simple, mais vous payez des correctifs à vie et dupliquer la logique.
  2. Traduction à la périphérie (gateway). Envoy/Kong/NGINX + transformations WASM/Lua/JS qui mappent les requêtes v1 vers v3 et les réponses v3 vers v1. La surcharge typique est de 0,5–2 ms par passage, bien moins chère que de maintenir des branches legacy.
  3. Adaptateurs côté client (SDK). Risqué si vous ne contrôlez pas tous les clients ; fonctionne mieux pour vos propres apps web/mobile.

Notre règle empirique : traduction à l’edge pour les partenaires externes, adaptateurs côté client pour vos apps, et code legacy conservé uniquement comme pont à durée limitée (90–180 jours) le temps de construire des transformations propres.

Rendez la casse visible : SLO de compatibilité

Si vous ne mesurez pas la compatibilité, vous ne l’avez pas. Définissez des SLO qui forcent l’action :

  • Taux d’erreur de compatibilité (par version) : les 4xx dus à des désalignements de schéma/de routage pour vN-1 et vN-2 restent sous 0,1 % des requêtes sur 7 jours.
  • Acceptance des champs : le taux de champs inconnus reste sous 1 % pendant 30 jours après l’ajout d’un nouveau champ (indique une évolution additive sûre).
  • Budget de diff « shadow » : les différences de réponse entre ancien et nouveau chemin de code restent sous 0,5 % pour le trafic miroir avant bascule.

Taggez chaque requête avec une dimension client-version et api-version. Limitez la cardinalité en hashant les versions au-delà des deux premiers segments (ex. : 5.12.x). Si vos logs ne permettent pas un découpage par version, vous conduisez sans compteur de vitesse.

Construisez un harnais de compatibilité exécutable en CI

Commencez simple ; itérez sans relâche :

  1. Corpus de payloads de référence : capturez et anonymisez 500–1 000 requêtes réelles par ressource majeure (couvrez le top 90 % du trafic live). Stockez-les avec les réponses attendues pour vN-1 et vN-2. Réexécutez à chaque PR et déploiement.
  2. Tests de contrat : utilisez des vérifications de schéma OpenAPI/Protobuf/GraphQL pour bloquer les changements cassants. Pour les API partenaires, ajoutez des contrats pilotés par les consommateurs (ex. : Pact) pour vos 10 principaux consommateurs.
  3. Trafic miroir (« shadow traffic ») : dupliquez 1–5 % du trafic production vers la nouvelle implémentation. Différenciez les réponses (whitelists d’en‑têtes, tolérances de valeur). Des outils comme Envoy Tap, des comparateurs à la Diffy ou un sidecar maison font très bien l’affaire.
  4. Garde-fous du registre de schémas : pour les événements, imposez la compatibilité à l’étape build du producteur et en CI.

Attendez-vous à ce que le harnais initial coûte 2–3 mois‑ingénieur pour être mis en place et ~0,5 ETP pour la maintenance. À l’échelle, il se rembourse en détectant les régressions bien avant vos clients.

La dépréciation est un budget, pas un billet de blog

Choisissez une politique de dépréciation cohérente avec votre horizon et appliquez-la sans exception :

  • Signaux : headers de dépréciation dans les réponses, logs/événements structurés pour vos outils CSM, et alertes dans la console d’admin pour les clients.
  • Fenêtres : B2C mobile, 180–270 jours ; B2B SaaS, 12–18 mois ; clients réglementés/SDK, 24–36 mois. Mettez la fenêtre dans vos MSAs.
  • Garde-fous : ne retirez rien tant que l’usage n’est pas passé sous 0,5 % des requêtes pendant 8 semaines, ou que vous n’avez pas des shims écrits et testés.
  • Exceptions : prolongations en un clic pour les comptes majeurs, limitées à 90 jours avec approbation explicite du CTO.

La cadence de communication compte : rappels à J‑90/60/30/14/7/1 par email et bannières in‑product. Fournissez des snippets de code pour les nouveaux endpoints, pas seulement des liens vers la doc.

SDK et mobile : réduire la pression de rupture binaire

Les SDK multiplient votre rayon d’explosion. Rendez-les ennuyeux et résilients :

  • Poussez la compatibilité au niveau du protocole : gardez des SDK fins ; la majeure partie de la logique côté serveur afin que les shims serveur protègent les anciens SDK.
  • Découverte dynamique : récupérez au démarrage les capacités d’API supportées ; ne figez pas des feature flags dans le binaire.
  • Plages d’OS et de runtimes : engagez-vous sur une version minimale d’OS réaliste et tenez-la ; relever la version minimale iOS/Android casse les entreprises avec des contraintes d’équipement.
  • Kill switches : flags de configuration distante pour désactiver des fonctionnalités nouvellement incompatibles sans livrer un nouvel artefact applicatif.

Économie d’ingénierie : ce que cela coûte vraiment

La discipline de compatibilité n’est pas gratuite. Budgétez-la comme une fonctionnalité cœur de fiabilité :

  • Couche de traduction : 0,25 ETP par version legacy activement supportée (transforms edge + tests).
  • Maintenance du harnais : 0,5 ETP en continu pour garder les payloads de référence à jour et les diffs shadow fiables.
  • Télémétrie + dashboards : 2–3 semaines pour ajouter le tagging de version, le contrôle de cardinalité et des tableaux de bord SLO.
  • Produit + Customer Success : 2–4 h/compte pour les communications de dépréciation et les prolongations chez les clients enterprise.

Nous avons vu cet investissement réduire les tickets de casse de 30–50 % et gagner 0,5–1,5 points de marge brute à l’échelle en éliminant le rework d’urgence et les remises lors des renouvellements.

Pièges courants (et comment les éviter)

  • « Ce n’est qu’un renommage. » Cela n’existe pas. Ajoutez un alias, conservez l’ancien champ et émettez les deux jusqu’à ce que l’usage baisse.
  • Montants en float : les erreurs d’arrondi se transforment en enfer de réconciliation. Conservez les unités mineures entières pour les anciens clients ; ajoutez des chaînes décimales pour les nouveaux.
  • Fuseaux horaires et DST : Acceptez les offsets, stockez en UTC, retournez en RFC 3339 avec info de zone. N’inférez pas l’heure locale utilisateur côté serveur.
  • Nil vs absent : En Protobuf v3 et JSON, absent et null se comportent différemment selon les langages. Rendez la sémantique explicite et testez dans les deux sens.
  • Clés d’idempotence : Changer le comportement d’idempotence (TTL, scope) est une rupture. Versionnez-le ou conservez la sémantique antérieure via un shim.
  • Nullabilité GraphQL : Durcir la nullabilité est cassant. Utilisez une dépréciation au niveau du champ + un nouveau champ non nul ; ne modifiez pas sur place.

Un plan de déploiement sur 90 jours

Jours 1–30 : D’abord la visibilité

  • Ajoutez le tagging api-version et client-version à chaque requête/réponse. Livrez des dashboards de volume et de taux d’erreur par version.
  • Publiez votre horizon de compatibilité (3/5/10 ans) en interne ; ajoutez-le à la doc et aux nouveaux contrats.
  • Gelez les changements cassants tant que les SLO et les tests n’existent pas.

Jours 31–60 : Garde-fous et harnais

  • Mettez en place des vérifications de contrat en CI (OpenAPI/Protobuf/GraphQL).
  • Construisez votre premier corpus de payloads de référence pour les cinq endpoints principaux et un test de relecture basique.
  • Activez des transforms Envoy/Kong ou équivalents à l’edge ; migrez un changement cassant trivial derrière pour fiabiliser le chemin.

Jours 61–90 : Le muscle de la dépréciation

  • Définissez des fenêtres de dépréciation par segment (B2C/B2B/réglementé) et intégrez-les dans les MSAs.
  • Lancez votre première expérience de trafic miroir (1 % de duplication) pour une migration vN vers vN+1 ; créez un budget de diff de réponse.
  • Formez le Support et les CSM à la cadence de com et au processus d’exception. Rendez les prolongations soumises à approbation du CTO.

Le changement de posture

La rétrocompatibilité ne veut pas dire ne jamais changer. Elle veut dire changer délibérément. Si Microsoft peut transporter des jeux à travers des générations de puces, vous pouvez garder en vie l’intégration d’un partenaire vieille de six ans tout en livrant une v4. Il suffit de traiter la compatibilité comme la latence ou la disponibilité : une propriété objective et mesurée avec budgets et mécanismes d’application — pas une promesse qu’on espère que les ingénieurs se rappelleront.

Points clés

  • Choisissez un horizon de compatibilité de 3/5/10 ans et publiez-le. Pas d’horizon, pas de plan.
  • Repoussez les changements cassants dans des shims à l’edge ; ne gardez le code legacy que comme pont à durée limitée.
  • Faites respecter des SLO de compatibilité : taux d’erreurs par version, budgets de diff shadow et garde-fous de schéma.
  • Mettez en place un harnais de compatibilité : payloads de référence, contrats pilotés par les consommateurs et 1–5 % de trafic miroir.
  • La dépréciation est un budget avec seuils et fenêtres, pas un billet de blog.
  • Gardez des SDK fins et résilients ; poussez la complexité « sur le fil » où des shims peuvent protéger les anciens clients.
  • Attendez-vous à 3–5 % de coût d’ingénierie ; évitez 10–15 % de risque de revenu sur vos comptes les plus « collants ».

Auteur : Diogo Hudson Dias

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation