REST, CRUD et bonnes pratiques

REST, CRUD et bonnes pratiques

Bref rappel des méthodes HTTP

Pour rappel, voici les principaux verbes HTTP que nous utilisons pour des architectures REST :

  • GET : Accède à une ressource
  • HEAD : Accède aux métadonnées d’une ressource
  • POST : Ajoute une ressource
  • PUT : Met à jour une ressource complète en la remplaçant par une nouvelle version (99% des cas).
  • PATCH : Met à jour une partie d’une ressource en envoyant un différentiel (une sorte de ‘git diff’)
  • DELETE : Supprime une ressource

Méthodes safes (sûres) et idempotents (idempotentes)

La RFC7231 (remplaçante de la RFC2616) a ajouté deux notions aux méthodes HTTP :

  • Les méthodes dites safes ne modifient pas les données sur le serveur. Peu importe le nombre de fois qu’elles sont appelées.
  • Les méthodes idempotents quant à elles peuvent modifier les données lors du premier appel. Lors des appels suivants, la réponse sera tout le temps identique. En cas de panne/problème réseau, vous pourrez donc lancer la requête sans craintes puisque le comportement est le même que vous exécutiez la requête une ou plusieurs fois.
Méthode Safe Idempotente
GET Oui Oui
HEAD Oui Oui
POST Non Non
PUT Non Oui
PATCH Non Non
DELETE Non Oui

Focus sur le DELETE : On serait tenté de dire que DELETE n’est pas idempotent car on ne peut pas supprimer deux fois le même objet. Ceci étant, si l’on regarde la RFC, DELETE doit bien retourner un code 200 (Success) en cas de réussite et non pas un 204 (No Content). Si l’API n’a pas été bien conçue et qu’elle renvoie un 404 (Not Found), alors le DELETE ne sera plus idempotent comme elle devrait l’être.

Cachabilité

“La requête la plus performante est celle que l’on a pas à faire !”
Un inconnu

La mise en cache est l’une des contraintes les plus importantes des architectures web. En effet, elle permet de :

  • décharger votre serveur en lui évitant de refaire la même chose deux fois,
  • réduire le temps de traitement d’une requête,
  • …et donc de renvoyer plus rapidement l’information au client.

Les méthodes dites safes sont donc à mettre en cache !

Bonnes/mauvaises pratiques

Il y a malheureusement, trop d’API conçue comme ceci :

POST api.mon-application.org/utilisateurs/rechercher
POST api.mon-application.org/utilisateurs/42/supprimer
POST api.mon-application.org/utilisateurs/42/modifier
GET api.mon-application.org/utilisateurs/supprimer?id=42
GET api.mon-application.org/utilisateurs/42?action=supprimer
GET api.mon-application.org/utilisateurs/42?action=envoyerMailConfirmation

Ce sont de mauvaises pratiques liées à une mauvaise compréhension des verbes HTTP. Quand il y a un nom d’action CRUD dans l’URI c’est qu’il y a un problème. De plus, dans la partie précédente, je parle de cachabilité. Si vous faites une recherche avec POST, le serveur n’utilisera pas le cache… mais il le mettra peut-être à jour (pour d’éventuels GET à venir qui ne viendront jamais).
Si vous utilisez un POST au lieu d’un PUT (ou autre méthode idempotent) et que par mégarde, vous exécutez cet appel plusieurs fois, il peut y avoir des effets de bords non désirés.

Création POST vs. PUT

Lors d’une création de ressource, il y a deux cas de figure : le serveur a la main sur la génération de l’id (ou du nom de la ressource) ou bien le cas contraire. Dans le premier cas, il faut privilégier le POST et dans l’autre le PUT. En d’autres termes, n’utilisez PUT pour la création uniquement si c’est au client connait l’URI de la ressource à créer.

Exemple avec les ressources subordonnées.

{
  "id": "42",
  "name": "calzone",
  "price": "$12",
  "ingredients": [
    { "id": "4", "name": "tomato sauce" }
    { "id": "8", "name": "ham" },
    { "id": "15", "name": "egg" },
    { "id": "16", "name": "basil leaves" }
    ...
  ]
}

Nous avons une ressource comme celle ci-dessus et que nous voulons ajouter un ingrédient. Il pourrait-être tentant de faire un PUT sur la ressource entière. Pourtant un ingrédient est… aussi une ressource ! Il faudra donc faire :

PUT http://api.example.com/pizzas/calzone/ingredients/<idIngredient>

En effet, ici POST pas possible car un ingrédient peut se retrouver dans plusieurs pizzas. Dans le cas contraire, un POST aurait été utilisé comme ceci :

POST http://api.example.com/pizzas/calzone/ingredients/

Le cas des modifications partielles

Si on regarde le panel de méthodes HTTP, on voit qu’il y a deux méthodes pour modifier une ressource : PUT et PATCH. Si vous vous souvenez bien GET requière une ressource entière et PATCH effectue un différentiel (avec les anciennes données). Donc dans les deux cas vous êtes obligés de faire un GET avant.

Là, je vous vois venir :

“On a qu’à faire un PUT partiel et puis c’est tout !”

Et bien non ! Comme le dit si bien le papa de REST, un PUT partiel n’est pas compatible avec une API RESTful. Par extension, il en va de même pour le POST partiel.

Bref, c’est toujours le même problème qui revient : Théorie Vs. Pragmatisme :

  • Soit vous collez à fond à la spec HTTP pour avoir une vrai API REST et utilisez PATCH ;
  • Soit vous oubliez avoir lu cet article et continuez d’utiliser un POST/PUT partiel. Mais votre API ne sera pas pleinement REST ;-)

Sortir du cadre CRUD

Même si ce n’est pas l’objet de cet article, je vais tout de même en parler un peu, car contrairement à ce que certains pensent, il est possible de sortir du cadre CRUD avec une API REST. Vous devrez alors utiliser la méthode POST, et ainsi profiter du caractère unsafe/non-idempotent de cette dernière. Utiliser POST vous protège d’effets de bord évoqués précédemment (ex : retirer deux fois l’argent sur le compte d’un client, double envoi de mail, …).

Exemple : POST api.mon-application.org/utilisateurs/42/envoyer-mail-confirmation

N’oubliez pas que c’est un échange entre le client et le serveur. Par conséquent, il doit y avoir une réponse. Réponse qui se matérialise dans le cas présent par des codes. Dans le cas présent, il faudra renvoyer un 204 No Content.

Sources

Immutability without JS library

Last month, I gave a talk about [*Immutability for functional JavaScript* in the DublinJS meetup (slides)](http://slides.maxpou.fr/immuta...… Continue reading

A la découverte d'Hubot

Published on July 14, 2017