Changements d'API

Changements cassants : ce qui compte, comment en livrer un

10 min de lecture mis à jour le

Un changement cassant est un changement qu’un appelant correctement écrit n’aurait pas pu survivre. La définition compte parce que la plupart des débats sur si quelque chose “compte” sont en réalité des débats sur qui le tenait mal. Si un appelant a suivi votre documentation et votre changement a fait cesser de fonctionner son code, le changement était cassant. Ce que vous vouliez dire n’a rien à voir là-dedans.

C’est tout le test. Le reste de cet article, c’est ce qui en découle : ce qui y échoue, ce qui le réussit, comment attraper un échec avant le merge, et quoi faire une fois que vous savez que vous en livrez un.

Qu’est-ce qui compte comme changement cassant ?

Appliquez le test à l’appelant, pas au diff. Un changement est cassant quand un appelant qui ne comptait que sur le comportement documenté doit changer son code, sa configuration ou ses données pour continuer à fonctionner. Supprimer un champ, renommer un endpoint, resserrer la validation, changer une valeur par défaut et changer le type d’une valeur se qualifient tous. Ajouter un champ optionnel non. Corriger un bug généralement non, avec une exception importante ci-dessous.

ChangementCassant ?Pourquoi
Supprimer ou renommer un champ, endpoint, flag ou optionOuiLes appelants corrects le référencent
Ajouter un champ optionnel ou un nouvel endpointNonLes appels existants ne changent pas
Rendre obligatoire une entrée optionnelleOuiLes appels qui l’omettaient échouent maintenant
Resserrer une validation acceptée auparavantOuiDes entrées qui fonctionnaient sont maintenant rejetées
Changer une valeur par défautOuiLes appelants qui ne l’ont pas définie reçoivent un nouveau comportement
Changer un type (string vers nombre, valeur unique vers array)OuiLes parsers écrits pour le type documenté échouent
Réordonner les clés d’un objetNonSauf si vous avez documenté l’ordre
Corriger un bug dont dépendaient les appelantsEn pratique ouiVoir la section sur les contrats accidentels
Relever une limite de taux ou un plafond de tailleNonRien de ce qui fonctionnait ne s’arrête
Baisser une limite de taux ou un plafond de tailleOuiDu trafic qui allait bien est maintenant limité
Changer la formulation d’un message d’erreurÇa dépendCassant si vous l’avez documenté ou si les appelants matchent dessus

Qu’est-ce qui n’est pas un changement cassant ?

Un changement est non cassant quand chaque appel qui fonctionnait avant fonctionne toujours, sans modification, et veut toujours dire la même chose. Ajouter un nouvel endpoint, ajouter un paramètre de requête optionnel, ajouter un champ à une réponse, rendre optionnelle une entrée obligatoire, relever une limite et améliorer un message d’erreur sur lequel personne ne matche réussissent tous le test. Ces changements additifs peuvent sortir dans une version mineure avec une entrée de changelog ordinaire.

Les changements additifs cassent quand même des appelants dans trois situations. Un client dont le désérialiseur rejette les champs inconnus échoue dès le premier nouveau champ de réponse : indiquez tôt dans la documentation que les appelants doivent ignorer les champs qu’ils ne reconnaissent pas. Une nouvelle valeur d’enum casse tout appelant avec un switch exhaustif (plus bas). Et une réponse qui grossit peut pousser un appelant au-delà d’une limite de taille, d’un timeout ou d’une largeur de colonne auxquels il n’a jamais eu à penser.

Quatre lignes du tableau méritent un regard plus attentif, parce que c’est là que naissent les désaccords.

Les quatre changements cassants que les équipes oublient

Contrats accidentels. Si votre API a renvoyé le même champ non documenté pendant trois ans, un appelant a construit dessus. La loi de Hyrum est la version courte : avec assez d’utilisateurs, chaque comportement observable de votre système dépendra de quelqu’un. C’est pourquoi “c’était une correction de bug” n’est pas une défense. La correction peut être correcte et quand même cassante. Livrez-la comme telle.

Changements de comportement sans changement de schéma. Le champ est toujours là, le type est le même, et la valeur signifie maintenant autre chose. Un status qui était active ou inactive et renvoie maintenant aussi suspended casse chaque appelant avec un switch exhaustif. Un timestamp qui passe de l’heure locale à UTC casse tous ceux qui n’ont pas relu la doc deux fois. Rien dans un diff du fichier OpenAPI ne montre ça.

