CodeGym /Cours /Spring REST & MVC /REST : le contrat externe de l'API

REST : le contrat externe de l'API

Spring REST & MVC
Niveau 3, Leçon 0
Disponible

1. Le problème : l'API commandée devient du chaos

Presque chaque développeur backend débutant finit par concevoir une API « comme une télécommande de télé » : un bouton pour chaque action. Au départ, ça semble même plaisant. Il y a une action « créer une tâche » — donc il y aura /createTask. Il y a une action « fermer une tâche » — il y aura /completeTask. Ça paraît logique : on transpose littéralement ses pensées en URL. Mais dès que le système commence à vivre, les boutons passent à 50, puis à 150, et à un moment on ne se souvient plus soi-même où est « terminer » et où est « archiver », et en quoi l'un diffère de l'autre.

Imagine qu'on doive ajouter encore un peu de fonctionnalité à Task Tracker. L'utilisateur veut assigner un exécutant, retirer l'exécutant, changer la priorité, définir une deadline, supprimer la deadline, ajouter un commentaire, supprimer un commentaire, joindre un fichier, supprimer un fichier, rouvrir la tâche… En mode commandé, on obtient très vite toute une collection URI d'incantations où le client n'a pas besoin de comprendre le domaine, mais de mémoriser ton dictionnaire de verbes.

C'est là que se révèle le problème principal : le client n'a pas besoin d'une liste de tes « commandes » internes. Il a besoin d'un ensemble clair d'objets et d'opérations qui se ressemblent de façon prévisible et se comportent de façon prévisible.

HTTP a déjà fourni la grammaire de base : method, path, headers, body, status. Mais la grammaire seule ne suffit pas si la surface de l'API est organisée comme un catalogue de commandes. L'essentiel maintenant c'est de comprendre autour de quoi distribuer le domaine, pour que GET, POST et les autres méthodes fonctionnent sur une carte claire, et non sur un ensemble de boutons.

REST dans ce cours : pas une religion, mais un moyen de s'entendre avec le client

Quand les gens entendent « REST », ça déclenche soit une guerre de religion, soit une légère panique. « Et si on ne fait pas un vrai REST ? » « HATEOAS est-il nécessaire ? » « Peut-on mettre des verbes dans l'URL ? » Soyons honnêtes : en développement commercial, REST — ce n'est pas un examen de philosophie. C'est un moyen pratique de rendre l'API lisible, prévisible et discutable avec l'équipe et les clients.

Dans le cadre de ce cours, REST — c'est avant tout un contrat externe. C'est-à-dire ce que voit le consommateur de ton API : une application mobile, le frontend, une intégration partenaire, Postman, des tests automatisés, parfois même un autre backend. REST aide à arrêter de penser : « quel méthode de contrôleur je vais écrire maintenant ? » — et à commencer à penser : « quel objet de notre domaine existe pour le client ? »

Levons encore une confusion. REST ne dicte pas l'architecture interne. En interne tu peux avoir 30 services, ou un seul énorme (c'est à éviter, mais bon), ou des couches controller -> service -> repository. Le client s'en fiche. REST — c'est une question de comment la surface du système se présente de l'extérieur, pas de comment fonctionne la cuisine en interne.

2. Contrat externe vs noms de méthodes

Pour changer de mode de pensée, une analogie simple est utile : l'API — c'est le menu d'un restaurant. Le client voit le menu. Il ne sait pas comment le chef a appelé la variable sauce2 ni quelle méthode il appelle en cuisine. Si demain tu renommes cookCarbonara() en makePastaLikeInRome() — le client du restaurant s'en moque. Mais si tu retires soudainement « Carbonara » du menu ou si tu la renommes en « Pâtes_42 », ça fera mal au client, et il ira chez la concurrence (ou, si c'est une intégration d'entreprise, t'enverra un email très poli au support, dont on entend quand même le cri).

Pareil avec l'API. En interne, tu peux vraiment avoir des commandes. Tu peux avoir un service avec des méthodes completeTask(taskId) ou addComment(taskId, text). C'est un modèle de comportement interne normal. Le problème commence quand tu décides : « Puisque ma méthode s'appelle completeTask, l'URL sera aussi /completeTask ». Autrement dit, le contrat externe devient le miroir des noms de méthodes internes.

Petite illustration en Java. Les commandes internes de l'application peuvent ressembler à ça :

class TaskCommands {
    // Modèle interne : "commandes" de l'application (comment tu nommes les méthodes dans ton code)
    void createTask() {}

    // taskId — identifiant interne de la tâche ; pour le client, ce qui compte c'est que c'est une "tâche", pas une "commande"
    void completeTask(String taskId) {}
}

Le contrat externe — les données qu'on expose et la façon d'y accéder — est plus facile à penser ainsi : « voici une tâche, voici sa représentation pour le client » :

// Modèle externe : DTO/représentation de la ressource "tâche" pour le client
record TaskView(String id, String title, String status) {}

Le sens ce n'est pas que record transforme magiquement l'API en REST. Le sens, c'est que le monde extérieur doit parler le langage des tâches, pas celui des commandes internes.

3. Transition vers le domaine du client

S'il ne fallait retenir qu'une phrase de cette lektsiya, que ce soit celle-ci : en pensée REST, la question principale est : « Avec quoi exactement le client travaille-t-il ? » Pas « qu'est-ce qu'on fait », pas « quelle méthode on appelle », mais « quel objet du domaine est central ici ».

Pour Task Tracker API, la réponse est évidente : l'objet central — c'est la tâche. C'est autour d'elle que se construit le sens du système. Les commentaires existent au sein de la tâche. Les pièces jointes aussi. Les tags dans notre projet pédagogique — plutôt des valeurs pratiques pour chercher et classer les tâches, pas une grande entité indépendante avec sa propre vie. Le statut — c'est une partie de l'état de la tâche, pas un « objet-commande » autonome.

Un effet secondaire agréable de cette question : elle t'oblige à penser non pas « où mettre l'endpoint », mais « quelle partie du domaine est en cours de discussion ». Du coup, l'API commence à ressembler à une carte du territoire, pas à une liste d'incantations aléatoires.

Voici un exemple court qui montre la différence de pensée, sans entrer dans Spring et les annotations :

import java.util.List;

// Style "commandé" : endpoint == verbe/opération qu'il faut mémoriser
List<String> rpcLike = List.of(
    "/createTask",
    "/completeTask",
    "/getAllTasks"
);

// Style "ressource" : endpoint == entité/ressource avec laquelle travaille le client
List<String> resourceLike = List.of(
    "/tasks",
    "/tasks/{taskId}"
);

Le premier ensemble ressemble à des boutons de télécommande. Le second — à « on a des tâches ; parfois on travaille avec la liste, parfois avec une tâche précise ». Et ce « parfois avec la liste, parfois avec une précise » — c'est déjà le premier pas vers un contrat solide.

4. Le style RPC-like : pratique, mais qui s'use mal

Une API RPC-like — c'est quand l'interface externe du système ressemble à un « appel de méthodes à distance ». L'URL devient un nom de commande, et la requête HTTP signifie en substance « appelle-moi cette fonction sur le serveur ». Du point de vue du débutant, ça semble vraiment pratique : tu écris une méthode et tu nommes l'endpoint presque avec les mêmes mots. Un minimum de réflexion, un maximum de vitesse. Presque du copier-coller, mais dans la tête.

Mais cette approche a un inconvénient fâcheux : elle ne s'adapte pas bien à la croissance. Chaque nouveau scénario ajoute un nouveau route verbal. De plus, elle mélange deux choses que HTTP sépare spécifiquement : l'adresse (path) et l'action (method). Il devient facile d'arriver à quelque chose comme GET /deleteTask — c'est-à-dire lancer une « suppression » par une « lecture ». HTTP est triste de ça, le caching se perd, idempotency commence à sonner comme un gros mot, et le client doit mémoriser les exceptions.

Comparons au niveau du sens (pas de l'implémentation). Voici la forme « commandée  :

import java.util.Map;

// Clé — "ce qu'on veut faire", valeur — "comment appeler exactement la commande via HTTP"
Map<String, String> commands = Map.of(
    "complete", "POST /completeTask",
    "delete", "GET /deleteTask/{taskId}"
);

Et voici comment commence à ressembler la forme « ressource », où l'objet central — c'est la tâche :

import java.util.Map;

// Clé — "quelle ressource", valeur — "comment la méthode HTTP + le chemin l'utilisent"
Map<String, String> resources = Map.of(
    "task collection", "POST /tasks",
    "one task", "DELETE /tasks/{taskId}"
);

Remarque : peu importe pour l'instant quelle méthode HTTP sera correcte dans chaque scénario — on le verra plus tard dans le cours. Ce qui compte, c'est autre chose : dans la deuxième variante, la même entité du domaine se lit toujours pareil. Et donc l'API est bien plus facile à discuter. On ne discute pas de « quel est notre endpoint pour terminer ? », on discute de « que signifie terminer une tâche en tant que changement de son état ? ».

5. Les mêmes opérations doivent se ressembler

REST est précieux non pas parce qu'il rend les URL « belles », mais parce qu'il rend l'API reconnaissable. La reconnaissance — c'est quand le client, voyant une partie de l'API pour la première fois, peut avec une bonne probabilité deviner le reste. Non pas parce qu'il est télépathe, mais parce que tu conçois le contrat de façon systémique : ce qui se ressemble — se ressemble aussi dans l'API.

La prévisibilité sauve particulièrement quand l'API grandit. Tu as plus de ressources, plus de scénarios, plus de développeurs dans l'équipe. Et là, la vérité terrifiante : personne ne gardera en tête une centaine d'endpoints « uniques » de style commandé. En revanche, un ensemble de ressources et des règles claires de « comment on travaille généralement avec elles » — ça, c'est mémorisable.

Imagine ça comme un langage. Si un langage n'a pas de grammaire, il reste à bourrer. « Cette commande s'appelle comme ça, celle-là comme ça, et celle-ci c'est une exception ». Si la grammaire existe, tu peux construire de nouvelles phrases sans les apprendre une par une. REST — c'est une telle « grammaire » pour les API fondées sur HTTP : on adresse le domaine via URI, et le type d'opération s'exprime par les méthodes et des conventions claires.

Pour le ressentir de façon technique, une petite mini-vérification suffit : si je vois /tasks/{taskId}, puis-je au moins approximativement m'attendre à ce que les opérations sur la tâche soient liées à ce même chemin, plutôt que dispersées dans /doSomethingWithTask ? En style ressource, la réponse est généralement « oui ». En style commandé — « je ne sais pas, il faut chercher dans la doc… si elle existe ».

6. Mini-cas sur Task Tracker API

On ne conçoit pas encore les URI finales et on n'arrête pas le versioning /api/v1 (ce sera plus tard). Mais dès maintenant on peut voir comment le même domaine devient soit lisible, soit chaotique — selon le style choisi.

Imagine qu'on veuille couvrir les scénarios de base de Task Tracker (créer une tâche, voir la liste, voir une seule, ajouter un commentaire, uploader une pièce jointe, obtenir la liste des tags). Le style commandé dérive très facilement vers ça :

import java.util.List;

// Mauvaise scalabilité : nouvelles features = nouveaux endpoints "verbaux"
List<String> rpcStyle = List.of(
    "/createTask",
    "/getAllTasks",
    "/getTaskById",
    "/addCommentToTask",
    "/uploadFileToTask",
    "/getAllTags"
);

Au départ ça semble acceptable. Mais essaie de continuer cette liste encore une semaine de développement. Très vite on obtient une explosion de routes verbales : un endpoint dédié pour chaque détail, souvent avec des verbes de formes différentes (create, add, upload, getAll, find, fetch…), et plus personne ne garantit la cohérence.

Le style ressource, au contraire, oblige à regrouper tout autour de l'objet principal :

import java.util.List;

// "Carte" du domaine : il y a des tâches et des sous-ressources de tâche, plus des référentiels séparés
List<String> resourceStyle = List.of(
    "/tasks",
    "/tasks/{taskId}",
    "/tasks/{taskId}/comments",
    "/tasks/{taskId}/attachments",
    "/tags"
);

Et là une « carte » apparaît dans la tête : il y a des tâches, les tâches ont des commentaires et des pièces jointes, et les tags — c'est une petite liste de valeurs séparée. Même si tu ne sais pas encore quelles méthodes et quels statuses auront chaque endpoint, la surface du système est déjà lisible.

Remarque encore un point important. Dans la deuxième variante, tu passes presque automatiquement à la discussion des frontières. Par exemple, les commentaires vivent « sous la tâche » — donc logiquement, on les adresse comme une partie de la tâche. Et les tags dans notre projet pédagogique ne deviennent pas une grande ressource CRUD séparée — donc ils vivent comme un endpoint de lookup séparé. Ces décisions ne sont pas « justes/fausses » dans l'absolu. Elles visent à garder le projet pédagogique compact, lisible et évolutif sans complexité inutile.

7. Les erreurs typiques dans la conception d'API REST

Erreur n°1 : confondre REST avec des « beaux paths ».
Il est très facile de réduire REST à la cosmétique : « il suffit de mettre les URL au pluriel — et c'est du REST ». Le problème, c'est que le pluriel seul ne transforme pas un ensemble d'actions aléatoires en contrat. Pour éviter ce piège, il est utile de revenir à chaque fois à la question « quel objet du domaine on montre au client ? ». Si la réponse n'existe pas, ça veut dire qu'on est en train d'inventer non pas une ressource, mais une commande.

Erreur n°2 : penser que REST dicte l'architecture interne de l'application.
Parfois on commence à débattre : « si REST, alors les services doivent être tels », « donc on ne peut pas avoir de méthode completeTask() ». Si, on peut. En interne, tu peux avoir n'importe quelles commandes, méthodes et même des verbes dans les noms — c'est normal. REST discipline le contrat externe. Tu modifieras les internes des dizaines de fois, et c'est pour ça que la couche externe doit être stable et indépendante de tes renommages internes.

Erreur n°3 : copier dans l'API externe les noms des méthodes internes.
C'est le « glissement par inertie » le plus fréquent : il y a une méthode uploadFileToTask() — donc il y aura /uploadFileToTask. Au final, le contrat externe commence à refléter l'implémentation actuelle, pas le domaine. Le remède : imagine que le client ne connaît pas ton code et ne le verra jamais. Le client ne comprend que le domaine (« tâches, commentaires, pièces jointes »). Alors, l'URL avec des commandes arrête tout de suite de paraître naturelle.

Erreur n°4 : percevoir REST comme un dogme ou un examen de « pureté ».
Dans la vraie vie, REST — c'est un outil de clarté, pas une compétition de conformité à la RFC. Essayer de correspondre parfaitement à toutes les restrictions théoriques peut faire s'enliser dans les détails et bloquer le développement. De l'autre côté, dire « REST c'est faire comme on veut » mène au chaos. L'approche pragmatique de ce cours : on garde le modèle ressource lisible et cohérent, sans transformer le projet en discussion académique.

Erreur n°5 : perdre l'objet central du système.
Dans Task Tracker API, l'objet central — c'est la tâche. Si tu conçois l'API de façon à ce qu'on ne voie pas l'« objet principal » (tout est dilué dans des commandes, et l'entité task n'apparaît que comme paramètre quelque part en profondeur), tu perds la carte du domaine. Alors chaque nouvelle feature s'ajoute non pas comme une extension du système, mais comme un nouveau bouton chaotique. Bonne auto-vérification : est-ce qu'on peut expliquer en 10 secondes, à la liste des endpoints, « de quoi parle ce service » ? Si non, tu es probablement retombé dans le style commandé.

1
Mission
Spring REST & MVC, niveau 3, leçon 0
Bloqué
Liste des chemins de commande et de ressources
Liste des chemins de commande et de ressources
1
Mission
Spring REST & MVC, niveau 3, leçon 0
Bloqué
Commandes internes et contrat public
Commandes internes et contrat public
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION