Serveur d'impression

Meilleures pratiques pour la conception d'une API RESTful pragmatique – Bien choisir son serveur d impression

Le 9 septembre 2019 - 34 minutes de lecture

Votre modèle de données a commencé à se stabiliser et vous êtes en mesure de créer une API publique pour votre application Web. Vous réalisez qu'il est difficile d'apporter des modifications significatives à votre API une fois qu'elle est publiée et souhaitez obtenir le plus de solutions possible dès le départ. Maintenant, Internet ne manque pas d'avis sur la conception des API. Mais comme il n’existe pas de norme largement adoptée qui fonctionne dans tous les cas, il vous reste un choix de choix: quels formats devez-vous accepter? Comment devriez-vous vous authentifier? Votre API doit-elle être versionnée?

En concevant une API pour Enchant (une alternative Zendesk), j'ai essayé de fournir des réponses pragmatiques à ces questions. Mon objectif est que l’API Enchant soit facile à utiliser, facile à adopter et suffisamment souple pour permettre à dogfood de fonctionner avec nos propres interfaces utilisateur.



TL; DR

… ou juste sauter au bas et inscription pour les mises à jour

<! –

->

Dernières nouvelles du blog Enchant

Comment faire en sorte que les gens tombent amoureux de votre application

Une application décente vous aide à faire le travail.
Une bonne application vous aide à le faire plus rapidement.
Une excellente application, cependant, le rend sans effort.

… et ces applications, celles qui permettent de se sentir sans effort, sont celles que nous aimons le plus.

Mais qu'est-ce qui fait qu'une application se sent sans effort?
Plus important encore, comment faire en sorte que votre application se sente sans effort?

LIRE PLUS →

Exigences clés pour l'API

La plupart des opinions de conception d'API trouvées sur le Web sont des discussions théoriques axées sur des interprétations subjectives de normes floues, par opposition à ce qui a du sens dans le monde réel. Mon objectif avec cet article est de décrire les meilleures pratiques pour une API pragmatique conçue pour les applications Web actuelles. Je ne fais aucune tentative pour satisfaire une norme si elle ne se sent pas bien. Pour aider à guider le processus de prise de décision, j'ai écrit certaines exigences auxquelles l'API doit s'efforcer:

  • Il devrait utiliser les normes Web là où avoir du sens
  • Il devrait être convivial pour le développeur et explorable via une barre d'adresse de navigateur
  • Il devrait être simple, intuitif et cohérent de rendre l'adoption non seulement facile mais agréable
  • Il devrait fournir suffisamment de flexibilité pour alimenter la majorité de l'interface utilisateur Enchant.
  • Il devrait être efficace, tout en maintenant l'équilibre avec les autres exigences

Une API est l'interface utilisateur d'un développeur. Comme toute interface utilisateur, il est important de veiller à ce que l'expérience de l'utilisateur soit pensée avec soin!

Utiliser des URL et des actions RESTful

Si une chose a été largement adoptée, ce sont les principes RESTful. Celles-ci ont été introduites pour la première fois par Roy Fielding au chapitre 5 de sa thèse sur les architectures logicielles basées sur le réseau.

Les principes clés de REST impliquent la séparation de votre API en ressources logiques. Ces ressources sont manipulées à l'aide de requêtes HTTP où la méthode (GET, POST, PUT, PATCH, DELETE) a une signification spécifique.

Mais que puis-je faire une ressource? Eh bien, ces noms doivent être des noms (pas des verbes!) Qui ont du sens du point de vue du consommateur d’API. Bien que vos modèles internes puissent correspondre parfaitement aux ressources, il ne s'agit pas nécessairement d'un mappage un à un. La clé ici est de ne pas divulguer des détails d'implémentation non pertinents à votre API! Certains des noms de Enchant seraient billet, utilisateur et groupe.

Une fois que vous avez défini vos ressources, vous devez identifier les actions qui leur sont applicables et leur correspondance avec votre API. Les principes RESTful fournissent des stratégies pour gérer les actions CRUD à l'aide de méthodes HTTP mappées comme suit:

  • Procurez-vous des billets – Récupère une liste de billets
  • GET / billets / 12 – Récupère un ticket spécifique
  • POST / billets – Crée un nouveau ticket
  • PUT / billets / 12 – Ticket de mise à jour # 12
  • PATCH / billets / 12 – Met à jour partiellement le ticket # 12
  • SUPPRIMER / billets / 12 – Supprime le ticket # 12

Le grand avantage de REST est que vous exploitez les méthodes HTTP existantes pour implémenter des fonctionnalités significatives sur un seul serveur. /des billets point final. Il n'y a aucune convention de dénomination de méthode à suivre et la structure de l'URL est claire et nette. REPOS FTW!

