Comment gérer les API REST de gestion des versions avec le printemps?
J'ai été à la recherche de la façon de gérer une API REST versions à l'aide de Printemps 3.2.x, mais je n'ai pas trouver quelque chose qui est facile à entretenir. Je vais vous expliquer d'abord le problème que j'ai, et puis une solution... mais je me demande si je suis re-inventer la roue ici.
Je veux gérer la version basée sur l'en-tête Accept, et par exemple, si une demande a l'en-tête Accept application/vnd.company.app-1.1+json
, je veux spring MVC transmettre à la méthode qui gère cette version. Et puisque toutes les méthodes de l'API de changement dans le même communiqué, je ne veux pas aller à chacun de mes contrôleurs et de changer quoi que ce soit pour un gestionnaire qui n'a pas changé entre les versions. Aussi, je ne veux pas la logique pour déterminer quelle version utilisée dans le contrôleur eux-mêmes (à l'aide du service des locators), comme le Printemps est déjà la découverte de la méthode à appeler.
Donc pris une API avec les versions 1.0, à 1,8 où un gestionnaire a été introduit dans la version 1.0 et modifié dans la v1.7, je voudrais répondre à ces questions de la manière suivante. Imaginez que le code est à l'intérieur d'un contrôleur, et qu'il y a un code qui est en mesure d'extraire la version de l'en-tête. (Ce qui suit n'est pas valide au Printemps)
@RequestMapping(...)
@VersionRange(1.0,1.6)
@ResponseBody
public Object method1() {
//so something
return object;
}
@RequestMapping(...) //same Request mapping annotation
@VersionRange(1.7)
@ResponseBody
public Object method2() {
//so something
return object;
}
Ce n'est pas possible au printemps que les 2 méthodes ont la même RequestMapping
annotation et le Printemps ne parvient pas à charger. L'idée est que le VersionRange
annotation peut définir une version ouverte ou fermée gamme. La première méthode est valable à partir de la version 1.0 à 1.6, tandis que la seconde pour la version 1.7 à partir de (y compris la dernière version 1.8). Je sais que cette approche des pauses si quelqu'un décide de passer de la version à 99,99, mais c'est quelque chose que je suis OK pour vivre avec.
Maintenant, depuis que ci-dessus n'est pas possible sans une sérieuse refonte de la façon dont des travaux du printemps, j'ai pensé à bricoler avec la façon dont les gestionnaires appariés à des demandes, en particulier pour écrire mes propres ProducesRequestCondition
, et la version de la gamme là. Par exemple
Code:
@RequestMapping(..., produces = "application/vnd.company.app-[1.0-1.6]+json)
@ResponseBody
public Object method1() {
//so something
return object;
}
@RequestMapping(..., produces = "application/vnd.company.app-[1.7-]+json)
@ResponseBody
public Object method2() {
//so something
return object;
}
De cette façon, je peux avoir fermé ou ouvert, version plages définies dans le produit une partie de l'annotation. Je suis en train de travailler sur cette solution maintenant, avec le problème que j'avais encore à remplacer certains Spring MVC classes (RequestMappingInfoHandlerMapping
, RequestMappingHandlerMapping
et RequestMappingInfo
), que je n'aime pas, parce que cela signifie un travail supplémentaire à chaque fois que je décide de mettre à niveau vers une version plus récente de printemps.
J'aimerais avoir des pensées... et surtout, une suggestion pour ce faire, dans un système plus simple, plus facile à maintenir façon.
Modifier
L'ajout d'une prime. Pour obtenir la prime, veuillez répondre à la question ci-dessus, sans suggérer d'avoir cette logique dans le contrôleur eux-mêmes. Le printemps a déjà beaucoup de logique pour sélectionner le contrôleur de la méthode à appeler, et je veux greffer sur que.
Edit 2
J'ai partagé l'origine de POC (avec quelques améliorations) sur github: https://github.com/augusto/restVersioning
- stackoverflow.com/questions/11715874/...
- Je ne comprends pas votre commentaire. Qui dit simplement que vous pouvez utiliser des en-têtes et, comme je l'ai dit, ce printemps de la boîte n'est pas suffisant pour les Api qui sont mises à jour constamment. Pire encore le lien sur cette réponse utilise la version dans l'URL.
- Peut-être pas exactement ce que vous cherchez, mais le Printemps 3.2 prend en charge un "produit" paramètre sur RequestMapping. Le seul inconvénient est que la version de la liste doit être explicite. E. g.,
produces={"application/json-1.0", "application/json-1.1"}
, etc - oui je sais sur ce paramètre. et vous m'a fait réaliser que j'ai fait une erreur dans la question. Depuis le produit n'a pas d'accepter une gamme de versions, je suis en train d'écrire mon propre
ProducesRequestCondition
, qui peut comprendre des versions. Comme je l'ai mentionné ci-dessus, afin d'écrire cette classe, j'ai dû réécrireRequestMappingInfoHandlerMapping
,RequestMappingHandlerMapping
etRequestMappingInfo
, parce queRequestMappingInfo
est définitive :(. - La question/problème ressemble à un doublon pour moi, même si vous en préférez une autre solution de la direction. La accepté de répondre, il vous dit que vous pouvez utiliser des en-têtes, mais d'autres réponses selon le type de contenu permettrait de répondre à la question de trop.
- Il me semble que vous n'avez pas 9 versions de cette ressource, vous ne disposez que de 2. Je dirais que si vous voulez la version de l'API (il semble que vous n'), puis mettre la version dans l'URI. Si vous voulez la version de la ressource (ou sa représentation) que ce critère se réfère, le mettre dans l'en-tête accept. Je ne recommande pas de faire les deux à la fois, ou il sera se salissant (comme vous l'avez découvert).
- Nous avons besoin de l'appui de plusieurs versions de notre Api, ces différences sont généralement des modifications mineures qui permettrait de faire quelques appels de certains clients incompatible (il ne sera pas étrange si nous avons besoin de soutien 4 versions mineures, dont certains points sont incompatibles). J'apprécie la suggestion de le mettre dans l'url, mais nous savons que c'est un pas dans la mauvaise direction, que nous avons un couple d'applications avec la version dans l'URL et il y a beaucoup de travail à chaque fois que nous avons besoin de remonter le version.
- vous fait vous n'avez pas trop. Juste conception de vos changements de l'API manière à ne pas casser la compatibilité ascendante. Juste me donner des exemple des changements que casser la compatibilité et je vous montrer comment faire ces changements dans la non-rupture de la mode.
- Avez-vous eu un coup d'oeil à stackoverflow.com/a/10336769/2615437 qui semble impliquer que votre déclaration "Ce n'est pas possible au printemps que les 2 méthodes ont la même RequestMapping annotation et le Printemps ne parvient pas à charger." n'est pas totalement correcte?
- Je n'ai pas vu que. C'est un proche de la solution pour ce que je fais en ce moment (comme j'ai besoin de mon propre condition), mais ça ressemble question plus propre que ma mise en œuvre
- xwoker vs xworker 🙂
- Je suis avec Alexey sur celui-ci. Suivez sémantique règles de contrôle de version et de minimiser les modifications de version majeure, et vous n'aurez pas à s'immiscer dans la version plages. Si vous êtes invité à faire une série de rétro-changements incompatibles, vous n'êtes pas à la gestion de votre système de très bien. Je sais que ce n'est pas la réponse que vous cherchez, mais parfois la réponse est de ne pas construire une échelle à l'échelle du 20 pieds du mur, à la fin de la ruelle sombre, mais de se tourner et de retourner à la Rue Principale. 🙂
Vous devez vous connecter pour publier un commentaire.
Indépendamment de savoir si le contrôle de version peut être évité en faisant compatible changements (ce qui n'est pas toujours possible quand vous êtes lié par certaines sociétés de lignes directrices ou de votre API clients sont mis en œuvre dans un buggy et qu'ils allaient se briser, même s'ils ne devraient pas) l'abstraction de l'exigence est intéressante:
Comment puis-je faire une demande personnalisée de cartographie qui ne arbitraire des évaluations de valeurs d'en-tête de la demande sans faire l'évaluation dans le corps de la méthode?
Comme décrit dans cette SORTE de réponse vous avez réellement peuvent avoir le même
@RequestMapping
et utiliser un autre annotation à se différencier au cours de l'acheminement qui se passe au cours de l'exécution. Pour ce faire, vous devrez:VersionRange
.RequestCondition<VersionRange>
. Puisque vous avez quelque chose comme un meilleur algorithme de correspondance, vous aurez pour vérifier si les méthodes annotées avec d'autresVersionRange
valeurs fournir une meilleure adéquation de la demande en cours.VersionRangeRequestMappingHandlerMapping
basée sur l'annotation et à la demande de l'état (comme décrit dans le post Comment mettre en œuvre @RequestMapping propriétés personnalisées).
VersionRangeRequestMappingHandlerMapping
avant d'utiliser la valeur par défautRequestMappingHandlerMapping
(par exemple en fixant sa commande à 0).Ce ne serait pas exiger de toute hacky le remplacement de Printemps composants, mais utilise le Printemps de configuration et les mécanismes d'extension donc, il faut travailler, même si vous mettez à jour votre Printemps version (tant que la nouvelle version prend en charge ces mécanismes).
mvc:annotation-driven
. Espérons que le Printemps sera de fournir une version demvc:annotation-driven
dans lequel on peut définir des conditions.Je viens de créer une solution personnalisée. Je suis à l'aide de la
@ApiVersion
annotation en combinaison avec@RequestMapping
annotation à l'intérieur de@Controller
classes.Exemple:
Mise en œuvre:
ApiVersion.java annotation:
ApiVersionRequestMappingHandlerMapping.java (ce qui est surtout copier et coller à partir
RequestMappingHandlerMapping
):Injection dans WebMvcConfigurationSupport:
/v1/aResource
et/v2/aResource
ressembler à différentes ressources, mais c'est juste une représentation différente de la même ressource! 2. à l'Aide des en-têtes HTTP qui est mieux, mais vous ne pouvez pas donner à quelqu'un une URL, parce que l'URL ne contient pas d'en-tête. 3. à l'Aide d'un paramètre de l'URL, c'est à dire/aResource?v=2.1
(btw: c'est la façon dont Google ne versioning)....
Je ne sais pas encore si j'irais avec l'option 2 ou 3, mais je ne vais jamais utiliser 1 de nouveau pour les raisons mentionnées ci-dessus.RequestMappingHandlerMapping
dans votreWebMvcConfiguration
, vous devez remplacercreateRequestMappingHandlerMapping
au lieu derequestMappingHandlerMapping
! Sinon vous rencontrerez des problèmes bizarres (j'ai soudain eu des problèmes avec la mise en veille prolongée initialisation différée en raison de l'huis clos)WebMvcConfigurationSupport
, mais s'étendentDelegatingWebMvcConfiguration
. Cela a fonctionné pour moi (voir stackoverflow.com/questions/22267191/...)Je voudrais encore vous recommandons d'utiliser des URL pour les versions, car dans les URLs @RequestMapping prend en charge les modèles et chemin de paramètres, dont le format peut être spécifiée avec des regexp.
Et à gérer les mises à niveau du client (que vous avez mentionné dans le commentaire), vous pouvez utiliser des alias comme "plus tard". Ou avez sans version de version de l'api qui utilise la dernière version (ouais).
Également à l'aide de chemin paramètres que vous pouvez implémenter une version complexe de la manipulation de la logique, et si vous voulez déjà avoir des plages, vous avez très probablement vouloir quelque chose de plus assez vite.
Voici quelques exemples:
Basé sur la dernière approche, vous pouvez effectivement mettre en place quelque chose comme ce que vous voulez.
Par exemple, vous pouvez avoir un contrôleur qui ne contient que de la méthode de la transperce avec la version de la manipulation.
Dans cette manipulation vous regardez (à l'aide de réflexion/AOP/génération de code bibliothèques) dans certains ressorts de service/composante ou dans la même classe pour la méthode avec le même nom/signature et nécessaires @VersionRange et à l'appeler le passage de tous les paramètres.
J'ai mis en œuvre une solution qui gère PARFAITEMENT le problème avec le repos de contrôle de version.
D'une manière générale, il existe 3 méthodes principales pour le repos versions:
Cheminbasée sur approche, dans laquelle le client définit la version dans l'URL:
Content-Type en-tête, dans lequel le client définit la version en Accepter en-tête:
En-Tête personnalisé, dans lequel le client définit la version de dans un en-tête personnalisé.
La problème avec le première approche est que si vous modifiez la version disons à partir de v1 -> v2, vous avez probablement besoin de copier-coller le v1 ressources qui n'ont pas changé de v2 chemin
La problème avec le deuxième approche est que certains outils comme http://swagger.io/ ne peut pas distincte entre les opérations avec le même chemin, mais les différents Type de Contenu (vérifier la question https://github.com/OAI/OpenAPI-Specification/issues/146)
La solution
Depuis que je travaille beaucoup avec le reste des outils de documentation, je préfère utiliser de la première approche. Ma solution gère le problème avec la première approche, de sorte que vous n'avez pas besoin de copier-coller l'extrémité de la nouvelle version.
Disons que nous avons des versions v1 et v2 pour l'Utilisateur contrôleur:
La exigence est que si je demande le v1 pour la ressource de l'utilisateur-je prendre le "Utilisateur V1" repsonse, sinon, si je demande à la v2, v3 et ainsi de suite je dois prendre le "de l'Utilisateur V2" réponse.
Pour mettre en œuvre ce printemps, nous avons besoin de remplacer la valeur par défaut RequestMappingHandlerMapping comportement:
}
La mise en œuvre se lit la version dans l'URL et lui demande de printemps pour résoudre l'URL .Dans le cas où cette URL n'existe (par exemple, le client a demandé v3) ensuite, nous avons essayer avec v2 et ainsi de un jusqu'à ce que nous trouvons la plus récente version de la ressource.
Afin de voir les avantages de cette mise en œuvre, disons que nous avons deux ressources: l'Utilisateur et l'Entreprise:
Disons que nous avons fait un changement de société "contrat" qui rompt le client. Nous avons donc mettre en œuvre les
http://localhost:9001/api/v2/company
et nous vous demandons de client à changer de v2 plutôt que sur la v1.De sorte que le nouveau demandes de client sont les suivantes:
au lieu de:
La meilleur partie ici, c'est qu'avec cette solution, le client devra obtenir les informations de l'utilisateur à partir de v1 et de la société de l'information à partir de v2 sans la nécessité pour créer un nouveau (même) d'extrémité de l'utilisateur v2!
Reste De La Documentation
Comme je l'ai dit avant que la raison pour laquelle j'sélectionnez l'URL de base de gestion des versions approche est que certains outils comme swagger n'document différemment les points de terminaison avec la même URL mais différents type de contenu. Avec cette solution, les deux points sont affichés depuis différentes URL:
GIT
Solution mise en œuvre au:
https://github.com/mspapant/restVersioningExample/
La
@RequestMapping
annotation prend en charge unheaders
élément qui vous permet d'affiner la requête correspondante. En particulier, vous pouvez utiliser leAccept
en-tête ici.Ce n'est pas exactement ce que vous décrivez, car il n'est pas directement gérer les plages, mais l'élément prend en charge le caractère générique * ainsi que !=. Si au moins vous pouviez sortir avec l'aide d'un joker pour les cas où toutes les versions de soutenir l'effet en question, ou même de toutes les versions mineures d'une version majeure (par exemple, 1.*).
Je ne pense pas en fait, j'ai utilisé cet élément avant (si je l'ai je ne m'en souviens pas), je vais donc juste au large de la documentation à
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html
application/*
et non de pièces de ce type. Par exemple, le suivant n'est pas valide au Printemps"Accept=application/vnd.company.app-1.*+json"
. Ceci est lié à la façon dont le printemps de classeMediaType
œuvresCe propos pour l'utilisation de l'héritage pour le modèle de contrôle de version? C'est ce que j'utilise dans mon projet et il ne requiert pas de configuration spring et me fait exactement ce que je veux.
Cet ensemble permet à peu de la duplication de code et la possibilité de remplacer les méthodes dans les nouvelles versions de l'api avec peu de travail. Il enregistre également le besoin de compliquer votre code source avec la version de commutation de la logique. Si vous n'avez pas de code d'un point de terminaison dans une version, il va saisir la précédente version par défaut.
Par rapport à ce que font les autres, cela semble plus facile. Est-il quelque chose que je suis absent?
Dans produit vous pouvez avoir de la négation. Donc, pour method1 dire
produces="!...1.7"
et dans method2 ont le positif.Le produit est également un tableau de sorte que vous pour method1 vous pouvez dire
produces={"...1.6","!...1.7","...1.8"}
etc (accepter tous sauf 1.7)Bien sûr pas aussi idéal que les plages que vous avez à l'esprit, mais je pense plus facile à entretenir que d'autres personnalisé des trucs si c'est quelque chose de rare dans votre système. Bonne chance!
J'ai déjà essayé à ma version de l'API à l'aide de la URI Versioning, comme:
Mais il y a certains défis en essayant de faire ce travail: comment organiser votre code avec des versions différentes? Comment gérer les deux (ou plus) des versions en même temps? Quel est l'impact lors de la suppression d'une version?
La meilleure alternative que j'ai trouvé n'était pas la version de l'ensemble de l'API, mais de contrôle de la version sur chaque extrémité. Ce modèle est appelé Gestion des versions à l'aide d'en-tête Accept ou Gestion des versions à travers la négociation de contenu:
Mise en œuvre sur le Printemps
D'abord, vous créez un Contrôleur avec un produit de base de l'attribut, qui sera appliqué par défaut pour chaque paramètre à l'intérieur de la classe.
Après cela, créer un scénario où vous avez deux versions d'un point de terminaison pour créer un bon de commande:
Fait! Appelez simplement chaque extrémité à l'aide de l'souhaité en-Tête Http version:
Ou, pour appeler la version deux:
Au sujet de vos inquiétudes:
Comme l'a expliqué, cette stratégie s'appuie sur chaque Contrôleur et le point de terminaison avec sa version actuelle. Vous modifiez uniquement le point de terminaison qui ont des modifications et des besoins d'une nouvelle version.
Et l'Arrogance?
Installation de l'Aisance avec les différentes versions est également très facile à l'aide de cette stratégie. Voir cette réponse pour plus de détails.
Vous pouvez utiliser l'AOP, autour de l'interception
Envisager de faire une demande de cartographie qui reçoit tous les
/**/public_api/*
et dans cette méthode ne rien faire;Après
La seule contrainte est que tout doit être dans le même contrôleur.
Pour les AOP configuration ont un look à http://www.mkyong.com/spring/spring-aop-examples-advice/