API de versioning

9

Supposons que vous ayez un grand projet pris en charge par une base d'API. Le projet fournit également une API publique que les utilisateurs finaux (ish) peuvent utiliser.

Parfois, vous devez apporter des modifications à la base d'API qui prend en charge votre projet. Par exemple, vous devez ajouter une fonctionnalité nécessitant une modification de l'API, une nouvelle méthode ou nécessitant la modification de l'un des objets ou du format de l'un de ces objets, transmis vers ou depuis l'API.

En supposant que vous utilisez également ces objets dans votre API publique, les objets publics changeront également chaque fois que vous le ferez, ce qui n'est pas souhaitable car vos clients peuvent compter sur les objets API restant identiques pour que leur code d'analyse fonctionne. (tousser les clients C ++ WSDL ...)

Une solution potentielle consiste donc à mettre à jour l'API. Mais lorsque nous disons «version» de l'API, il semble que cela doit également signifier la version des objets API ainsi que la fourniture d'appels de méthode en double pour chaque signature de méthode modifiée. J'aurais donc un simple vieil objet clr pour chaque version de mon api, ce qui semble encore une fois indésirable. Et même si je fais cela, je ne construirai sûrement pas chaque objet à partir de zéro, car cela aboutirait à de grandes quantités de code dupliqué. Au contraire, l'API est susceptible d'étendre les objets privés que nous utilisons pour notre API de base, mais nous rencontrons ensuite le même problème car des propriétés ajoutées seraient également disponibles dans l'API publique lorsqu'elles ne sont pas censées l'être.

Alors, quel est le bon sens qui est généralement appliqué à cette situation? Je connais de nombreux services publics tels que Git pour Windows maintient une API versionnée, mais j'ai du mal à imaginer une architecture qui prend en charge cela sans de grandes quantités de code en double couvrant les différentes méthodes versionnées et les objets d'entrée / sortie.

Je suis conscient que des processus tels que le versioning sémantique tentent de mettre un certain sens sur le moment où des ruptures d'API publiques devraient se produire. Le problème est plus qu'il semble que beaucoup ou la plupart des changements nécessitent de casser l'API publique si les objets ne sont pas plus séparés, mais je ne vois pas un bon moyen de le faire sans dupliquer le code.

Cas
la source
1
I don't see a good way to do that without duplicating code- Votre nouvelle API peut toujours appeler des méthodes dans votre ancienne API, ou vice versa.
Robert Harvey
2
AutoMapper à la rescousse, malheureusement oui - vous avez besoin de versions distinctes de chaque contrat, n'oubliez pas que tous les objets référencés par votre contrat font partie de ce contrat. Le résultat est que votre implémentation réelle doit avoir sa propre version unique de modèles et vous devez transformer la version unique en différentes versions de contrat. AutoMapper peut vous aider ici et rendre les modèles internes plus intelligents que les modèles contractuels. En l'absence d'AutoMapper, j'ai utilisé des méthodes d'extension pour créer des traductions simples entre les modèles internes et les modèles de contrat.
Jimmy Hoffa
Existe-t-il une plate-forme / un contexte spécifique pour cela? (c.-à-d. DLL, API REST, etc.)
GrandmasterB
.NET, avec MVC et Webforms ui, classes dll. Nous avons à la fois une API repos et soap.
Affaire

Réponses:

6

Lors de la maintenance d'une API utilisée par des tiers, il est inévitable que vous deviez apporter des modifications. Le niveau de complexité dépendra du type de changement qui se produit. Ce sont les principaux scénarios qui se présentent:

  1. Nouvelle fonctionnalité ajoutée à l'API existante
  2. Ancienne fonctionnalité déconseillée de l'API
  3. Fonctionnalité existante dans l'API changeant d'une manière ou d'une autre

Ajout de nouvelles fonctionnalités à une API existante

Il s'agit du scénario le plus simple à prendre en charge. L'ajout de nouvelles méthodes à une API ne devrait nécessiter aucune modification des clients existants. Il est sûr de déployer pour les clients qui ont besoin de la nouvelle fonctionnalité car il n'a pas de mises à jour pour un client existant.

Ancienne fonctionnalité déconseillée de l'API