Le nom du point final doit-il être singulier ou pluriel? La règle Keep-it-simple s'applique ici. Bien que votre grammaticien intérieur vous dise qu'il est faux de décrire une instance unique d'une ressource en utilisant un pluriel, la réponse pragmatique consiste à conserver la cohérence du format de l'URL et à toujours utiliser un pluriel. Ne pas avoir à faire face à une pluralisation bizarre (personne / personnes, oie / oies) améliore la vie du consommateur d'API et est plus facile à mettre en œuvre par le fournisseur d'API (car la plupart des infrastructures modernes gèrent de manière native /des billets et / billets / 12 sous un contrôleur commun).

Mais comment gérez-vous les relations? Si une relation ne peut exister qu'au sein d'une autre ressource, les principes RESTful fournissent des indications utiles. Regardons cela avec un exemple. Un ticket dans Enchant contient un certain nombre de messages. Ces messages peuvent être logiquement mappés au /des billets point final comme suit:

  • GET / tickets / 12 / messages – Récupère la liste des messages pour le ticket # 12
  • GET / tickets / 12 / messages / 5 – Récupère le message n ° 5 du ticket n ° 12
  • POST / tickets / 12 / messages – Crée un nouveau message dans le ticket # 12
  • PUT / tickets / 12 / messages / 5 – Mise à jour du message n ° 5 pour le ticket n ° 12
  • PATCH / tickets / 12 / messages / 5 – Met à jour partiellement le message n ° 5 pour le ticket n ° 12
  • DELETE / tickets / 12 / messages / 5 – Supprime le message n ° 5 pour le ticket n ° 12

Alternativement, si une relation peut exister indépendamment de la ressource, il est logique d'inclure simplement un identifiant pour celle-ci dans la représentation de sortie de la ressource. Le consommateur d'API devrait alors atteindre le noeud final de la relation. Toutefois, si la relation est généralement demandée parallèlement à la ressource, l'API pourrait offrir une fonctionnalité permettant d'incorporer automatiquement la représentation de la relation et d'éviter le second hit de l'API.

Qu'en est-il des actions qui ne s'inscrivent pas dans le monde des opérations CRUD?

C'est là que les choses peuvent devenir floues. Il y a plusieurs approches:

  1. Restructurez l'action pour qu'elle apparaisse comme un champ d'une ressource. Cela fonctionne si l'action ne prend pas de paramètres. Par exemple un Activer l'action pourrait être mappée à un booléen activé champ et mis à jour via un PATCH à la ressource.
  2. Traitez-le comme une sous-ressource avec les principes RESTful. Par exemple, l’API de GitHub vous permet de jouer le rôle principal dans PUT / gists /: id / star et unstar avec DELETE / gists /: id / star.
  3. Parfois, vous ne pouvez vraiment pas mapper l'action sur une structure RESTful sensible. Par exemple, une recherche multi-ressources n'a pas vraiment de sens pour être appliquée au noeud final d'une ressource spécifique. Dans ce cas, /chercher serait le plus logique même si ce n’est pas une ressource. C'est correct – faites ce qui est juste du point de vue du consommateur d'API et assurez-vous qu'il soit clairement documenté pour éviter toute confusion.

SSL partout – tout le temps

Toujours utiliser SSL. Aucune exception. Aujourd'hui, vous pouvez accéder à vos API Web à partir de n'importe quel endroit d'Internet (bibliothèques, cafés, aéroports, etc.). Tous ne sont pas sécurisés. Beaucoup ne chiffrent pas du tout les communications, ce qui permet une écoute ou une usurpation d'identité faciles si les informations d'authentification sont détournées.

Un autre avantage de toujours utiliser SSL est que les communications cryptées garanties simplifient les efforts d’authentification – vous pouvez vous en tirer avec de simples jetons d’accès au lieu de devoir signer chaque demande d’API.

Une chose à surveiller est l’accès non SSL aux URL de l’API. Faire ne pas les rediriger vers leurs homologues SSL. Lancer une erreur dure à la place! La dernière chose que vous souhaitiez est que les clients mal configurés envoient des demandes à un point de terminaison non chiffré, juste pour être redirigés en mode silencieux vers le point de terminaison chiffré.

Documentation

Une API ne vaut que par sa documentation. Les documents doivent être faciles à trouver et accessibles au public. La plupart des développeurs vont consulter la documentation avant de tenter tout effort d'intégration. Lorsque les documents sont cachés dans un fichier PDF ou nécessitent une connexion, ils sont non seulement difficiles à trouver, mais également difficiles à rechercher.

Les documents doivent montrer des exemples de cycles complets de demande / réponse. De préférence, les demandes doivent être des exemples pouvant être collés – des liens pouvant être collés dans un navigateur ou des exemples de boucles pouvant être collés dans un terminal. GitHub et Stripe font un excellent travail avec cela.

Une fois que vous avez publié une API publique, vous vous êtes engagé à ne pas casser des choses sans préavis. La documentation doit inclure tous les programmes et recommandations relatifs à la désapprobation concernant les mises à jour des API visibles de manière externe. Les mises à jour doivent être envoyées via un blog (c'est-à-dire un journal des modifications) ou une liste de diffusion (de préférence les deux!).

Gestion des versions

Toujours version de votre API. La gestion des versions vous permet de parcourir plus rapidement et d'empêcher les demandes non valides d'atteindre les ordinateurs d'extrémité mis à jour. Cela aide également à éviter les principales transitions de version d'API, car vous pouvez continuer à offrir les anciennes versions d'API pendant un certain temps.

Les avis sont partagés sur le point de savoir si une version de l'API doit être incluse dans l'URL ou dans un en-tête. D'un point de vue académique, cela devrait probablement être dans un en-tête. Cependant, la version doit figurer dans l'URL pour que les ressources puissent être explorées par le navigateur (vous vous souvenez des exigences de l'API spécifiées en haut de cet article?).

Je suis un grand fan de l'approche adoptée par Stripe pour la gestion des versions d'API: l'URL a un numéro de version majeur (v1), mais l'API a des sous-versions basées sur la date qui peuvent être choisies à l'aide d'un en-tête de requête HTTP personnalisé. Dans ce cas, la version principale fournit la stabilité structurelle de l'API dans son ensemble, tandis que les sous-versions prennent en compte les modifications moins importantes (déprécations de champs, modifications de points de terminaison, etc.).

Une API ne sera jamais complètement stable. Le changement est inévitable. Ce qui est important, c'est la façon dont ce changement est géré. Des calendriers de dépréciation de plusieurs mois bien documentés et annoncés peuvent constituer une pratique acceptable pour de nombreuses API. Cela revient à ce qui est raisonnable compte tenu du secteur et des éventuels consommateurs de l’API.

Filtrage, tri et recherche des résultats

Il est préférable de garder les URL de ressources de base aussi minces que possible. Les filtres de résultats complexes, les exigences de tri et la recherche avancée (lorsqu'ils sont limités à un seul type de ressource) peuvent tous être facilement implémentés en tant que paramètres de requête au-dessus de l'URL de base. Regardons ces plus en détail:

Filtration: Utilisez un paramètre de requête unique pour chaque champ implémentant le filtrage. Par exemple, lorsque vous demandez une liste de tickets à /des billets vous souhaitez peut-être limiter ceux-ci à ceux qui sont à l'état ouvert. Cela pourrait être accompli avec une requête comme GET / tickets? State = ouvert. Ici, Etat est un paramètre de requête qui implémente un filtre.

Tri: Similaire au filtrage, paramètre générique Trier peut être utilisé pour décrire les règles de tri. Répondez aux exigences de tri complexes en laissant le paramètre de tri entrer dans une liste de champs séparés par des virgules, chacun avec un éventuel négatif unaire impliquant un ordre de tri décroissant. Regardons quelques exemples:

  • GET / tickets? Sort = -priority – Récupère une liste de tickets par ordre de priorité décroissant
  • GET / tickets? Sort = -priority, created_at – Récupère une liste de tickets par ordre de priorité décroissant. Au sein d'une priorité spécifique, les tickets les plus anciens sont classés en premier

Recherche: Parfois, les filtres de base ne suffisent pas et vous avez besoin de la puissance de la recherche en texte intégral. Peut-être utilisez-vous déjà ElasticSearch ou une autre technologie de recherche basée sur Lucene. Lorsque la recherche en texte intégral est utilisée comme mécanisme de récupération d'instances de ressources pour un type de ressource spécifique, elle peut être exposée sur l'API en tant que paramètre de requête sur le noeud final de la ressource. Disons q. Les requêtes de recherche doivent être transmises directement au moteur de recherche et la sortie de l'API doit être au même format qu'un résultat de liste normal.

En combinant ces éléments, nous pouvons créer des requêtes telles que:

  • GET / tickets? Sort = -updated_at – Récupérer les billets mis à jour récemment
  • GET / tickets? State = closed & sort = -updated_at – Récupérer les tickets récemment fermés
  • GET / tickets? Q = return & state = open & sort = -priority, created_at – Récupérer les tickets ouverts avec la plus haute priorité en mentionnant le mot 'retour'

Alias ​​pour les requêtes courantes

Pour rendre l'expérience API plus agréable pour le consommateur moyen, envisagez de regrouper des ensembles de conditions dans des chemins RESTful facilement accessibles. Par exemple, la requête de tickets récemment fermée ci-dessus pourrait être empaquetée comme GET / tickets / récemment_closed

Limiter les champs renvoyés par l'API

Le consommateur d'API n'a pas toujours besoin de la représentation complète d'une ressource. La possibilité de sélectionner et de choisir les champs renvoyés permet en grande partie au consommateur d’API de réduire le trafic réseau et d’accélérer son utilisation de l’API.

Utiliser un des champs paramètre de requête qui prend une liste de champs à inclure séparés par des virgules. Par exemple, la requête suivante extrairait juste assez d’informations pour afficher une liste triée de tickets ouverts:

GET / tickets? Fields = id, sujet, client_nom, updated_at & state = open & sort = -updated_at

Les mises à jour et la création doivent renvoyer une représentation de ressource

Un appel PUT, POST ou PATCH peut apporter des modifications aux champs de la ressource sous-jacente qui ne faisaient pas partie des paramètres fournis (par exemple: timestamps created_at ou updated_at). Pour éviter à un consommateur d'API d'avoir à nouveau recours à l'API pour obtenir une représentation mise à jour, demandez à l'API de renvoyer la représentation mise à jour (ou créée) dans le cadre de la réponse.

Dans le cas d'un POST ayant entraîné une création, utilisez un code d'état HTTP 201 et incluez un en-tête Location qui pointe vers l'URL de la nouvelle ressource.

Faut-il HATEOAS?

Il existe de nombreuses opinions mitigées sur le point de savoir si le consommateur d'API doit créer des liens ou si des liens doivent être fournis à l'API. Les principes de conception RESTful spécifient HATEOAS, qui indique en gros que l'interaction avec un point d'extrémité doit être définie dans les métadonnées fournies avec la représentation en sortie et non sur des informations hors bande.

Bien que le Web fonctionne généralement sur des principes de type HATEOAS (où nous allons à la page d'accueil d'un site Web et suivons des liens basés sur ce que nous voyons sur la page), je ne pense pas que nous soyons prêts pour HATEOAS sur les API pour l'instant. Lors de la navigation sur un site Web, les décisions sur les liens sur lesquels l'utilisateur cliquera sont prises au moment de l'exécution. Cependant, avec une API, les décisions concernant les demandes à envoyer sont prises lors de l'écriture du code d'intégration de l'API, et non au moment de l'exécution. Les décisions pourraient-elles être reportées au temps d'exécution? Bien sûr, cependant, il n’ya pas grand-chose à gagner à ce chemin, car le code ne serait toujours pas en mesure de gérer des modifications importantes de l’API sans se rompre. Cela dit, je pense que HATEOAS est prometteur mais pas encore prêt pour le prime time. Des efforts supplémentaires doivent être consentis pour définir des normes et des outils autour de ces principes afin que son potentiel soit pleinement exploité.

Pour le moment, il est préférable de supposer que l'utilisateur a accès à la documentation et inclut les identifiants de ressources dans la représentation en sortie que le consommateur d'API utilisera lors de la création de liens. Il est avantageux de s'en tenir aux identifiants: les données circulant sur le réseau sont minimisées et les données stockées par les utilisateurs d'API le sont également (car ils stockent de petits identifiants, par opposition aux URL contenant des identifiants).

De plus, étant donné que cet article recommande les numéros de version dans l'URL, il est plus logique à long terme pour le consommateur d'API de stocker les identificateurs de ressources plutôt que les URL. Après tout, l'identifiant est stable d'une version à l'autre, mais pas l'URL qui le représente!

Réponses JSON uniquement

Il est temps de laisser XML derrière dans les API. Il est détaillé, difficile à analyser, à lire, son modèle de données n'est pas compatible avec la manière dont la plupart des langages de programmation modélisent les données et ses avantages en termes d'extensibilité sont sans intérêt lorsque les besoins principaux de votre représentation en sortie sont la sérialisation à partir d'une représentation interne.

Je ne vais pas faire beaucoup d’efforts pour expliquer ce qui précède, car il ressemble à d’autres (YouTube, Gazouillement & Box) ont déjà commencé l’exode XML.

Je vous laisse simplement le graphique Google Trends suivant (API XML vs API JSON) comme matière à réflexion:

Toutefois, si votre clientèle est composée d’un grand nombre de clients entreprises, vous devrez peut-être quand même prendre en charge XML. Si vous devez faire cela, vous vous retrouverez avec une nouvelle question:

Le type de support doit-il changer en fonction des en-têtes Accepter ou de l'URL? Pour assurer l'explorabilité du navigateur, il devrait être dans l'URL. L’option la plus judicieuse serait ici d’ajouter un .json ou .xml extension à l'URL du noeud final.

snake_case vs camelCase pour les noms de champs

Si vous utilisez JSON (JavaScript Notation d'objet) en tant que format de représentation principal, la "bonne" chose à faire est de suivre les conventions de dénomination JavaScript – ce qui signifie camelCase pour les noms de champs! Si vous décidez ensuite de créer des bibliothèques clientes dans différentes langues, il vaut mieux y appliquer des conventions de dénomination idiomatiques – camelCase pour C # et Java, snake_case pour Python & Ruby.

Matière à réflexion: J'ai toujours pensé que snake_case est plus facile à lire que la convention JavaScript de camelCase. Je n'avais aucune preuve pour sauvegarder mes sentiments instables, jusqu'à maintenant. Basé sur une étude de suivi oculaire sur camelCase et snake_case (PDF) de 2010, snake_case est 20% plus facile à lire que camelCase! Cet impact sur la lisibilité affecterait l'explorabilité de l'API et les exemples de documentation.

De nombreuses API JSON populaires utilisent snake_case. Je suppose que cela est dû aux bibliothèques de sérialisation qui suivent les conventions de nommage du langage sous-jacent qu'elles utilisent. Peut-être faudrait-il que les bibliothèques de sérialisation JSON gèrent les transformations de convention de nommage.

Jolie impression par défaut et s'assurer que gzip est pris en charge

Une API qui fournit une sortie compressée en espace blanc n'est pas très amusante à regarder depuis un navigateur. Bien qu’une sorte de paramètre de requête (comme ? joli = vrai) pourrait être fourni pour permettre de jolies impressions, une API qui imprime joliment par défaut est beaucoup plus accessible. Le coût du transfert de données supplémentaire est négligeable, en particulier si vous comparez le coût de la non-mise en œuvre de gzip.

Prenons quelques cas d'utilisation: Que se passe-t-il si un consommateur d'API est en train de déboguer et que son code imprime les données qu'il a reçues de l'API – Elles seront lisibles par défaut. Ou si le consommateur a saisi l'URL générée par son code et l'a frappé directement à partir du navigateur, il sera lisible par défaut. Ce sont de petites choses. De petites choses qui rendent une API agréable à utiliser!

Mais qu'en est-il de tous les transferts de données supplémentaires?

Regardons cela avec un exemple du monde réel. J'ai extrait des données de l'API de GitHub, qui utilise jolie impression par défaut. Je ferai aussi des comparaisons gzip:





$ curl https://api.github.com/users/veesahni> with-whitespace.txt
$ ruby ​​-r json -e 'met JSON JSON.parse (STDIN.read)' < with-whitespace.txt > sans-espace-blanc.txt
$ gzip -c avec-whitespace.txt> avec-whitespace.txt.gz
$ gzip -c sans-espace-blanc.txt> sans-espace-blanc.txt.gz

Les fichiers de sortie ont les tailles suivantes:

  • sans-espace-blanc.txt – 1252 octets
  • with-whitespace.txt – 1369 octets
  • sans-espace-blanc.txt.gz – 496 octets
  • avec-whitespace.txt.gz – 509 octets

Dans cet exemple, les espaces ont augmenté la taille de la sortie de 8,5% lorsque gzip n'est pas en lecture et de 2,6% lorsque gzip est en lecture. D'autre part, l'acte de gzipping en soi a permis d'économiser plus de 60% de la bande passante. Comme le coût d'une jolie impression est relativement faible, il est préférable d'imprimer par défaut et de vous assurer que la compression gzip est prise en charge!

Pour marteler davantage sur ce point, Twitter a constaté qu’il existait un 80% d'économies (dans certains cas) lors de l'activation de la compression gzip sur leur Streaming API. Stack Exchange est allé jusqu'à ne jamais renvoyer une réponse non compressée!

Ne pas utiliser une enveloppe par défaut, mais le rendre possible en cas de besoin

De nombreuses API encapsulent leurs réponses dans des enveloppes comme celle-ci:






  "Les données" : 
    "id": 123,
    "nom": "John"
  

Cela peut être justifié par deux raisons: il est facile d’inclure des métadonnées ou des informations de pagination supplémentaires, certains clients REST ne permettent pas un accès facile aux en-têtes HTTP et les requêtes JSONP n’ont pas accès aux en-têtes HTTP. Cependant, avec les normes qui sont rapidement adoptées telles que CORS et l'en-tête Link de la RFC 5988, l'enveloppement commence à devenir inutile.

Nous pouvons mettre à l’épreuve l’API en conservant l’enveloppe libre par défaut et en n’enveloppant que dans des cas exceptionnels.

Comment utiliser une enveloppe dans des cas exceptionnels?

Une enveloppe est vraiment nécessaire dans 2 situations: si l'API doit prendre en charge les requêtes interdomaine via JSONP ou si le client est incapable de travailler avec des en-têtes HTTP.

Les requêtes JSONP sont fournies avec un paramètre de requête supplémentaire (généralement nommé rappeler ou jsonp) représentant le nom de la fonction de rappel. Si ce paramètre est présent, l'API doit basculer vers un mode enveloppe complète dans laquelle elle répond toujours avec un code d'état 200 et transmet le code d'état réel dans la charge JSON. Tous les en-têtes HTTP supplémentaires qui auraient été transmis avec la réponse doivent être mappés aux champs JSON, comme suit:





callback_function (
  status_code: 200,
  next_page: "https: // ..",
  réponse: 
    ... corps de réponse JSON réel ...
  
)

De même, pour prendre en charge des clients HTTP limités, autorisez un paramètre de requête spécial. ? enveloppe = vrai cela déclencherait un enveloppement complet (sans la fonction de rappel JSONP).

Corps codés JSON POST, PUT & PATCH

Si vous suivez l'approche décrite dans cet article, vous avez adopté JSON pour toutes les sorties d'API. Considérons JSON pour l'entrée API.

De nombreuses API utilisent le codage d'URL dans leurs corps de requête d'API. Le codage d'URL correspond exactement à ce que cela ressemble: corps de la requête où les paires clé-valeur sont codées selon les mêmes conventions que celles utilisées pour coder les données dans les paramètres de requête d'URL. C'est simple, largement pris en charge et fait le travail.

Cependant, le codage d'URL pose quelques problèmes qui le rendent problématique. Il n'a pas de concept de types de données. Cela oblige l'API à analyser les entiers et les booléens en dehors des chaînes. De plus, il n’a pas de concept réel de structure hiérarchique. Bien que certaines conventions permettent de construire une structure à partir de paires clé-valeur (comme ajouter [ ] clé pour représenter un tableau), ce n'est pas une comparaison avec la structure hiérarchique native de JSON.

Si l'API est simple, le codage d'URL peut suffire. Toutefois, les API complexes doivent s’appuyer sur JSON pour leur entrée d’API. Dans les deux cas, choisissez-en un et soyez cohérent dans toute l'API.

Une API qui accepte les requêtes POST, PUT & PATCH codées JSON doit également nécessiter la Type de contenu en-tête être mis à application / json ou émettez un code d'état HTTP de type de support non pris en charge 415.

Les API qui aiment les enveloppes incluent généralement des données de pagination dans l'enveloppe elle-même. Et je ne les en blâme pas – jusqu'à récemment, il n'y avait pas beaucoup de meilleures options. Pour bien inclure les détails de la pagination aujourd'hui, utilisez l'en-tête Link introduit par la RFC 5988.

Une API qui utilise l'en-tête Lien peut renvoyer un ensemble de liens prêts à l'emploi, de sorte que le consommateur d'API ne doit pas créer de liens eux-mêmes. Ceci est particulièrement important lorsque la pagination est basée sur un curseur. Voici un exemple d'en-tête de lien utilisé correctement, extrait de la documentation de GitHub:





Lien: ; rel = "next", ; rel = "last"

Toutefois, cette solution n’est pas complète, car de nombreuses API préfèrent renvoyer les informations de pagination supplémentaires, comme un décompte du nombre total de résultats disponibles. Une API nécessitant l’envoi d’un nombre peut utiliser un en-tête HTTP personnalisé tel que X-Total-Count.

Chargement automatique des représentations de ressources associées

Dans de nombreux cas, un consommateur d'API doit charger des données liées à (ou référencées) à partir de la ressource demandée. Plutôt que d'exiger du consommateur qu'il sollicite à plusieurs reprises l'API pour obtenir ces informations, il serait un gain d'efficacité considérable de permettre le renvoi et le chargement des données associées, à la demande de la ressource d'origine.

Cependant, comme cela va à l’encontre de certains principes RESTful, nous pouvons minimiser notre écart en ne le faisant que sur la base intégrer (ou développer) paramètre de requête.

Dans ce cas, intégrer serait une liste séparée par des virgules de champs à incorporer. La notation par points peut être utilisée pour faire référence à des sous-champs. Par exemple:

GET /tickets/12?embed=customer.name,assigned_user

Cela renverrait un ticket avec des détails supplémentaires, comme:






  "id": 12,
  "sujet": "J'ai une question!",
  "summary": "Salut, ....",
  "client" : 
    "nom": "Bob"
  ,
  utilisateur_attribué: 
   "id": 42,
   "nom": "Jim",
  

Bien entendu, la capacité à mettre en œuvre quelque chose comme cela dépend vraiment de la complexité interne. Ce type d’incorporation peut facilement donner lieu à un problème de sélection N + 1.

Redéfinition de la méthode HTTP

Certains clients HTTP ne peuvent fonctionner qu'avec de simples requêtes GET et POST. Pour augmenter l'accessibilité à ces clients limités, l'API a besoin d'un moyen de remplacer la méthode HTTP. Bien qu'il n'y ait pas de normes strictes ici, la convention populaire est d'accepter un en-tête de requête X-HTTP-Method-Override avec une valeur de chaîne contenant l’un des éléments suivants: PUT, PATCH ou DELETE.

Notez que l'en-tête de substitution doit seulement être accepté sur les demandes POST. Les requêtes GET ne doivent jamais modifier les données sur le serveur!

Taux limitant

Pour éviter les abus, il est de pratique courante d’ajouter une sorte de limitation de débit à une API. La RFC 6585 a introduit un code de statut HTTP 429 Trop de demandes pour répondre à cela.

Cependant, il peut être très utile d'informer le consommateur de ses limites avant qu'il ne le frappe réellement. C’est un domaine qui manque actuellement de normes, mais qui comporte un certain nombre de conventions populaires utilisant des en-têtes de réponse HTTP.

Au minimum, incluez les en-têtes suivants (en utilisant Twitter conventions de nommage les en-têtes n’ayant généralement pas de lettres majuscules):

  • X-Rate-Limit-Limit – le nombre de demandes autorisées dans la période en cours
  • X-Rate-Limit-Remaining – le nombre de demandes restantes dans la période en cours
  • X-Rate-Limit-Reset – le nombre de secondes restantes dans la période en cours

Pourquoi le nombre de secondes restantes est-il utilisé au lieu d'un horodatage pour la réinitialisation du taux limite X?

Un horodatage contient toutes sortes d'informations utiles mais non nécessaires, telles que la date et éventuellement le fuseau horaire. Un consommateur d'API veut vraiment savoir quand il peut envoyer la demande à nouveau et le nombre de secondes répond à cette question avec un traitement supplémentaire minimal de son côté. Cela évite également les problèmes liés au décalage d'horloge.

Certaines API utilisent un horodatage UNIX (secondes depuis l'époque) pour X-Rate-Limit-Reset. Ne fais pas ça!

Pourquoi est-il déconseillé d’utiliser un horodatage UNIX pour X-Rate-Limit-Reset?

La spécification HTTP spécifie déjà l'utilisation de formats de date RFC 1123 (actuellement utilisés dans les en-têtes HTTP Date, If-Modified-Since et Last Modified). Si nous devions spécifier un nouvel en-tête HTTP prenant un horodatage, les conventions RFC 1123 devraient être respectées au lieu d'utiliser des horodatages UNIX.

Authentification

Une API RESTful doit être sans état. Cela signifie que l'authentification de la demande ne devrait pas dépendre de cookies ou de sessions. Au lieu de cela, chaque demande doit être accompagnée de certaines informations d'identification d'authentification.

En utilisant toujours SSL, les informations d'authentification peuvent être simplifiées en un jeton d'accès généré de manière aléatoire qui est fourni dans le champ du nom d'utilisateur de HTTP Basic Auth. La grande chose à propos de cela est que le navigateur est totalement explorable – le navigateur affichera une invite demandant des informations d’identification s’il reçoit un message. 401 non autorisé code d'état du serveur.

Toutefois, cette méthode d'authentification token-basic-basic-auth n'est acceptable que dans les cas où il est pratique de demander à l'utilisateur de copier un jeton depuis une interface d'administration vers l'environnement consommateur d'API. Dans les cas où cela n’est pas possible, OAuth 2 doit être utilisé pour fournir un transfert de jetons sécurisé à un tiers. OAuth 2 utilise des jetons de support et dépend également de SSL pour son cryptage de transport sous-jacent.

Une API devant prendre en charge JSONP nécessitera une troisième méthode d'authentification, car les requêtes JSONP ne peuvent pas envoyer d'informations d'identification HTTP Basic Auth ni de jetons Bearer. Dans ce cas, un paramètre de requête spécial jeton d'accès peut être utilisé. Remarque: l'utilisation d'un paramètre de requête pour le jeton pose un problème de sécurité inhérent, car la plupart des serveurs Web stockent les paramètres de requête dans des journaux de serveur.

Pour ce qui en vaut la peine, les trois méthodes ci-dessus ne sont que des moyens de transporter le jeton à travers la limite de l'API. Le jeton sous-jacent lui-même pourrait être identique.

Caching

HTTP fournit un cadre de mise en cache intégré! Tout ce que vous avez à faire est d’inclure des en-têtes de réponse sortante supplémentaires et de valider un peu lorsque vous recevez des en-têtes de demande entrante.

Il existe 2 approches: ETag et Last-Modified

ETag: Lors de la génération d'une réponse, incluez un en-tête HTTP ETag contenant un hachage ou une somme de contrôle de la représentation. Cette valeur doit changer chaque fois que la représentation en sortie change. Maintenant, si une requête HTTP entrante contient un Si-aucun-match en-tête avec une valeur ETag correspondante, l’API doit renvoyer un 304 non modifié code de statut au lieu de la représentation en sortie de la ressource.

Dernière modification: Cela fonctionne fondamentalement comme pour ETag, sauf qu'il utilise des horodatages. L'en-tête de réponse Dernière modification contient un horodatage au format RFC 1123 qui est validé par Si-Modifié-Depuis. Notez que la spécification HTTP a 3 formats de date acceptables différents et que le serveur doit être prêt à accepter l’un d’eux.

les erreurs

Tout comme une page d'erreur HTML affiche un message d'erreur utile à un visiteur, une API doit fournir un message d'erreur utile dans un format de consommable connu. La représentation d'une erreur ne doit pas être différente de la représentation d'une ressource, mais avec son propre ensemble de champs.

L'API doit toujours renvoyer des codes d'état HTTP sensibles. Les erreurs d'API sont généralement divisées en deux types: les codes d'état de la série 400 pour les problèmes clients et les codes d'état de la série 500 pour les problèmes de serveur. Au minimum, l'API doit indiquer que toutes les erreurs de la série 400 sont accompagnées d'une représentation d'erreur JSON consommable. Si possible (c'est-à-dire si les équilibreurs de charge et les proxys inversés peuvent créer des corps d'erreur personnalisés), cela devrait s'étendre à 500 codes d'état de la série.

Un corps d'erreur JSON devrait fournir quelques éléments au développeur: un message d'erreur utile, un code d'erreur unique (que l'on peut rechercher pour plus de détails dans la documentation) et éventuellement une description détaillée. La représentation en sortie JSON pour quelque chose comme ceci pourrait ressembler à ceci:






  "code": 1234,
  "message": "Quelque chose de mauvais est arrivé :(",
  "description": "Plus de détails sur l'erreur ici"

Les erreurs de validation pour les demandes PUT, PATCH et POST nécessiteront une ventilation par champ. Ceci est mieux modélisé en utilisant un code d'erreur de niveau supérieur fixe pour les échecs de validation et en fournissant les erreurs détaillées dans un fichier supplémentaire. les erreurs champ, comme si:






  "code": 1024,
  "message": "Validation Failed",
  "les erreurs" : [
    
      "code" : 5432,
      "field" : "first_name",
      "message" : "First name cannot have fancy characters"
    ,
    
       "code" : 5622,
       "field" : "password",
       "message" : "Password cannot be blank"
    
  ]

Codes de statut HTTP

HTTP définit un ensemble de codes d'état significatifs pouvant être renvoyés à partir de votre API. Celles-ci peuvent être utilisées pour aider les utilisateurs d’API à acheminer leurs réponses en conséquence. J'ai préparé une courte liste de celles que vous devriez absolument utiliser:

  • 200 OK – Réponse à un succès GET, PUT, PATCH ou DELETE. Peut aussi être utilisé pour un POST qui ne crée pas de création.
  • 201 créées – Réponse à un POST qui aboutit à une création. Doit être combiné avec un en-tête Location indiquant l'emplacement de la nouvelle ressource.
  • 204 Pas de contenu – Réponse à une demande réussie qui ne renvoie pas de corps (comme une demande DELETE)
  • 304 non modifié – Utilisé lorsque les en-têtes de mise en cache HTTP sont en jeu
  • 400 mauvaise demande – La requête est mal formée, par exemple si le corps n'analyse pas
  • 401 non autorisé – Quand aucun détail d'authentification ou invalide n'est fourni. Aussi utile pour déclencher un popup d'authentification si l'API est utilisée depuis un navigateur
  • 403 interdit – Lorsque l'authentification a réussi mais que l'utilisateur authentifié n'a pas accès à la ressource
  • 404 introuvable – Lorsqu'une ressource inexistante est demandée
  • 405 Méthode non autorisée – Lorsqu'une méthode HTTP demandée n'est pas autorisée pour l'utilisateur authentifié
  • 410 est parti – Indique que la ressource à ce point de terminaison n'est plus disponible. Utile comme réponse globale pour les anciennes versions d'API
  • 415 Unsupported Media Type – If incorrect content type was provided as part of the request
  • 422 Unprocessable Entity – Used for validation errors
  • 429 Too Many Requests – When a request is rejected due to rate limiting

En résumé

An API is a user interface for developers. Put the effort in to ensure it's not just functional but pleasant to use.

Developers from Spotify, IBM and eBay already follow this blog.
You should too.

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.