Quel est un modèle recommandé pour la planification des points de terminaison REST pour les modifications prévues

25

Essayer de concevoir une API pour des applications externes avec une vision du changement n'est pas facile, mais un peu de réflexion à l'avance peut vous faciliter la vie plus tard. J'essaie d'établir un schéma qui prendra en charge les modifications futures tout en restant rétrocompatible en laissant les gestionnaires de versions précédents en place.

La principale préoccupation de cet article est de savoir quel modèle doit être suivi pour tous les points finaux définis pour un produit / une entreprise donnée.

Schéma de base

Étant donné un modèle d'URL de base que https://rest.product.com/j'ai mis au point que tous les services résident dans le /apilong avec /authet d' autres points d' extrémité à base non de repos tels que /doc. Par conséquent, je peux établir les points de terminaison de base comme suit:

https://rest.product.com/api/...
https://rest.product.com/auth/login
https://rest.product.com/auth/logout
https://rest.product.com/doc/...

Points de terminaison de service

Maintenant pour les points de terminaison eux-mêmes. Préoccupation au sujet POST, GET, DELETEn'est pas l'objectif principal de cet article et est la préoccupation de ces actions elles - mêmes.

Les points de terminaison peuvent être décomposés en espaces de noms et actions. Chaque action doit également se présenter de manière à prendre en charge les modifications fondamentales du type de retour ou des paramètres requis.

Prenant un service de chat hypothétique où les utilisateurs enregistrés peuvent envoyer des messages, nous pouvons avoir les points de terminaison suivants:

https://rest.product.com/api/messages/list/{user}
https://rest.product.com/api/messages/send

Maintenant, pour ajouter la prise en charge de la version pour les futurs changements d'API qui pourraient se casser. Nous pourrions ajouter la signature de la version après /api/ou après /messages/. Étant donné le sendpoint final, nous pourrions alors avoir ce qui suit pour v1.

https://rest.product.com/api/v1/messages/send
https://rest.product.com/api/messages/v1/send

Donc ma première question est, quel est un endroit recommandé pour l'identifiant de version?

Gestion du code du contrôleur

Donc, maintenant que nous avons établi que nous devons prendre en charge les versions antérieures, nous devons donc gérer le code pour chacune des nouvelles versions qui peuvent devenir obsolètes au fil du temps. En supposant que nous écrivons des points de terminaison en Java, nous pourrions gérer cela via des packages.

package com.product.messages.v1;
public interface MessageController {
    void send();
    Message[] list();
}

Cela a l'avantage que tout le code a été séparé par des espaces de noms où toute modification de rupture signifierait qu'une nouvelle copie des points de terminaison de service. Le désavantage de cela est que tout le code doit être copié et que les corrections de bogues souhaitées doivent être appliquées aux versions nouvelles et antérieures doivent être appliquées / testées pour chaque copie.

Une autre approche consiste à créer des gestionnaires pour chaque point de terminaison.

package com.product.messages;
public class MessageServiceImpl {
    public void send(String version) {
        getMessageSender(version).send();
    }
    // Assume we have a List of senders in order of newest to oldest.
    private MessageSender getMessageSender(String version) {
        for (MessageSender s : senders) {
            if (s.supportsVersion(version)) {
                return s;
            }
        }
    }
}

Cela isole maintenant la version de chaque point de terminaison lui-même et rend les correctifs de bogues compatibles avec le port en ne devant dans la plupart des cas être appliqués qu'une seule fois, mais cela signifie que nous devons faire un peu plus de travail sur chaque point de terminaison individuel pour le prendre en charge.

Il y a donc ma deuxième question "Quelle est la meilleure façon de concevoir le code de service REST pour prendre en charge les versions précédentes."

Brett Ryan
la source

Réponses:

13

Il y a donc ma deuxième question "Quelle est la meilleure façon de concevoir le code de service REST pour prendre en charge les versions précédentes."

Une API très soigneusement conçue et orthogonale n'aura probablement jamais besoin d'être modifiée de manière incompatible en amont, donc la meilleure façon est de ne pas avoir de versions futures.

Bien sûr, vous n'obtiendrez probablement pas vraiment cela du premier coup; Alors:

  • Mettez à jour votre API, tout comme vous le planifiez (et c'est l'API qui est versionnée, pas les méthodes individuelles à l'intérieur), et faites beaucoup de bruit à ce sujet. Assurez-vous que vos partenaires savent que l'API peut changer et que leurs applications doivent vérifier s'ils utilisent la dernière version; et conseiller aux utilisateurs de mettre à niveau quand un plus récent est disponible. La prise en charge de deux anciennes versions est difficile, la prise en charge de cinq est intenable.
  • Résistez à l'envie de mettre à jour la version de l'API à chaque "version". Les nouvelles fonctionnalités peuvent généralement être intégrées dans la version actuelle sans casser les clients existants; ce sont de nouvelles fonctionnalités. La dernière chose que vous souhaitez, c'est que les clients ignorent le numéro de version, car il est de toute façon principalement rétrocompatible. Ne mettez à jour la version de l'API que lorsque vous ne pouvez absolument pas avancer sans casser l'API existante.
  • Lorsque vient le temps de créer une nouvelle version, le tout premier client doit être l'implémentation rétrocompatible de la version précédente. L '"API de maintenance" doit elle-même être implémentée sur l'API actuelle. De cette façon, vous n'êtes pas prêt à conserver plusieurs implémentations complètes; uniquement la version actuelle, et plusieurs "shells" pour les anciennes versions. L'exécution d'un test de régression pour l'API désormais obsolète sur le client rétrocompatible est un bon moyen de tester à la fois la nouvelle API et la couche de compatibilité.
SingleNegationElimination
la source
3

La première option de conception d'URI exprime mieux l'idée que vous versionnez l'intégralité de l'API. La seconde pourrait être interprétée comme une version des messages eux-mêmes. C'est donc mieux l'OMI:

rest.product.com/api/v1/messages/send

Pour la bibliothèque client, je pense que l'utilisation de deux implémentations complètes dans des packages Java distincts est plus propre, plus facile à utiliser et plus facile à entretenir.

Cela étant dit, il existe de meilleures méthodes pour faire évoluer les API que la gestion des versions. Je suis d'accord avec vous que vous devez vous y préparer, mais je pense au versioning comme méthode de dernier recours, à utiliser avec prudence et parcimonie. La mise à niveau représente un effort relativement important pour les clients. Il y a quelque temps, j'ai mis certaines de ces réflexions dans un article de blog:

http://theamiableapi.com/2011/10/18/api-design-best-practice-plan-for-evolution/

Pour le contrôle de version de l'API REST en particulier, vous trouverez cet article de Mark Nottingham utile:

http://www.mnot.net/blog/2011/10/25/web_api_versioning_smackdown

Ferenc Mihaly
la source
3

Une autre approche pour gérer le contrôle de version d'API consiste à utiliser Version dans les en-têtes HTTP. Comme

POST /messages/list/{user} HTTP/1.1
Host: http://rest.service.com
Content-Type: application/json
API-Version: 1.0      <----- like here
Cache-Control: no-cache

Vous pouvez analyser l'en-tête et le gérer de manière appropriée dans le backend.

Avec cette approche, les clients n'ont pas besoin de changer l'URL, juste l'en-tête. Et cela rend toujours les points d'extrémité REST plus propres.

Si l'un des clients n'a pas envoyé l'en-tête de version, vous envoyez soit 400 - Mauvaise demande ou vous pouvez le gérer avec une compatibilité descendante version de votre API.

sincerekamal
la source