L’idée reçue veut que toute nouvelle API doive être versionnée d’emblée, souvent en préfixant les URLs par un numéro de version (/v1, /v2, etc.). Cette pratique est censée faciliter les évolutions futures sans casser les clients existants. Pourtant, dans de nombreux cas, il n’est pas nécessaire de mettre en place un versionnement par défaut. En effet, si tous les consommateurs de l’API sont sous votre contrôle, la flexibilité apportée par le versionnement peut ne pas justifier la complexité de gestion supplémentaire introduite. Nous allons examiner pourquoi le versionnement systématique n’est pas toujours indispensable, quels en sont les coûts, et comment faire évoluer vos APIs internes de façon pragmatique et pédagogique.
Qu’est-ce que le versionnement d’API et pourquoi est-il recommandé ?
Le versionnement d’API consiste à faire coexister plusieurs versions d’une même API afin de gérer des modifications dans les interfaces exposées. Par exemple, on peut avoir une version 1 et une version 2 d’un service REST, chacune acceptant des requêtes et renvoyant des réponses dans un format possiblement différent. L’intérêt est de permettre à des anciens clients de continuer à fonctionner sur l’API v1 tandis que de nouveaux clients utilisent l’API v2, surtout si des changements rétro-incompatibles (breaking changes) sont introduits. En microservices, où chaque service évolue indépendamment, cette capacité à exposer plusieurs versions peut sembler nécessaire pour ne pas perturber l’écosystème logiciel lors des déploiements.
En effet, la recommandation habituelle pour les API publiques est d’implémenter un versionnement dès le départ afin de ne pas bloquer les évolutions futures. Par exemple, Google Cloud souligne qu’un des malentendus courants est de croire “qu’il faut intégrer le versionnement dans vos APIs dès le départ”. Cette approche préventive amène de nombreux développeurs à préfixer systématiquement leurs routes d’API par /v1 dès la première version, “suivant des conseils lus sur internet”.
Pourquoi cette précaution ? Prenons un scénario microservices classique : le Service A fournit une API consommée par d’autres services (B, C, etc.). Si A doit changer son contrat (par exemple, renommer un champ, modifier le format de réponse ou la logique métier), les services consommateurs risquent de ne plus fonctionner correctement. Le versionnement offre alors une solution : on publie une nouvelle version de l’API (v2) tout en gardant l’ancienne (v1) accessible, ce qui permet aux anciens et aux nouveaux clients de continuer à fonctionner sans interruption. Les clients existants peuvent migrer vers la v2 à leur rythme, tandis que les nouveaux profitent directement des évolutions. Cela évite des coupures brutales du service et facilite des déploiements progressifs : “la nouvelle version d’API peut être déployée à côté de l’ancienne, assurant un service ininterrompu pour tous les clients”. Dans un contexte de microservices à grande échelle, le versionnement offre aussi la possibilité de tester de nouvelles fonctionnalités sur un sous-ensemble de consommateurs (canary release) sans impacter tout le monde.
Enfin, le versionnement clarifie souvent la communication et la documentation : chaque version peut avoir sa documentation propre détaillant les changements, ce qui aide les développeurs consommateurs à s’adapter aux évolutions. En résumé, versionner une API est généralement considéré comme une bonne pratique pour assurer la compatibilité descendante, permettre des évolutions sans casse, et donner de la flexibilité aux clients quant au moment où ils adoptent les nouveautés.
Cependant, cette médaille a un revers : supporter plusieurs versions simultanément a des coûts non négligeables, et ces coûts sont parfois inutiles dans un environnement maîtrisé. Voyons quels sont ces inconvénients.
Les coûts et inconvénients du versionnement d’API
Introduire et maintenir plusieurs versions d’une API entraîne de la complexité additionnelle. D’abord, chaque version supplémentaire est un code à maintenir en parallèle : en cas de bug découvert, il faut vérifier s’il affecte toutes les versions et le corriger partout si nécessaire. Cela multiplie les efforts de test et de QA, car une correction ou une nouvelle fonctionnalité doit potentiellement être reportée sur plusieurs branches de code. Comme le formule un expert : “supporter plus d’une version de quoi que ce soit est pénible. Trouver un bug dans une version oblige à confirmer son absence dans les autres versions”. À l’extrême, on peut se retrouver à maintenir indéfiniment de vieilles versions pour quelques clients retardataires, avec le risque d’y introduire des divergences ou des régressions.
Ensuite, le versionnement “par défaut” peut encourager des changements incompatibles plus fréquents qu’il ne faudrait. Sous prétexte qu’une nouvelle version majeure va sortir, des équipes pourraient accumuler des modifications non rétro-compatibles dans la v2 “puisque de toute façon les clients devront s’adapter”. Cette mentalité peut nuire à la stabilité et à la confiance des consommateurs si la communication des changements n’est pas irréprochable. De plus, multiplier les versions peut fragmenter votre base de consommateurs : certains resteront sur les anciennes versions pendant que d’autres migrent, rendant l’écosystème global plus hétérogène et donc plus difficile à faire évoluer de manière cohérente.
La documentation et le support client s’alourdissent également. Chaque version de l’API doit être documentée, avec les différences, les plans de dépréciation, etc. Les équipes consommatrices doivent, elles, gérer potentiellement plusieurs versions de clients. Tout ceci représente un coût de communication et de coordination non négligeable.
Enfin, comme le souligne Reda Hmeid, beaucoup des problèmes posés par le versionnement sont en réalité auto-infligés. Avoir plusieurs versions, c’est ajouter du poids mort si finalement tous les clients auraient pu utiliser une API unique évolutive. Il note par exemple que “le versionnage des interfaces n’est souvent qu’une façon de gérer le changement à l’avantage du propriétaire de l’API, au détriment de la continuité pour le client”. En effet, du point de vue du consommateur, ce qui compte c’est que l’API continue de fonctionner sans qu’il ait besoin de modifier son code. Si on peut garantir cela sans changer d’URL ni de contrat, le besoin d’une nouvelle version distincte est discutable.
En somme, le versionnement apporte une solution puissante pour gérer le changement, mais il complexifie le cycle de vie de l’API. Il s’agit d’un compromis: flexibilité pour le fournisseur et pour les clients les plus à jour, contre lourdeur de maintenance et nécessité pour d’autres clients de migrer un jour ou l’autre. Il convient donc de se demander si ce compromis est toujours justifié. En définitive, demandez-vous : “Le bénéfice de ce changement justifie-t-il le temps, l’effort et l’impact de supporter plusieurs versions, aussi bien pour nos consommateurs que pour nous-mêmes ?”.
Dans bien des cas, notamment lorsque l’on contrôle l’ensemble des consommateurs, la réponse est non. Voyons ces situations où l’on peut raisonnablement éviter le versionnement par défaut.
Quand peut-on se passer de versionnement ?
Consommateurs internes sous contrôle
Si votre API est consommée uniquement en interne, par exemple par vos propres applications frontales ou d’autres microservices de votre entreprise, vous avez une maîtrise totale de l’écosystème. Cela change la donne : vous pouvez coordonner les déploiements des producteurs et consommateurs de l’API pour effectuer des changements sans avoir à maintenir deux versions en parallèle sur le long terme.
Dans un système distribué basé sur des microservices .NET, il est courant que les différentes équipes (ou la même équipe) gèrent à la fois l’API fournie et les clients de cette API. Cette situation permet une agilité que les API publiques n’ont pas : vous pouvez décider que “la version de l’API évolue en même temps que la version du client ». Autrement dit, dès qu’un service fournit un nouveau contrat, les services consommateurs sont déployés (ou modifiés) pour s’aligner sur ce contrat.
Un développeur témoignait sur ce point qu’il y a “beaucoup de pratiques logicielles qui n’ont pas de sens quand on contrôle tout en interne. Si vous possédez à la fois le client et le serveur, un système de versionnement long-terme n’est pas nécessaire ». Autrement dit, si vous maîtrisez l’ensemble des applications clientes, vous pouvez souvent vous passer de versionner formellement votre API : il suffit de déployer la mise à jour du client en même temps que celle du serveur. Ceci est particulièrement vrai pour une application web SPA (Single-Page Application) ou un frontend qui est déployé par vos soins : vous pouvez déployer le nouveau frontend simultanément à la nouvelle API, assurant que personne (aucun utilisateur) n’utilise l’ancienne version de l’API. Dans ce cas, maintenir deux versions côte à côte serait un overkill inutile.
Un autre cas fréquent est celui de microservices pair-à-pair au sein d’un système. Par exemple, un microservice Inventaire expose une API consommée par un microservice Commande. Si l’API de Inventaire change, l’équipe en charge peut synchroniser la modification avec l’équipe de Commande (ou c’est la même équipe). Plutôt que de déployer Inventaire v2 et de garder Inventaire v1 actif pendant des mois, on peut choisir de mettre à jour le service Commande pour qu’il utilise immédiatement le nouveau contrat. Cela nécessite de la communication et éventuellement un ordre de déploiement précis (d’abord déployer la nouvelle version d’Inventaire qui supporte à la fois l’ancien et le nouveau format, puis déployer Commande, puis éventuellement retirer la compatibilité ancienne côté Inventaire). Mais tout ceci peut se faire sans jamais formaliser deux versions d’API différentes aux yeux du client final. Aux yeux de Commande, il y a toujours une seule API Inventaire, qui a simplement évolué.
En bref, si le nombre de consommateurs de l’API est limité et connu, et que vous pouvez les faire évoluer rapidement, le versionnement n’apporte pas grand-chose de plus que ce que de bonnes pratiques de déploiement coordonné permettent déjà. Comme l’exprime un architecte : “Si l’API n’est consommée que par vos propres services (donc “pas publique” dans le sens externe), et qu’en plus vous êtes le seul consommateur, la valeur du versionnement est discutable ». Au-delà de 1 consommateur ou d’équipes différentes, il faut commencer à réfléchir, mais tant que vous restez “entre vous”, la contrainte de ne pas casser le client peut être gérée par des processus internes plutôt que par du versioning.
Concevoir des changements non bloquants (évolutivité sans version)
Décider de ne pas versionner par défaut ne signifie pas que l’API ne va jamais changer, au contraire, elle va évoluer, mais on va s’efforcer de le faire sans bris pour les consommateurs. Il s’agit donc d’adopter une philosophie de conception évolutive (evolvable API) où l’on privilégie les modifications rétro-compatibles et les extensions non ruptures.
Voici quelques stratégies de conception et d’évolution qui permettent d’éviter un versionnement explicite tout en faisant évoluer une API :
- N’ajoutez que des éléments facultatifs : si vous devez enrichir la réponse avec de nouvelles données, ajoutez-les sans supprimer ni modifier les champs existants. Par exemple, si votre réponse JSON contient
{ "nom": "X", "age": 30 }et que vous voulez ajouter le sexe, renvoyez{ "nom": "X", "age": 30, "sexe": "F" }. Un client bien conçu ignorera le champ sexe qu’il ne connaissait pas. (⚠️ Attention toutefois : certains clients trop stricts pourraient mal tolérer des champs inconnus dans la réponse ; assurez-vous de la tolérance de vos consommateurs). De même, si vous ajoutez un nouveau champ requis dans une requête, réfléchissez : peut-il avoir une valeur par défaut ? Si oui, faites-en un paramètre optionnel avec un défaut côté serveur, ce qui évite de casser les anciens appels. - Rendez obligatoires les changements vraiment nécessaires, et rien de plus : en d’autres termes, ne faites pas de changement breaking “pour le plaisir”. Si vous envisagez de rendre un champ obligatoire là où il était optionnel, demandez-vous s’il est vraiment impossible de le garder optionnel. Comme le dit Reda Hmeid, “si ce nouveau champ est vraiment obligatoire pour la logique métier, pourquoi maintenir une version de l’API qui ne l’exige pas ?”. Autrement dit, dans la mesure du possible, évitez de rendre une ancienne requête invalide. Parfois, cela signifie que certaines nouvelles règles métier doivent être gérées côté serveur sans intervention du client (par exemple, un service peut décider qu’en l’absence d’un champ optionnel, une valeur par défaut interne sera utilisée, plutôt que de rejeter l’appel).
- Conservez la prise en charge des anciennes conventions en parallèle des nouvelles, temporairement : si vous devez renommer un champ ou corriger une erreur (typo dans un nom, etc.), la règle d’or est : “ne supprimez pas immédiatement l’ancien”. Vous pouvez accepter les deux orthographes du champ pendant un temps, documenter la nouvelle préférence, puis éventuellement retirer l’ancienne quand vous êtes sûr que plus personne ne l’envoie. Par exemple, si votre requête attendait
?ordre=ascet que vous voulez passer à?triAsc=true, votre API peut supporter les deux paramètres (et peut-être ignorer ordre en le dépréciant) sans renvoyer d’erreur, le temps que tous les consommateurs passent au nouveau paramètre. - Préférez de nouveaux endpoints pour de nouvelles fonctionnalités majeures : si la modification change radicalement la structure des données ou le paradigme (par exemple passer d’une API REST à GraphQL, ou fournir des données dans un format complètement différent), il peut être pertinent de créer un nouveau service ou endpoint séparé, plutôt que de transformer l’existant de manière incompatible. Dans l’esprit de Roy Fielding (créateur de REST), ce n’est pas tant une nouvelle version qu’un nouveau service à part entière lorsque le changement est fondamental. Par exemple, GitHub a introduit une API GraphQL à côté de son API REST sans simplement l’appeler “v4” de l’API REST, c’est considéré comme une toute autre API. Cette approche évite de surcharger la notion de version et de mélanger des approches très différentes sous la même étiquette.
- Documentez votre politique de changement : si vous choisissez de faire évoluer l’API sans gestion de versions multiples, il est important de clarifier dans votre documentation que “des changements non breaking peuvent survenir sans incrément de version”. Par exemple, préciser que l’API peut être enrichie de nouveaux champs ou de nouveaux paramètres sans préavis de version, mais que toute suppression ou modification incompatible sera annoncée et gérée de manière spécifique. Ainsi vos consommateurs savent à quoi s’attendre et ne conçoivent pas leur client de façon trop rigide.
En appliquant ces principes, on peut obtenir des APIs dites “évolutives” (evolvable), c’est-à-dire capables de changer progressivement sans briser leurs consommateurs. Roy Fielding recommande par exemple l’utilisation judicieuse de l’hypermedia (HATEOAS) dans les API REST, où les liens entre ressources sont découverts dynamiquement par le client. Cela rend possible de modifier l’URL ou la structure des ressources derrière les liens sans casser le contrat, puisque le client suit les liens fournis plutôt que d’en construire de son côté. Sans aller forcément jusqu’à implémenter HATEOAS, on peut retenir l’idée générale : une API bien conçue doit minimiser les breaking changes afin de ne pas imposer de mise à jour client. Idéalement, “ne cassez jamais vos consommateurs” devient la devise, versioning ou pas versioning.
Exemple : microservice .NET évolutif sans versionnage explicite
Pour illustrer concrètement, prenons l’exemple d’un microservice .NET (disons un Web API ASP.NET Core) dans un système de e-commerce modulable. Ce service gère les produits et expose des endpoints REST pour que d’autres services (commande, panier, recherche, etc.) puissent consulter les informations produit. Supposons qu’à l’origine, l’endpoint GET /api/produits/{id} retourne un JSON avec le nom et le prix du produit. Au fil du temps, on souhaite enrichir cette réponse avec de nouvelles informations (par exemple, la disponibilité en stock, le poids, etc.), et modifier certains champs pour coller à de nouvelles exigences métier.
Sans versionnement, comment procéder pour ne pas casser les services consommateurs (internes) ? On appliquerait les bonnes pratiques ci-dessus : ajouter les champs stock ou poids en plus dans la réponse JSON, sans retirer ni renommer les champs existants. Un service consommateur qui n’a pas été mis à jour pour utiliser ces nouveaux champs continuera de fonctionner comme avant (il ignorera juste les données supplémentaires). Si l’un des nouveaux champs devait être requis pour toutes les commandes, on pourrait décider qu’en absence de ce champ dans la requête du client, la valeur par défaut est assumée côté produit (exemple : si on introduit un champ devise pour le prix, on peut par défaut considérer devise="CAD" si un ancien client ne fournit rien). Ainsi, le microservice Produits devient plus intelligent pour préserver la compatibilité.
Côté implémentation .NET, on peut tirer profit de fonctionnalités du framework : ASP.NET Core, par défaut, ignore gracieusement les champs JSON qu’il ne connaît pas lors de la désérialisation d’une requête dans un objet C#. De même, on peut utiliser des paramètres facultatifs dans les méthodes de contrôleur, avec des valeurs par défaut en C#. Par exemple, public IActionResult ObtenirProduits(guid id, string devise = "CAD") acceptera les appels ne spécifiant pas devise et utilisera “CAD” par défaut, ce qui signifie qu’un ancien client qui ne connaît pas ce paramètre continuera à obtenir un comportement cohérent. Enfin, pour un renommage de champ, ASP.NET Core permet d’accepter plusieurs bindings (via des attributs [FromQuery(Name="...")] par exemple) pour un même paramètre, ce qui facilite la transition.
Si vraiment un changement n’est pas gérable de façon transparente (par exemple, on veut complètement refondre le modèle de données produit), on peut choisir de créer un nouvel endpoint comme /api/nouveaux-produits/{id} ou /api/produits2/{id} sans toucher à l’ancien. Ce n’est pas très différent d’une nouvelle version, sauf qu’on ne l’annonce pas comme tel globalement ; on considère que c’est une autre ressource. Les consommateurs intéressés migreront vers le nouvel endpoint tandis que les autres pourront, pendant un temps, continuer à utiliser l’ancien. La différence subtile est qu’on ne gère pas ça via un cadre formel de “versions v1/v2” annoncé à tout le monde, mais via de la documentation et de la dépréciation ciblée. Souvent, en interne, on peut se permettre de retirer l’ancien endpoint assez rapidement une fois que les consommateurs ont migré (puisqu’on peut les identifier précisément). Ainsi on évite de faire vivre deux versions trop longtemps.
En résumé, dans un contexte de microservices .NET internes, il est tout à fait envisageable de faire évoluer les APIs sans mettre en place de versionnement global par défaut. Cela requiert de la discipline (notamment pour ne pas briser le contrat par inadvertance) et de la coordination, mais en échange on garde un système plus simple : à tout instant, chaque service n’expose qu’une interface (évolutive) au lieu de devoir en maintenir plusieurs en parallèle.
Quand le versionnement devient-il indispensable ?
Tout ce qui précède ne signifie pas que le versionnement est à proscrire en toute circonstance. Certaines situations le rendent quasiment indispensable :
- API publiques ou ouvertes à des tiers : Si votre API est consommée par des applications ou développeurs externes à votre organisation (partenaires, clients, grand public), vous n’avez pas la main sur les mises à jour de ces consommateurs. Ils peuvent tarder à adopter vos changements, ou ne jamais le faire s’ils n’ont pas le besoin ou les ressources. Dans ce cas, il est dangereux d’évoluer sans versionner, car vous risquez de briser des intégrations en production chez autrui. Microsoft le rappelle : “si votre API est publique et utilisée par de multiples applications clientes, vous ne pourrez typiquement pas forcer tous les clients à se mettre à jour immédiatement… il faut déployer de nouvelles versions d’API en parallèle des anciennes pendant un certain temps”. C’est exactement le scénario pour lequel le versionnement a été popularisé. Par exemple, les APIs de Twitter, Facebook, etc., ont des versions multiples gérées sur de longues périodes pour laisser le temps aux intégrateurs de migrer.
- Applications mobiles déployées chez les utilisateurs : Les applications mobiles sont un cas particulier des clients externes, même si c’est votre application mobile consommant votre API, une fois l’application publiée, vous ne contrôlez pas quand les utilisateurs la mettront à jour. Beaucoup gardent d’anciennes versions de l’application installées, parfois pendant des années. Si une ancienne version de l’application dialogue avec votre API, vous serez contraint soit de la tuer (la rendre inutilisable) en changeant l’API sans support backward, soit de maintenir une compatibilité. Le versionnement d’API est alors une solution pour continuer à supporter les anciennes versions de l’application mobile sans empêcher d’avancer. Un développeur partageait ainsi : “attendez-vous à ce que des utilisateurs exécutent votre application mobile pendant très longtemps… la longue traîne des versions mobiles est réelle, surtout sur Android, il faut la prendre en compte”. Concrètement, beaucoup d’éditeurs d’API fixent des politiques (par exemple : “on supporte chaque version d’API pendant 2 ans ou “on supporte les 3 dernières versions majeures”). Cela permet de planifier la déprecation progressive, en communiquant aux clients qu’ils doivent migrer avant une date butoir.
- Cas de refonte complète ou de rupture totale : Parfois, malgré tous vos efforts, une évolution majeure nécessite une rupture franche. Par exemple, une refonte de l’architecture (passage de REST à gRPC ou GraphQL), ou des changements légaux/réglementaires qui imposent de nouvelles réponses non compatibles avec l’existant. Dans ces cas, une nouvelle version d’API s’impose – ou comme dirait Fielding, une nouvelle API tout court. On peut la signaler par un numéro de version (v2) dans l’URL ou le
header Accept, etc. L’important est de planifier cette transition : souvent, on introduit la v2 en parallèle de la v1, on incite progressivement les consommateurs à migrer, puis on finit par éteindre la v1 quand son usage tombe suffisamment (ou à une échéance annoncée). Ce processus doit être géré avec soin (communication, support, peut-être outillage de monitoring pour voir qui utilise quoi). Ce type de changement lourd est heureusement peu fréquent si l’on suit une philosophie d’évolution continue. D’ailleurs, comme mentionné précédemment, GitHub n’a eu que très peu de versions en plus d’une décennie, la dernière en date correspondant à un changement d’architecture (GraphQL) plus qu’à de simples modifications de champs.
En résumé, le versionnement est surtout nécessaire quand on ne peut pas contrôler l’adoption du changement côté client. Si tous vos consommateurs ne peuvent pas être mis à jour instantanément et indépendamment (typiquement le cas grand public), alors versionner évite un choix cornélien entre stagnation et casse. À l’inverse, si vous avez la maîtrise des deux bouts de la chaîne, vous pouvez souvent vous en passer, surtout en restant vigilant à ne pas briser le contrat de l’API.
Conclusion : réfléchissez avant de versionner par défaut
Le versionnement d’API n’est pas une fin en soi, c’est un outil parmi d’autres pour gérer l’évolution des systèmes distribués. Comme tout outil, il a ses avantages et ses inconvénients. Dans un contexte de microservices en .NET au sein d’une même organisation, où l’on a un contrôle sur les clients, il est parfaitement envisageable de s’abstenir de versionner tant que ce n’est pas nécessaire, et de faire évoluer son API de manière continue et compatibile. Cette approche peut simplifier grandement la maintenance en évitant le fardeau de plusieurs versions simultanées.
Avant de céder au réflexe de créer une v1 par défaut, posez-vous les questions suivantes : Qui consomme mon API ? Puis-je mettre à jour ces consommateurs facilement ? Ce changement va-t-il réellement en briser certains, ou puis-je l’introduire de façon transparente ? Suis-je prêt à supporter deux versions en parallèle, et combien de temps ? Souvent, un effort de conception initial permet d’éviter la rupture : “Faites le travail difficile dès la phase de design, et vous obtiendrez des APIs faciles à utiliser, évolutives, maintenables, qui satisferont tout le monde”.
En fin de compte, le meilleur service à rendre aux consommateurs de votre API est de la rendre aussi facile et stable que possible. Si vous pouvez offrir de nouvelles fonctionnalités sans imposer de migration forcée, tout le monde y gagne. Le versionnement reste un outil précieux dans l’arsenal de l’architecte, mais il n’est pas obligatoire par défaut. Utilisez-le consciemment, lorsque le contexte l’exige, et non par dogmatisme. Comme le résume Roy Fielding de façon provocante : “Quelle est la meilleure façon de versionner une API ? Ne le faites pas”. Sans aller forcément jusque-là pour chaque situation, son conseil rappelle qu’il vaut mieux éviter les changements incompatibles que d’en gérer les conséquences via une n-ième version. En d’autres termes, mieux vaut prévenir que guérir : concevez vos APIs pour qu’elles évoluent en douceur, et vous n’aurez peut-être jamais à sortir de v2 😉.