Validation resserrée. Vous commencez à rejeter des emails sans TLD, ou des espaces en fin de chaîne, ou des noms de plus de 80 caractères. Chaque appelant qui envoyait exactement ça reçoit maintenant un 400 pour une requête qui fonctionnait la semaine dernière. Les changements de validation sont les plus souvent livrés comme correction de “durcissement”.

Valeurs par défaut changées. Personne qui a défini la valeur explicitement ne remarque rien. Tous ceux qui ne l’ont pas fait, ce qui est la majorité des appelants, reçoivent un nouveau comportement sans changer une ligne. Une valeur par défaut changée casse la majorité de vos utilisateurs précisément parce qu’ils n’ont jamais vu le réglage.

Comment détecter un changement cassant avant qu’il sorte ?

Comparez le contrat de la pull request au contrat de la branche principale, en CI, et faites échouer le build sur toute différence cassante. Des outils de diff de schéma existent pour la plupart des formats d’interface, et chacun connaît les règles de cassure de son propre format :

InterfaceOutilCe qu’il compare
REST (OpenAPI)oasdiffDeux specs OpenAPI, avec un rapport des changements cassants
gRPC (Protobuf)buf breakingLes fichiers .proto, au niveau du wire ou des sources
GraphQLGraphQL InspectorDeux schémas, en signalant les changements cassants et dangereux
Crates Rustcargo-semver-checksL’API publique face à la dernière version publiée
Paquets TypeScriptAPI ExtractorUn rapport versionné de l’API publique du paquet

Ces outils attrapent de façon fiable les champs supprimés, les opérations renommées et les types changés. Ils ne voient pas les deux premiers des quatre types ci-dessus, un contrat accidentel ou un changement de comportement, parce qu’aucun n’apparaît dans un schéma. Utilisez l’outil pour arrêter les cas évidents, et la question de revue “un appelant correct pourrait-il le remarquer ?” pour le reste. Le même job de CI est un endroit naturel pour exiger une entrée de changelog, comme décrit dans imposer les entrées de changelog en CI, et les changements d’API gRPC et Protobuf passe en revue les cas au niveau du wire.

Comment marquer un changement cassant dans un commit ?

Avec Conventional Commits, un changement cassant se marque par un ! avant les deux-points (feat(api)!: remove the legacy export endpoint) ou par un footer qui commence par BREAKING CHANGE: suivi d’une description. L’un ou l’autre correspond à une version majeure. Écrivez le footer comme le premier jet de l’entrée de changelog, en nommant qui est concerné et ce qu’il doit faire. Conventional commits et le changelog explique jusqu’où la convention vous mène.

La même règle vaut pour les bibliothèques. Une fonction publique supprimée, un type de paramètre rétréci ou une valeur de retour changée donnent une version majeure sous versionnage sémantique. Les bibliothèques ne la suivent pas toujours : une étude de 119 879 mises à jour sur Maven Central a trouvé que 16,6% rompaient le versionnage sémantique, alors que seulement 7,9% des projets clients étaient touchés, parce que la plupart de ces changements concernaient du code qu’aucun client n’appelait. La casse se mesure chez l’appelant.

Comment livre-t-on un changement cassant ?

Vous le livrez ouvertement, avec une date, avec un chemin. Les étapes ci-dessous sont dans l’ordre, et la dernière est celle que la plupart des équipes sautent : dire aux personnes concernées que ce qu’elles attendaient s’est maintenant produit.

  1. Décidez si c’en est un. Utilisez le test ci-dessus, pas le diff. Si deux ingénieurs ne sont pas d’accord, c’est cassant ; le désaccord est la preuve qu’un appelant aurait raisonnablement pu compter sur l’ancien comportement.
  2. Versionnez-le. Sous versionnage sémantique, un changement cassant est une version majeure. Si vous exploitez une API datée ou versionnée, ça va dans une nouvelle version et l’ancienne continue de fonctionner jusqu’à une date déclarée. Si vous ne pouvez pas versionner, vous ne livrez pas un changement cassant, vous livrez une panne avec une entrée de changelog. Quel schéma porte la version est le sujet de bonnes pratiques de versionnage d’API.
  3. Écrivez l’entrée avant que le code soit mergé. L’entrée a une forme fixe : ce qui change, qui ça concerne, ce qu’ils doivent faire, et pour quand. Si vous ne pouvez pas remplir les quatre, le changement n’est pas prêt. La release notes template met ces entrées en premier, avec une date plutôt qu’un numéro de version, exactement pour ça.
  4. Donnez une échéance, pas un numéro de version. “Supprimé en v5” ne signifie rien pour quelqu’un qui ne suit pas vos versions. “Cesse de fonctionner le 1er novembre 2026” signifie la même chose pour tout le monde.
  5. Fournissez la migration. Un exemple de code de l’ancien appel à côté du nouveau. Si le changement est un renommage, donnez les deux noms dans la même phrase. Si c’est un champ supprimé, dites où sont allées les données.
  6. Annoncez-le partout où l’ancien comportement était documenté. Le changelog, la page docs qui décrit l’endpoint, les release notes du SDK, et l’en-tête de dépréciation dans la réponse si vous en avez un. Annoncé à un seul endroit, c’est annoncé aux gens qui ont eu la chance de regarder là.
  7. Fermez la boucle. Si une cliente a demandé le changement, ou signalé le bug qui y a mené, dites-le-lui quand ça sort. C’est l’étape qui transforme ça d’une chose faite à vos utilisateurs en une chose faite avec eux.

