Maison > Article > développement back-end > Gestion des versions API chez Monite
Nous aimons tous avoir de nouveaux outils brillants, mais détestons la corvée de les mettre à jour constamment. Cela s'applique à tout : les systèmes d'exploitation, les applications, les API, les packages Linux. C'est douloureux lorsque notre code cesse de fonctionner à cause d'une mise à jour et c'est doublement douloureux lorsque la mise à jour n'a même pas été initiée par nous.
Dans le développement d'API Web, vous risquez constamment de casser le code de vos utilisateurs à chaque nouvelle mise à jour. Si votre produit est une API, alors ces mises à jour seront terrifiantes à chaque fois. Les principaux produits de Monite sont notre API et notre SDK en marque blanche. Nous sommes une entreprise axée sur les API, nous prenons donc grand soin de maintenir notre API stable et facile à utiliser. Par conséquent, le problème des modifications interrompues figure en tête de notre liste de priorités.
Une solution courante consiste à émettre des avertissements de dépréciation à vos clients et à publier rarement les modifications importantes. Du coup, vos versions peuvent désormais prendre des mois et certaines fonctionnalités doivent rester cachées, voire non fusionnées, jusqu'à chaque version suivante. Cela ralentit votre développement et oblige vos utilisateurs à mettre à jour leur intégration tous les quelques mois.
Si vous publiez plus rapidement, vos utilisateurs vont devoir mettre à jour leur intégration trop souvent. Si vous allongez le délai entre les versions, votre entreprise évoluera plus lentement. Plus vous le rendrez gênant pour les utilisateurs, plus cela sera pratique pour vous, et vice versa. Ce n’est certainement pas un scénario optimal. Nous voulions avancer à notre rythme sans rien casser pour les clients existants, ce qui serait impossible avec une approche de dépréciation régulière. C'est pourquoi nous avons choisi une solution alternative : le Versionnage API.
C'est une idée assez simple : publier toutes les modifications importantes à tout moment mais les masquer sous une nouvelle version de l'API. Il vous offre le meilleur des deux mondes : les utilisateurs ne verront pas leurs intégrations systématiquement interrompues et vous pourrez vous déplacer à la vitesse que vous souhaitez. Les utilisateurs migreront quand ils le souhaitent, sans aucune pression.
Compte tenu de la simplicité de l'idée, elle semble parfaite pour toute entreprise. C’est ce que l’on s’attend à lire dans un blog d’ingénierie typique. Malheureusement, ce n'est pas si simple.
Le contrôle de version des API est difficile, très difficile. Sa simplicité illusoire disparaît rapidement une fois que vous commencez à la mettre en œuvre. Malheureusement, Internet ne vous prévient jamais vraiment car il existe étonnamment peu de ressources sur le sujet. La majorité absolue d'entre eux discutent de l'endroit où placer la version de l'API, mais seuls quelques rares articles tentent de répondre : "Comment pouvons-nous l'implémenter ?". Les plus courants sont :
Les déploiements séparés peuvent devenir très coûteux et difficiles à prendre en charge, la copie de routes uniques ne s'adapte pas très bien aux changements importants, et la copie de l'application entière crée tellement de code supplémentaire que vous commencerez à vous noyer dedans après seulement quelques versions.
Même si vous essayez de choisir le moins cher, le fardeau du versioning vous rattrapera bientôt. Au début, cela semblera simple : ajoutez un autre schéma ici, une autre branche de logique métier là, et dupliquez quelques routes à la fin. Mais avec suffisamment de versions, votre logique métier deviendra rapidement ingérable, beaucoup de vos développeurs confondront les versions d'application et les versions d'API, et commenceront à versionner les données dans votre base de données, et votre application deviendra impossible à maintenir.
Vous pourriez espérer ne jamais avoir plus de deux ou trois versions d'API en même temps ; que vous pourrez supprimer les anciennes versions tous les quelques mois. C’est vrai si vous ne prenez en charge qu’un petit nombre de consommateurs internes. Mais les clients extérieurs à votre organisation n’apprécieront pas l’expérience d’être obligés d’effectuer une mise à niveau tous les quelques mois.
La gestion des versions des API peut rapidement devenir l'un des éléments les plus coûteux de votre infrastructure. Il est donc essentiel d'effectuer des recherches assidues au préalable. Si vous ne prenez en charge que les consommateurs internes, vous aurez peut-être plus de facilité avec quelque chose comme GraphQL, mais cela peut rapidement devenir aussi coûteux que la gestion des versions.
Si vous êtes une startup, il serait sage de reporter la gestion des versions de l'API jusqu'aux étapes ultérieures de votre développement, lorsque vous disposez des ressources nécessaires pour le faire correctement. D’ici là, les dépréciations et les stratégies de changement additives pourraient suffire. Votre API n'aura pas toujours fière allure, mais au moins vous économiserez beaucoup d'argent en évitant le versioning explicite.
Après quelques essais et de nombreuses erreurs, nous étions à la croisée des chemins : nos précédentes approches de versionnement que nous avons mentionnées ci-dessus étaient trop coûteuses à maintenir. À la suite de nos difficultés, j'ai élaboré la liste suivante d'exigences qui seraient requises pour un cadre de gestion de versions parfait :
Malheureusement, il n'existait que peu ou pas d'alternatives à nos approches existantes. C'est à ce moment-là qu'une idée folle m'est venue à l'esprit : et si nous essayions de créer quelque chose de sophistiqué, quelque chose de parfait pour le travail - quelque chose comme la gestion des versions de l'API de Stripe ?
Grâce à d'innombrables expériences, nous disposons désormais de Cadwyn : un framework de gestion de versions d'API open source qui non seulement implémente l'approche de Stripe, mais s'appuie également de manière significative sur celle-ci. Nous parlerons de sa mise en œuvre Fastapi et Pydantic, mais les principes fondamentaux sont indépendants du langage et du framework.
Le problème de toutes les autres approches de gestion des versions est que nous dupliquons trop. Pourquoi devrions-nous dupliquer l'intégralité de l'itinéraire, du contrôleur ou même de l'application alors que seule une infime partie de notre contrat a été rompue ?
Avec Cadwyn, chaque fois que les responsables de l'API ont besoin de créer une nouvelle version, ils appliquent les dernières modifications à leurs derniers schémas, modèles et logique métier. Ensuite, ils créent un changement de version : une classe qui encapsule toutes les différences entre la nouvelle version et une version antérieure.
Par exemple, disons qu'auparavant nos clients pouvaient créer un utilisateur avec une adresse mais que maintenant nous aimerions leur permettre de spécifier plusieurs adresses au lieu d'une seule. Le changement de version ressemblerait à ceci :
class ChangeUserAddressToAList(VersionChange): description = ( "Renamed `User.address` to `User.addresses` and " "changed its type to an array of strings" ) instructions_to_migrate_to_previous_version = ( schema(User).field("addresses").didnt_exist, schema(User).field("address").existed_as(type=str), ) @convert_request_to_next_version_for(UserCreateRequest) def change_address_to_multiple_items(request): request.body["addresses"] = [request.body.pop("address")] @convert_response_to_previous_version_for(UserResource) def change_addresses_to_single_item(response): response.body["address"] = response.body.pop("addresses")[0]
instructions_to_migrate_to_previous_version sont utilisées par Cadwyn pour générer du code pour les anciennes versions API des schémas et les deux fonctions de conversion sont l'astuce qui nous permet de maintenir autant de versions que nous le souhaiterions. Le processus ressemble au suivant :
Une fois que nos responsables de l'API ont créé le changement de version, ils doivent l'ajouter à notre VersionBundle pour indiquer à Cadwyn que ce VersionChange sera inclus dans certaines versions :
VersionBundle( Version( date(2023, 4, 27), ChangeUserAddressToAList ), Version( date(2023, 4, 12), CollapseUserAvatarInfoIntoAnID, MakeUserSurnameRequired, ), Version(date(2023, 3, 15)), )
Ça y est : nous avons ajouté un changement radical mais notre logique métier ne gère qu'une seule version : la dernière. Même après avoir ajouté des dizaines de versions d'API, notre logique métier sera toujours exempte de logique de version, de renommage constant, de si et de convertisseurs de données.
Les changements de version dépendent de l'interface publique de l'API et nous n'ajoutons presque jamais de modifications majeures aux versions d'API existantes. Cela signifie qu'une fois que nous aurons publié la version, elle ne sera pas cassée.
Étant donné que les changements de version décrivent les modifications majeures au sein des versions et qu'il n'y a pas de modifications majeures dans les anciennes versions, nous pouvons être sûrs que nos modifications de version sont complètement immuables – elles n'auront jamais de raison de changer. Les entités immuables sont beaucoup plus faciles à maintenir que si elles faisaient partie de la logique métier, car elles sont en constante évolution. Les changements de version sont également appliqués les uns après les autres, formant une chaîne de transformateurs entre les versions qui peuvent migrer toute demande vers une version plus récente et toute réponse vers une version plus ancienne.
Les contrats API sont bien plus complexes que de simples schémas et champs. Ils comprennent tous les points de terminaison, les codes d'état, les erreurs, les messages d'erreur et même les comportements de logique métier. Cadwyn utilise le même DSL que nous avons décrit ci-dessus pour gérer les points de terminaison et les codes d'état, mais les erreurs et les comportements de la logique métier sont une autre histoire : ils sont impossibles à décrire à l'aide d'un DSL, ils doivent être intégrés dans la logique métier.
This makes such version changes much more expensive to maintain than all others because they affect business logic. We call this property a "side effect" and we try to avoid them at all costs because of their maintenance burden. All version changes that want to modify business logic will need to be marked as having side effects. It will serve as a way to know which version changes are "dangerous":
class RequireCompanyAttachedForPayment(VersionChangeWithSideEffects): description = ( "User must now have a company_id in their account " "if they want to make new payments" )
It will also allow API maintainers to check that the client request uses an API version that includes this side effect:
if RequireCompanyToBeAttachedForPayment.is_applied: validate_company_id_is_attached(user)
Cadwyn has many benefits: It greatly reduces the burden on our developers and can be integrated into our infrastructure to automatically generate the changelog and improve our API docs.
However, the burden of versioning still exists and even a sophisticated framework is not a silver bullet. We do our best to only use API versioning when absolutely necessary. We also try to make our API correct on the first try by having a special "API Council". All significant API changes are reviewed there by our best developers, testers, and tech writers before any implementation gets moving.
Special thanks to Brandur Leach for his API versioning article at Stripe and for the help he extended to me when I implemented Cadwyn: it would not be possible without his help.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!