Depuis que j’ai commencé à travailler pour Apache APISIX, j’ai essayé d’approfondir ma compréhension de REST par divers moyens. Avez-vous lu ma critique du livre « API Design Patterns » ?
Dans la littérature actuelle, REST est généralement promu comme la meilleure chose depuis le pain tranché. Pourtant, cela comporte de nombreux défis. En 2010 (!), Martin Fowler a écrit un article sur la gloire de REST. Il énumère trois étapes pour qu’une API devienne vraiment DU REPOS:
À chacune de ces étapes, des problèmes se cachent. Ce billet de blog se concentre sur la liste de certains d’entre eux et fournit des conseils sur les moyens de les résoudre.
Ressources
REST a émergé des inconvénients de SOAP. SOAP fournit un point de terminaison unique et exécute du code en fonction de la charge utile. L’idée de REST est de fournir plusieurs points de terminaison, chacun exécutant un code différent.
Je vais être honnête : il y a peu de problèmes à ce stade. La plus importante consiste à deviner une identité à partir d’une identité existante. Si les identifiants de ressource sont séquentiels ou même uniquement numériques, il est facile de deviner les points de terminaison d’autres ressources, par exemple, à partir de /customers/1
pour /customers/2
. La solution consiste à utiliser des identifiants non numériques non séquentiels, c’est-à-dire des identifiants universellement uniques.
Remontons le modèle de maturité REST.
Verbes HTTP
Les verbes HTTP sont la prochaine étape vers la gloire de REST. Ils proviennent d’interactions avec HTML « à l’époque ». Les interactions sont venues de CRUD opérations.
C’est assez simple :
Opération | Verbe |
---|---|
Créer | POST
|
Lire | GET
|
Mettre à jour | PUT
|
PATCH
|
|
Supprimer | DELETE |
Le principal problème avec les API est que vous devez aller au-delà de CRUD. Imaginons un exemple concret avec un virement bancaire : il prélève de l’argent sur un compte et le transfère sur un autre. Comment va-t-on le modéliser ?
Nous pourrions utiliser le compte d’origine comme ressource, par exemple., /accounts/a1b2c3d4e5f6
. Le compte cible, le montant, etc., peuvent être passés en tant que paramètres de requête ou dans le corps. Mais quel verbe HTTP utiliserons-nous ?
Elle modifie certes la ressource identifiée, mais elle a des « effets secondaires ». Cela modifie également une autre ressource : le compte cible. Voici quelques options sur la façon de gérer le verbe HTTP :
- Utiliser
POST
car cela change la ressource source. C’est trompeur parce qu’il ne parle pas des effets secondaires. - Utilisez un verbe HTTP dédié, par exemple,
TRANSFER
. Ce n’est pas explicite et est contraire aux principes REST. -
Utiliser
POST
avec un soi-disant méthode personnalisée. Les méthodes personnalisées sont une proposition d’amélioration de l’API Google :Les méthodes personnalisées ne doivent être utilisées que pour les fonctionnalités qui ne peuvent pas être facilement exprimées via des méthodes standard ; préférez les méthodes standard si possible, en raison de leur sémantique cohérente.
L’URI HTTP devoir utiliser un
:
caractère suivi du verbe personnalisé.Voici notre URI de transfert de compte bancaire :
/accounts/a1b2c3d4e5f6:transfer
.Quelle est la meilleure alternative ? « Ça dépend. »
Hypermédia
Fowler décrit Hypermedia Controls comme l’étape ultime pour atteindre la gloire de REST. Il est aujourd’hui connu sous le nom de HATÉOAS:
Avec HATEOAS, un client interagit avec une application réseau dont les serveurs d’application fournissent des informations de manière dynamique par hypermédia. Un client REST n’a besoin que de peu ou pas de connaissances préalables sur la façon d’interagir avec une application ou un serveur au-delà d’une compréhension générique de l’hypermédia.
— HATEOAS sur Wikipédia, l’encyclopédie libre
HATEOAS est un concept. Voici une implémentation possible tirée de Wikipedia. Quand on demande un compte bancaire, disons /accounts/a1b2c3d4e5f6
la réponse contient des liens vers des actions possibles avec ce compte bancaire spécifique :
{
"account": {
"account_number": "a1b2c3d4e5f6",
"balance": {
"currency": "USD",
"value": 100.00
},
"links": {
"_self": "/accounts/a1b2c3d4e5f6",
"deposit": "/accounts/a1b2c3d4e5f6:deposit",
"withdrawal": "/accounts/a1b2c3d4e5f6:withdrawal",
"transfer": "/accounts/a1b2c3d4e5f6:transfer",
"close-request": "/accounts/a1b2c3d4e5f6:close-request"
}
}
}
Si le solde est négatif, seul le lien de dépôt sera disponible :
{
"account": {
"account_number": "a1b2c3d4e5f6",
"balance": {
"currency": "USD",
"value": 100.00
},
"links": {
"_self": "/accounts/a1b2c3d4e5f6",
"deposit": "/accounts/a1b2c3d4e5f6:deposit",
}
}
}
Un problème commun avec REST est le manque de normes. HATEOAS n’est pas différent. La première tentative d’apporter un certain degré de normalisation a été le langage d’application hypertexte JSON, alias HAL. A noter qu’il a été créé en 2012 : la dernière version date de 2016, et il est encore en projet.
Voici un schéma rapide qui résume la proposition :
Nous pouvons retravailler ce qui précède avec HAL comme suit :
GET /accounts/a1b2c3d4e5f6 HTTP/1.1
Accept: application/hal+json
HTTP/1.1 200 OK
Content-Type: application/hal+json
{
"account": {
"account_number": "a1b2c3d4e5f6",
"balance": {
"currency": "USD",
"value": 100.00
},
"_links": { <1>
"self": { <2>
"href" : "/accounts/a1b2c3d4e5f6",
"methods": ["GET"] <3>
},
"deposit": {
"href" : "/accounts/a1b2c3d4e5f6:deposit", <4>
"methods": ["POST"] <3>
}
}
}
}
- Liens disponibles
- Lien vers soi
- Dites quel verbe HTTP peut être utilisé
- Lien vers le dépôt
Une autre tentative de normalisation est la RFC 8288, alias Web Linking. Il décrit le format et contient un registre de relations de liens, par exemple, alternate
et copyright
. La différence la plus significative avec HAL est que la RFC 8288 communique des liens via des en-têtes de réponse HTTP.
HTTP/2 200 OK
Link: </accounts/a1b2c3d4e5f6> rel="self";
method="GET", <1>
</accounts/a1b2c3d4e5f6:deposit> rel="https://my.bank/deposit";
title="Deposit";
method="POST" <2>
{
"account": {
"account_number": "a1b2c3d4e5f6",
"balance": {
"currency": "USD",
"value": 100.00
}
}
}
- Lien vers la ressource actuelle avec le non-standard
self
type de relation - Lien vers le dépôt avec l’extension
https://my.bank/deposit
type de relation et un arbitrairetitle
attribut cible
D’autres spécifications de types de supports alternatifs sont disponibles.
Nom | La description | Fourni par |
---|---|---|
Base uniforme pour l’échange de déclarations |
Le format de document UBER est un type hypermédia minimal en lecture/écriture conçu pour prendre en charge les transferts d’état simples et les transitions ad hoc basées sur l’hypermédia. Cette spécification décrit à la fois les variantes XML et JSON du format et fournit des directives pour la prise en charge des messages codés UBER sur le protocole HTTP. |
Personnes |
Collection+JSON |
Collection+JSON est un type hypermédia en lecture/écriture basé sur JSON conçu pour prendre en charge la gestion et l’interrogation de collections simples. |
Individuel |
JSON : API |
JSON:API est une spécification indiquant comment un client doit demander que les ressources soient récupérées ou modifiées, et comment un serveur doit répondre à ces demandes. |
Personnes |
Sirène |
Siren est une spécification hypermédia pour représenter des entités. Comme HTML est utilisé pour représenter visuellement des documents sur un site Web, Siren est une spécification pour présenter des entités via une API Web. Siren propose des structures pour communiquer des informations sur les entités, des actions pour exécuter des transitions d’état et des liens pour la navigation du client. |
Individuel |
Sémantique de profil au niveau de l’application |
Un document ALPS peut être utilisé comme profil pour expliquer la sémantique d’application d’un document avec un type de média indépendant de l’application (tel que HTML, HAL, Collection+JSON, Siren, etc.). Cela augmente la possibilité de réutilisation des documents de profil sur tous les types de supports. |
IETF |
Bonus : état de la réponse HTTP
Ce que le message de Fowler ne mentionne pas, c’est le statut de la réponse HTTP. La plupart des lecteurs connaissent les plages d’état :
- Réponses informatives : 100 – 199
- Réponses positives : 200 – 299
- Messages de redirection : 300 – 399
- Réponses d’erreur client : 400 – 499
- Réponses d’erreur du serveur : 500 – 599
De même, la plupart ont également un statut HTTP régulièrement trouvé :
Le problème, c’est qu’au-delà de ces cas simples, c’est la pagaille. Par exemple, regardez cette question StackOverflow : « Quel code d’état HTTP signifie Pas encore prêt, réessayez plus tard ? » Voici un résumé des réponses proposées, de la plus votée à la plus faible :
- 503 Service Indisponible
- 202 Accepté (réponse acceptée)
- 423 verrouillé
- 404 Non trouvé
- 302 Trouvé
- 409 Conflit
- 501 Non mis en œuvre (voté contre)
Ce n’est pas une réponse simple : il y a eu beaucoup de débats autour des alternatives. Pour mémoire, je pense que la réponse acceptée est la bonne.
C’est déjà beaucoup du côté du concepteur, mais le côté client contient également beaucoup d’incertitude, car certains grands fournisseurs d’API utilisent leurs propres codes d’état HTTP.
Conclusion
La « gloire du REST » ne veut pas dire grand-chose. Il n’y a pas de sémantique univoque sur laquelle s’appuyer, malgré toute affirmation contraire. En l’état, cela dépend principalement de l’implémentation et de l’interprétation : les deux nécessitent de documenter le comportement personnalisé au lieu de s’appuyer sur une spécification partagée.
Le plus gros défaut de SOAP était sa complexité et sa concentration sur les grandes entreprises, mais il fournissait au moins un ensemble partagé de spécifications standard. L’industrie l’a remplacé par REST : pas un cahier des charges, mais un site architectural. REST est plus simple et donc plus accessible, mais il nécessite beaucoup d’efforts personnalisés, qui changent d’un projet à l’autre.
Il existe des initiatives visant à assurer une certaine normalisation, mais elles sont peu nombreuses et certaines sont en contradiction avec d’autres. De plus, ils ont une faible traction, donc les gens ne les connaissent pas, ce qui crée un vicieux…