À quoi ressemble une bonne entrée pour un changement cassant ?

Une bonne entrée nomme l’appelant concerné dans la première ligne, indique la date, et inclut la correction. En voici une pour le cas de validation resserrée, dans la forme qu’on utilise :

Les adresses email sans domaine sont rejetées à partir du 1er novembre 2026. POST /users et PATCH /users/:id acceptent actuellement des valeurs email comme alice@localhost. À partir du 1er novembre, celles-ci renvoient 400 invalid_email. Concerne toute intégration qui crée des utilisateurs depuis des annuaires internes. Migration : envoyez une adresse complètement qualifiée, ou omettez le champ et définissez-le plus tard. Aucun changement n’est nécessaire si vos adresses ont déjà un domaine, ce qui est vrai pour 99,4% des comptes créés cette année.

Où cet avis a sa place, et ce qui devrait l’accompagner, fait l’objet du changelog d’API.

Le pourcentage à la fin n’est pas de la décoration. Il dit à la lectrice si elle doit s’inquiéter, ce qui est la question avec laquelle elle a ouvert l’entrée.

Pourquoi ne pas simplement les éviter ?

Parce que l’alternative est pire. Une API qui ne casse jamais rien accumule chaque erreur qu’elle a jamais faite : le champ mal nommé, la mauvaise valeur par défaut, le timestamp en heure locale. Chacune est une taxe sur chaque nouvel appelant pour toujours, pour protéger des appelants qui auraient pu migrer en un après-midi. Les équipes avec les meilleures réputations de stabilité cassent rarement des choses, selon un calendrier, avec un chemin de migration et un avertissement qui a atteint les gens pour qui il était destiné.

La mécanique de cet avertissement est le sujet de l’article compagnon sur déprécier une API. L’entrée qui l’annonce est rédigée de la même manière que n’importe quelle autre entrée dans le flux de changelog : depuis la pull request mergée, retenue pour un humain, puis publiée à l’endroit où les appelants concernés lisent déjà.

FAQ

Quelle est la différence entre un changement cassant et un changement non cassant ? Un changement cassant force un appelant correct à changer son code, sa configuration ou ses données pour continuer à fonctionner. Un changement non cassant laisse chaque appel existant fonctionner avec le même sens, ce qui explique pourquoi les ajouts sont généralement sûrs et les suppressions, renommages et règles resserrées généralement non.

Ajouter un champ obligatoire compte-t-il ? Oui. Chaque appel existant l’omet, donc chaque appel existant échoue maintenant. Ajoutez-le comme optionnel avec une valeur par défaut sensée, ou versionnez l’endpoint.

Une correction de bug compte-t-elle ? Ça peut. Si des appelants dépendaient du comportement buggé, le corriger les casse, quoi que dise la documentation. Traitez toute correction qui change la sortie observable comme cassante, sauf si vous pouvez montrer que personne n’en dépendait.

Le versionnage sémantique s’applique-t-il à une API web ? La règle oui : les changements cassants reçoivent une nouvelle version majeure et l’ancienne continue de fonctionner pendant une période déclarée. Le numéro vit souvent dans l’URL ou un en-tête de date plutôt que dans une version de paquet.

Combien de préavis suffit ? Assez pour qu’un appelant trouve l’avis et fasse le travail. Quatre-vingt-dix jours est un plancher courant pour les API publiques ; plus long pour tout ce qui est utilisé dans du code livré aux utilisateurs finaux et qui ne peut pas être mis à jour à distance.


Les affirmations techniques de cet article n'ont pas été vérifiées de façon indépendante. Si quelque chose est faux, dis-le-nous et nous le corrigerons.

À voir sur changeloop : Modèle de release notes, Documentation développeurs

changeloop
L'équipe qui construit un changelog qui boucle la boucle. Tes utilisateurs demandent, ton équipe livre, la personne qui a demandé est prévenue.