REST et CQRS : relier les lectures de l’API aux décisions métier
GET sert souvent les vues de lecture, tandis que les écritures demandent une décision métier. REST et CQRS se combinent sans imposer le même découpage.
Article
Une réponse hypermédia contient des données, des liens et des actions. Le client suit ces indications, tout en partageant un vocabulaire clair avec le serveur.
Une commande attend son paiement. Au lieu de demander au client de construire lui-même l'adresse de paiement, l'API peut lui fournir l'action disponible, sa destination et les champs attendus. Après paiement, elle propose d'autres actions. Ce principe, appelé HATEOAS, permet au client de suivre les possibilités offertes par le serveur à chaque étape.
HATEOAS développe Hypermedia As The Engine Of Application State. Le nom décrit une façon de faire avancer un parcours à partir des liens et des actions présents dans les réponses. Le client sait lire le format ; le serveur indique les possibilités disponibles.
Une action décrite dans un message est parfois appelée une affordance. Elle peut préciser une méthode HTTP, une adresse et les paramètres à fournir. Dans une page web, un formulaire remplit ce rôle.
Le client découvre la destination d'une action au lieu de la fabriquer à partir d'un chemin enregistré dans son code. Il choisit encore l'action selon son objectif : suivre un lien ne lui apprend pas automatiquement s'il faut payer, annuler ou attendre.
Le bénéfice attendu est de réduire le nombre d'adresses et de transitions codées à l'avance dans le client. Le serveur peut faire évoluer certaines destinations tant qu'il conserve le contrat que le client comprend.
Voici une commande en attente de paiement. Elle porte ses données, un lien vers le client et deux actions, chacune avec sa méthode, sa cible et ses champs :
{
"reference": "C-42",
"statut": "en attente de paiement",
"_liens": { "client": { "href": "/clients/7" } },
"_actions": {
"payer": { "method": "POST", "href": "/commandes/C-42/paiement",
"champs": ["moyen", "montant"] },
"annuler": { "method": "DELETE", "href": "/commandes/C-42" }
}
}
Dans cet exemple de format, les noms _liens et _actions sont des conventions à documenter. Le client utilise l'adresse reçue pour soumettre le paiement. Une fois la commande payée, la réponse peut remplacer cette action par le suivi d'expédition.
Les actions affichées ne remplacent pas les contrôles du serveur. L'état ou les droits peuvent changer entre leur lecture et leur utilisation ; le serveur doit encore vérifier la demande et signaler un conflit si nécessaire.
Fielding place l'hypermédia parmi les quatre composantes de l'interface uniforme du style REST. Elles permettent de partager des règles de communication entre clients et serveurs. Cette généralité peut coûter en efficacité par rapport à une interface conçue pour un seul échange.
| Sous-contrainte | Ce qu'elle exige | Point de vigilance |
|---|---|---|
| Identifier les ressources | une URI stable par concept, pas par action | Vérifier que le contrat est documenté et stable |
| Manipuler par les représentations | le client échange des messages, jamais la chose elle-même | Vérifier que le contrat est documenté et stable |
| Messages auto-descriptifs | méthode, type de média et statut portent le sens | partiellement : application/json ne porte aucune sémantique, et l'auto-description s'arrête souvent à la méthode et au statut |
| Hypermédia comme moteur | la réponse porte les transitions possibles | Les transitions doivent être présentes et comprises par le client |
Identifier les ressources. Une ressource est un concept — une commande, un article, un compte — désigné de façon stable par une URI. Nommez des choses, pas des actions, et agissez dessus ensuite.
Manipuler par les représentations. Le client ne touche jamais la ressource : il échange des représentations, des messages qui la photographient à un instant et dans un format donnés. Une même ressource peut être servie en HTML à un navigateur et en JSON à un programme, au choix du client par l'en-tête Accept.
Des messages auto-descriptifs. Chaque message se comprend seul. La méthode dit l'intention : lire, créer, remplacer. Le type de média (l'en-tête Content-Type) dit comment lire le corps. Le statut dit ce qui s'est passé : 200, 404 ou 409 ont un sens partagé.
La méthode donne aussi des garanties, et elles disent moins que ce qui leur est prêté. GET est sûre : le client ne demande aucun changement d'état et n'en répond pas. Ce que le serveur fait par ailleurs, journaliser ou facturer un affichage, ne lui est pas imputable. C'est une propriété distincte de la cacheabilité, que HTTP accorde à GET, HEAD et POST, et que les caches n'exploitent en pratique que pour les deux premières.
PUT et DELETE sont idempotentes : l'effet voulu de N requêtes identiques est celui d'une seule. Un client peut donc relancer sa requête quand la connexion tombe avant qu'il ait pu lire la réponse — l'effet sera le même, même si la réponse diffère, le second DELETE trouvant la ressource déjà supprimée. Ce que le serveur fait à côté, journaliser ou garder un historique, n'entre pas dans la garantie.
Ces garanties sont celles de HTTP, pas du métier. Un cache sait qu'il peut stocker la réponse à un GET ; un client dont la connexion est tombée sait qu'il peut relancer son PUT. Un proxy, lui, n'a le droit de rejouer automatiquement que l'idempotent.
Des types de média pensés pour le JSON normalisent la façon d'y placer ces contrôles. Mais pas au même degré, et la nuance a son importance. HAL s'en tient délibérément aux liens : « omettre les formulaires était une décision de conception intentionnelle », dit son annexe. JSON:API non plus n'en définit pas. Il faut Siren pour obtenir une action complète, avec sa méthode et ses champs.
Le type de média définit comment lire les données et les contrôles. Le client doit également comprendre le vocabulaire métier utilisé : ce que veut dire une relation « payer », quels montants sont exprimés et quelles erreurs sont possibles. Un format partagé ne donne pas à lui seul toute cette connaissance.
Le couplage ne disparaît pas pour autant : il se déplace. Ce qui reste à partager n'est plus la liste des URL mais le vocabulaire : le sens des relations, portées par l'attribut rel d'un lien (que veut dire « payer », « suivant », « client » ?), et celui des champs de formulaire. HAL le reconnaît d'ailleurs en demandant qu'une relation personnalisée soit une URI qui, ouverte dans un navigateur, serve sa propre documentation.
Écrire un client qui suit vraiment les affordances demande aussi plus de soin. C'est pourquoi tant d'« API REST » hypermédia retombent, en pratique, dans les URL écrites en dur.
Le navigateur illustre la séparation entre un mécanisme générique et les choix de l'utilisateur. Il sait afficher le HTML et suivre les formulaires ; la personne comprend le contenu et décide quoi faire. Cette compréhension humaine ne peut pas être supposée automatiquement chez tout client logiciel.
Une application mobile branchée sur une « API REST » ordinaire a, elle, le gabarit /api/v1/commandes/{id} gravé dans son code. Que le serveur réorganise ses chemins, et l'application casse : il faut la republier, puis attendre que chaque utilisateur la mette à jour.
Les messages auto-descriptifs indiquent comment interpréter l'échange ; l'hypermédia fournit les transitions proposées. Fielding souligne leur rôle dans les API REST, dans la continuité du modèle décrit dans sa thèse. Le client reste lié aux formats et aux relations qu'il sait utiliser.
Je privilégierais cette approche lorsque plusieurs clients doivent suivre des parcours qui évoluent. Elle demande de concevoir les réponses et les clients ensemble. Ajouter quelques liens à une API dont les consommateurs continuent à construire toutes les adresses ne procure pas le même bénéfice.
Explorer
Choisis dans l’index d’après les étiquettes de cet article — les plus proches d’abord, hors de sa série. Rien n’est écrit à la main : un article publié demain avec les mêmes étiquettes y prendra place.
GET sert souvent les vues de lecture, tandis que les écritures demandent une décision métier. REST et CQRS se combinent sans imposer le même découpage.
Un agent peut écrire à partir d’une ancienne version. Une précondition vérifiée par le serveur permet de détecter le conflit et de reprendre sur un état relu.
Une réponse perdue ne signifie pas que l’opération a échoué. Méthodes HTTP, clés d’idempotence et suivi du résultat aident à réessayer sans créer de doublon.
Laisser un commentaire
Votre commentaire sera relu avant d'être publié.