Dans ce scénario, vous devez communiquer aux consommateurs existants de votre API que la fonctionnalité ne sera pas prise en charge à long terme. Jusqu'à ce que vous supprimiez la prise en charge de l'ancienne fonctionnalité (ou jusqu'à ce que tous les clients soient passés à la nouvelle fonctionnalité), vous devez conserver l'ancienne et la nouvelle fonctionnalité d'API en même temps. S'il s'agit d'une bibliothèque fournie, la plupart des langues ont un moyen de marquer les anciennes méthodes comme obsolètes / obsolètes. S'il s'agit d'un service tiers quelconque, il est généralement préférable d'avoir différents points de terminaison pour l'ancienne ou la nouvelle fonctionnalité.

Fonctionnalité existante dans l'API changeant d'une manière ou d'une autre

Ce scénario dépendra du type de changement. Si les paramètres d'entrée n'ont plus besoin d'être utilisés, vous pouvez simplement mettre à jour le service / bibliothèque pour ignorer les données désormais supplémentaires. Dans une bibliothèque, il faudrait que la méthode surchargée appelle en interne la nouvelle méthode qui nécessite moins de paramètres. Dans un service hébergé, le point de terminaison ignore les données supplémentaires et il peut desservir les deux types de clients et exécuter la même logique.

Si la fonctionnalité existante doit ajouter de nouveaux éléments requis, vous devez disposer de deux points de terminaison / méthodes pour votre service / bibliothèque. Jusqu'à la mise à jour des clients, vous devez prendre en charge les deux versions.

d'autres pensées

Rather, the API is likely to extend the private objects we are using for our base API, but then we run into the same problem because added properties would also be available in the public API when they are not supposed to be.

N'exposez pas d'objets privés internes via votre bibliothèque / service. Créez vos propres types et mappez l'implémentation interne. Cela vous permettra d'apporter des modifications internes et de minimiser la quantité de mise à jour que les clients externes doivent faire.

The problem is more that it seems like many or most changes require breaking the public API if the objects aren't more separated, but I don't see a good way to do that without duplicating code.

L'API, qu'il s'agisse d'un service ou d'une bibliothèque, doit être stable au point d'intégration avec les clients. Plus vous prenez de temps pour identifier les entrées et les sorties et les conserver en tant qu'entités distinctes, vous éviterez beaucoup de maux de tête sur la route. Faites en sorte que l'API contracte sa propre entité distincte et mappez-la aux classes qui fournissent le travail réel. Le temps gagné lorsque les implémentations internes changent devrait plus que compenser le temps supplémentaire nécessaire pour définir l'interface supplémentaire.

Ne considérez pas cette étape comme "duplication de code". Bien que similaires, ce sont des entités distinctes qui valent la peine d'être créées. Alors que les modifications de l'API externe nécessitent presque toujours une modification correspondante de l'implémentation interne, les modifications de l'implémentation interne ne doivent pas toujours changer l'API externe.

Exemple

Supposons que vous fournissiez une solution de traitement des paiements. Vous utilisez PaymentProviderA pour effectuer des transactions par carte de crédit. Plus tard, vous obtenez un meilleur taux grâce au processeur de paiement de PaymentProviderB. Si votre API expose les champs de carte de crédit / adresse de facturation de votre type au lieu de la représentation de PaymentProviderA, le changement d'API est 0 car l'interface est restée la même (espérons que de toute façon, si PaymentProviderB nécessite des données qui n'étaient pas requises par PaymentProviderA, vous devez choisir soit soutenir les deux ou garder le pire taux avec PaymentProviderA).

Phil Patterson
la source
Merci pour cette réponse très détaillée. Connaissez-vous des exemples de projets open source que je pourrais parcourir pour savoir comment les projets existants ont fait cela? J'aimerais voir des exemples concrets de la façon dont ils ont organisé le code qui leur permet de le faire sans simplement copier leurs divers codes de construction POCO dans les différents objets de version, car si vous appelez des méthodes partagées, vous devrez le fractionner de la méthode partagée pour pouvoir modifier cet objet pour l'objet versionné.
Affaire
1
Je ne connais pas de bons exemples du haut de ma tête. Si j'ai du temps ce week-end, je pourrais créer une application de test à lancer sur GitHub pour montrer comment certaines de ces choses pourraient être mises en œuvre.
Phil Patterson
1
La partie délicate est qu'à partir d'un niveau élevé, il existe de nombreuses façons différentes d'essayer de minimiser le couplage entre les clients et les serveurs. WCF a une interface appelée IExtensibleDataObject qui vous permet de transmettre des données qui ne sont pas dans le contrat du client et de les envoyer par câble au serveur. Google a créé Protobuf pour la communication entre les systèmes (il existe des implémentations open source pour .NET, Java, etc.). De plus, il existe de nombreux systèmes basés sur des messages qui peuvent également fonctionner (en supposant que votre processus peut être exécuté de manière asynchrone).
Phil Patterson
Ce n'est peut-être pas une mauvaise idée de montrer un exemple spécifique de la duplication de code que vous essayez de supprimer (comme une nouvelle question de dépassement de pile) et de voir quelles solutions la communauté propose. Il est difficile de répondre à la question en termes généraux; donc un scénario spécifique pourrait être mieux.
Phil Patterson