Plan du site  
pixel
pixel

Articles - Étudiants SUPINFO

De la conception d'une API REST - La pratique

Par Rémi PARENT Publié le 31/10/2015 à 23:57:17 Noter cet article:
(0 votes)
Avis favorable du comité de lecture

Lors de notre première partie théorique sur la conception d'une API REST - que je vous recommande le lire pour suivre cet article dans les meilleures conditions, nous avons découvert ce qu'était une API, ses enjeux et les spécificités d'une API RESTful.

 

Cette fois-ci, je vous propose de plonger un peu plus dans la technique en découvrant les bonnes pratiques de la conception d'une API web RESTful en prenant pour exemple un service qui gèrerait les commandes d'une pizzeria.

L'identification des ressources

Dans une API REST, les données sont appelées des ressources. Elles sont identifiées grâce à des URLs respectant un format bien particulier.

Deux URLs par ressource, ni plus, ni moins

Chaque ressource doit être identifiée par exactement deux URLs différentes : une ciblant l’ensemble de ses éléments et une ciblant un élément en particulier. Voyons comment nous pouvons identifier les pizzas à la carte de notre restaurant.

 

Pour récupérer l'ensemble des pizzas à la carte :

 

https://api.pizzaplanet.com/pizzas

 

Pour récupérer les informations sur une pizza en particulier :

 

https://api.pizzaplanet.com/pizzas/42

Utiliser des noms, pas des verbes

Inutile de compliquer l’identification et la manipulation des ressources avec des URLs contenant des verbes telles que :

 

https://api.pizzaplanet.com/createpizza

 

https://api.pizzaplanet.com/updatepizza/42

 

https://api.pizzaplanet.com/pizzas/12/remove

 

Ces URLs peuvent être utilisées avec des API orientées RPC - souvenez-vous, le Remote Procedure Call, telles que les APIs SOAP, davantage orientées actions.

 

En revanche, dans le cas d’une API REST, les ressources doivent être identifiées grâce à un nom et manipulées à l’aide de méthodes HTTP - comme nous le verrons par la suite – et ce dans le but de conserver un couplage faible entre identification et manipulation.

 

Les URLs présentées ci-dessus seraient alors remplacées par les suivantes, bien plus claires, accompagnées de la méthode http adaptée :

 

POST https://api.zzaplanet.com/pizzas

 

PUT https://api.zzaplanet.com/pizzas/42

 

DELETE https://api.zzaplanet.com/pizzas/12

Le pluriel et uniquement le pluriel

Vous avez sans doute remarqué que les deux URLs utilisées pour identifier nos pizzas contenaient toutes les deux un nom au pluriel. Dans le premier cas, on attend une collection ; c’est normal. Dans le second, on identifie un seul élément, pourquoi utiliser le pluriel ? Il s’agit d’une convention de nommage qui fait sens pour plusieurs raisons.

 

En premier lieu, on conserve une uniformité entre les deux URLs : la seconde est une extension de la première, on sélectionne un élément de la collection /pizzas.

 

On évite également les problèmes liés aux pluriels irréguliers. Imaginez les URLs d’une API où les dentistes devraient identifier des dents :

 

https://api.mydentistiscool.com/teeth

 

https://api.mydentistiscool.com/tooth/45

Des sous-ressources pour les relations

Afin de représenter les relations d'une ressource, il convient d'utiliser des sous-ressources. Autrement dit, nous créons à nouveaux deux URLs raccordées à une ressource donnée. Pour illustrer mon propos, tentons d'identifier le contenu des commandes (orders) de notre pizzeria.

 

Une commande particulière serait identifiée par cette URL :

 

https://api.zzaplanet.com/orders/381

 

Pour identifier l'ensemble des éléments de cette commande, on utiliserait cette URL :

 

https://api.zzaplanet.com/orders/381/items

 

Enfin, pour identifier un élément particulier de la commande, nous utiliserions celle-ci :

 

https://api.zzaplanet.com/orders/381/items/816

 

Pour des raisons de lisibilité, sauf cas exceptionnel, il est déconseillé d'ajouter un niveau supplémentaire à ces sous-ressources. Il est généralement possible de revoir son organisation de manière à les représenter autrement - et plus proprement.

Manipulation des ressources

Après avoir vu comment identifier une ressource, tentons maintenant de les utiliser. La plupart des actions effectuées sur des APIs REST sont des opérations CRUD : Create, Read, Update, Delete (Créer, Lire, Modifier, Supprimer). Et ça tombe bien : il existe des méthodes HTTP pour cela !

Utiliser des méthodes HTTP appropriées

Présentons sans détour les méthodes principales du protocole HTTP utilisées pour manipuler nos ressources :

 

  • GET permet de lire des données, autrement dit de récupérer la représentation d'une ressource. Elle s'applique à la fois sur une collection et sur une ressource en particulier ;

     

  • POST permet de créer une ressource. Elle s'applique sur la collection à laquelle on veut ajouter la ressource ;

     

  • PUT et PATCH permettent toutes les deux de modifier une ressource. Elles s'appliquent toutes les deux sur une ressource en particulier mais adoptent une démarche différente : PUT remplace complètement la ressource existante là où PATCH donne des instructions de modifications. Cette dernière, bien que plus complexe à mettre en place, ne renvoie pas la totalité de la ressource et utilise donc moins de bande passante. L'article de William Durand à ce sujet en fait une description plus exhaustive ;

     

  • DELETE permet de supprimer une ressource. Elle peut s'appliquer sur une ressource en particulier comme sur une collection. Dans le second cas de figure, elle supprime bien évidemment tous les éléments de cette collection.

     

  • HEAD est une méthode similaire à GET à la seule différence que seules les en-têtes HTTP sont envoyées - pas le corps. Cela permet de récupérer les métadonnées tout en maîtrisant le coût de la requête en bande passante.

     

On peut à présent établir un lien avec la première partie de cet article. Je vous conseillais de ne pas utiliser de verbes dans vos URLs d'identifications. C'est inutile, HTTP l'a fait pour nous et chacun d'eux a un sens. Ceux-ci ayant été développés ci-dessus, la requête suivante doit désormais vous paraître absurde :

 

GET https://api.zzaplanet.com/pizzas/12/delete

Principe d'idempotence

Difficile d'aborder le sujet des méthodes utilisées pour interagir avec son API REST sans développer le principe d'idempotence. On dit d'une méthode qu'elle est idempotente si elle laisse le système dans le même état après chaque appel identique. En d'autres termes, on peut répéter cette même requête autant que fois que nous le souhaitons : le résultat sera le même.

 

L'avantage que présentent les méthodes idempotentes est qu'en cas d'erreur, le client peut renvoyer autant de fois la requête qu'il le souhaite, jusqu'à obtenir une réponse. Bien sûr, c'est aux développeurs du service de respecter ces contraintes d'idempotence.

 

Les méthodes de lectures GET et HEAD, par définition, n'altèrent pas le système. Elles sont donc considérées comme idempotentes. Puisqu'elles ne touchent absolument à rien, on dit également qu'elles sont « safe ».

 

La méthode de suppression DELETE laisse toujours le système dans le même état : après le premier appel, la ressource est supprimée. Lors des appels suivants, la ressource est déjà supprimée - le système est donc dans le même état qu'après la première requête. Cette méthode est de ce fait également idempotente. Cependant, la réponse du serveur sera différente : un code 200 Successful ou 204 No-Content sera renvoyé sur la première requête, une erreur 404 Not-Found viendra avertir le client de l'absence de la ressource pour les suivantes.

 

Penchons nous sur le cas de l'ajout de ressources. La méthode POST va ajouter une nouvelle ressource à chaque requête, même si le corps de la requête est identique. L'état du système est donc différent après chaque appel : il y a une ressource supplémentaire. Elle n'est donc pas idempotente.

 

En mettant à jour une ressource avec la méthode PUT, on obtient un état identique : après chaque appel, la ressource identifiée sera celle envoyée dans le corps de la requête. Idempotente.

 

En revanche, mettre à jour une ressource avec la méthode PATCH peut être idempotent comme ne pas l'être, en fonction des modifications demandées.

Overrider les méthodes HTTP

Il arrive que certains clients HTTP n'acceptent que les requêtes GET et POST. Pas d'inquiétude, il y a une solution pour ça aussi. Le protocole HTTP propose une en-tête X-HTTP-Method-Override prenant comme valeur la méthode choisie : PUT, PATCH ou DELETE.

 

Attention toutefois : ce header ne doit être utilisé qu'au sein d'une requête POST, les requêtes GET ayant pour propriété de ne pas altérer l'état des données.

Et si ce n'est pas CRUD ?

Votre service nécessite peut-être d'effectuer des actions qui ne sont pas des opérations CRUD. Plusieurs choix s'offrent à vous :

  • Faire en sorte que cette action se résume à la modification d'un champ, modification effectuée à l'aide d'une requête PUT, voire mieux, d'une requête PATCH ;

     

  • Gérer cette action en tant que sous-ressource. Imaginons une fonctionnalité permettant d'apposer un like sur votre pizza préférée. On l'apposerait et l'annulerait comme suit :

     

    PUT https://api.zzaplanet.com/pizza/12/like

     

    DELETE https://api.zzaplanet.com/pizza/12/like

     

  • Briser l'une des premières règles du REST-club : "On n'identifie pas une ressource avec un verbe". Pour cette fois, on ferme les yeux. Mettons en place un système de recherche afin de trouver les pizzas contenant nos ingrédients préférés :

     

    GET https://api.zzaplanet.com/search?contains=pepperoni,tomato

Représentation des ressources

Nous savons à présent récupérer des données grâce à l'identification des ressources et à la méthode GET. Qu'en est-il de la forme de ces données ?

Préférer le JSON au XML

Si vous souhaitez ouvrir votre API à un maximum de développeurs et ne laisser personne de côté, plusieurs formats de représentation peuvent cohabiter - XML et JSON en tête.

 

Cependant, si vous devez faire un choix, préférez le JSON. Ce format, bien que relativement récent, a désormais dépassé le XML dans les APIs modernes. Et ce n'est pas le fruit du hasard :

  • Le XML est difficile à lire pour un humain alors qu'un document JSON est bien plus intuitif ;

     

  • Le XML est verbeux ;

     

  • Le XML est plus compliqué à parser. Le JSON, quant à lui, est lisible par du code Javascript, utilisé par les frameworks front-end, dès sa réception.

     

Pour illustrer ces propos, voici deux représentations possibles d'une ressource pizza en XML et en JSON :

 

<pizza>
	<id>12</id>
	<name>Margherita</name>
	<price>9.5</price>
	<ingredients>
		<ingredient>tomato</ingredient>
		<ingredient>mozzarella</ingredient>
		<ingredient>olive</ingredient>
	</ingredients>
</pizza>
			
{
	pizza: {
		id: 12,
		name: "Margherita",
		price: 9.5,
		ingredients: [
			"tomato",
			"mozzarella",
			"olive"
		]
	}
}
			

Retourner une représentation lors d'une création ou d'une modification

Beaucoup d'APIs se contentent de faire savoir que l'opération de création ou de modification s'est déroulée comme prévu en renvoyant un code HTTP 200 Successful voire, de manière plus appropriée, 204 No-Content sans donner plus d'informations.

 

Pourtant, certains champs peuvent avoir été modifiés : les dates de création et de modification de la ressource ont été mises à jour, l'identifiant a été généré s'il s'agit d'une création, d'autres champs encore ont pu être affectés. Pour éviter au client de devoir envoyer une nouvelle requête GET (augmentant la charge serveur d'une part, faisant perdre de précieuses millisecondes au client d'autre part), on retournera une représentation de la ressource créée ou modifiée.

 

Après une création, un code 201 Created sera retourné accompagné d'une en-tête Location contenant l'URL de la ressource fraîchement créée.

 

Dans le cas d'une modification, on préfèrera un code 200 Successful.

Renseigner le format

Evitons au client de devoir détecter lui-même le format de la représentation. Pour ce faire, deux écoles s'affrontent :

  • La version pratique : placer le format comme une extension de la ressource :

     

    GET https://api.zzaplanet.com/pizzas/12.json

     

    De cette manière, la requête et la réponse utilisent le même moyen de connaître le format demandé dans un sens et renvoyé dans l'autre : l'URL.

     

  • La version académique, qui respecte les standards HTTP : placer le format dans une en-tête HTTP. Pour demander un format spécifique lors d'une requête, on utilisera l'en-tête Accept. Lors d'une réponse du serveur, celui-ci renverra une en-tête Content-Type. La valeur à utiliser pour récupérer du JSON est application/json.

     

Limiter les champs affichés

Tous les champs d'une ressource ne sont pas forcément utiles aux clients qui la consomment. En conséquence, une utilisation de la bande passante importante pour une utilité moindre.

 

On peut également laisser le choix à l'utilisateur d'afficher les champs comme bon lui semblent avec un paramètre d'URL. Si je souhaite uniquement connaître le nom et le prix d'une pizza, je pourrais alors envoyer la requête suivante :

 

GET https://api.zzaplanet.com/pizzas/12?fields=name,price

Versionnement

Au fil du temps, notre API va sans doute évoluer pour prendre en compte davantage d'informations et exposer de nouvelles fonctionnalités. L'architecture REST permet de modifier l'implémentation côté serveur des méthodes exposées de manière transparente pour les clients. En revanche, si leur utilisation est revue (paramètres en entrée, format des ressources en sortie), il faut monter de version tout en préservant l'état de la version actuelle intacte. Autrement, le fonctionnement des clients basés sur cette version serait impacté.

Format de version

Il est délicat d'utiliser des points dans une URL, ceux-ci pouvant être mal interprétés. On veillera donc à ne donner que des numéros de version entiers en débutant par la version 1. Ce numéro de version sera précédé d'un V minuscule. Ainsi, nous aurons successivement les versions v1, v2, v3 etc.

Renseigner le numéro de version

Exactement comme pour le format des représentations - et pour exactement les mêmes raisons -, deux styles cohabitent :

  • Placer le numéro de version dans l'URL d'identification des ressources, le plus haut possible dans la hiérarchie (le plus à gauche dans l'URL) :

     

    GET https://api.zzaplanet.com/v1/pizzas/12

     

    Un des arguments des détracteurs de cette approche est que modifier l'URL d'identification d'une ressource signifie désigner une autre ressource, ce qui n'est pas le cas - seule la représentation peut changer, pas ce qu'elle représente.

     

  • Placer le numéro de version dans une en-tête HTTP Accept, au même titre que le format de la représentation souhaité si c'est cette méthode que vous avez choisie. L'en-tête serait donc Accept: application/json; version=v1.

Gestion des erreurs

Tout système informatique peut rencontrer des problèmes, fussent-ils de la faute des utilisateurs ou de celle des développeurs. Comme dans tout système informatique, le traitement des erreurs doit recevoir une attention particulière. Si un problème survient lors d'une requête envoyée par un client, il doit en être informé de la manière la plus précise possible. Voici comment nous allons procéder.

Renvoyer des codes HTTP appropriés

Le protocole HTTP possède des dizaines de codes, en particulier de codes d'erreur. Profitons-en pour renseigner le client sur l'anomalie survenue. Intérerrons nous particulièrement aux erreurs 4xx qui nous informent d'une erreur client :

  • 400 Bad Request : la requête n'a pas été envoyée dans la forme demandée par le service. Des paramètres manquants ou le corps de la requête au mauvais format peuvent en être la cause ;

     

  • 401 Unauthorized : l'utilisateur n'est pas authentifié et devrait l'être pour accéder à la ressource demandée. Si l'utilisateur est authentifié mais n'a pas les droits nécessaires, c'est une erreur 403 Forbidden qui sera levée ;

     

  • 403 Forbidden : les droits de l'utilisateur ne sont pas suffisants pour accéder à la ressource demandée ;

     

  • 404 Not found : bien connue des informaticiens, elle signifie que la ressource demandée n'existe pas ;

     

  • 405 Method not allowed : la méthode HTTP utilisée sur cette ressource n'est pas disponible ;

     

  • 406 Not Acceptable : équivalent du code d'erreur 405 pour le format de représentation demandé (en-tête Accept);

     

  • 429 Too many requests : le client a dépassé la limite d'utilisation du service pour un temps donné - utile pour éviter la surchage.

     

Messages d'erreur

En plus des codes d'erreur HTTP, le service doit renvoyer dans le corps de la réponse un message d'erreur décrivant le problème et indiquant un code d'erreur interne qui fait référence à un paragraphe de la documentation (que le développeur prendra soin de rédiger !).

 

{
	code: 4587,
	message: "Unable to create pizza. Check that your request body provides the required informations."
}
			

 

Pour aller plus loin, on peut même décrire les problèmes de validations des champs envoyés :

 

{
	code: 4587,
	message: "Unable to create pizza. Check that your request body provides the required informations.",
	errors: [
		{
			code: 8632,
			field: "name",
			name: "Name must be at least 3 caracters."
		},
		{
			code: 8645,
			field: "price",
			name: "Price must be an number. String given."
		}
	]
}
			

Sécuriser son API

Comme pour un site web, l'accès à votre service doit être sécurisé pour ne laisser l'accès qu'à des utilisateurs de confiance identifiés.

Authentification

Le protocole HTTP est par définition un protocole sans état. Cela signifie qu'aucune information de connexion n'est stocké en session côté serveur et que chaque requête est indépendante. Le client doit donc fournir des informations de connexion lors de chacune d'entre elles.

 

Le meilleur moyen de gérer l'authentification d'un client à une API est de stocker un jeton côté client qui sera envoyé au serveur et qui contient les informations de connexion chiffrées. Ces jetons peuvent être générés par des protocoles comme OAuth2 et des librairies telles que Json Web Tokens

 

Pour envoyer ce jeton de connexion, on peut utiliser un paramètre d'URL mais on préfèrera une nouvelle fois l'en-tête HTTP indiquée pour cette tâche, Authorization: Basic [jeton].

SSL sur toutes les requêtes

Vous avez sans doute remarqué que toutes les URLs d'exemple utilisaient en réalité le protocole HTTPs. Il est fortement recommandé de n'utiliser que ce protocole pour correspondre avec l'API. De cette façon, les informations reçues seront chiffrées et le jeton de connexion communiqué, malgré son chiffrement, n'en sera que plus protégé.

 

Si un client tente de contacter l'API avec le protocole HTTP, ne le redirigez pas vers l'URL HTTPS correspondante. Préférez renvoyer un message d'erreur.

Limiter l'utilisation

Nous en parlions dans la section consacrée aux erreurs. Bien qu'il s'agisse davantage d'un souci de performance, il est tout de même bon de prévenir une éventuelle surcharge qui pourrait rendre indisponible votre service.

Tester son API

« Tester, c'est douter » vous diront certains. Il est évidemment impératif de s'assurer du bon fonctionnement de son service avant de l'ouvrir aux clients. De nombreux outils sont à votre disposition pour remplir cette tâche, parmi lesquels PostMan est l'un des plus complets.

Le mot de la fin

C'est ainsi que se conclut ce guide de bonnes pratiques sur la conception d'une API web RESTful. Bien sûr, certains de ces conseils sont discutables et donnent parfois lieu à des débats byzantins sur les différentes forums spécialisés. N'oubliez pas, votre API sera utilisée par des utilisateurs externes : rendez leur la vie facile en exposant votre service de manière à rendre leur expérience la plus agréable possible.

A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Toeic, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels