CodeGym /Cours /Spring REST & MVC /Première découverte de Spring REST & MVC

Première découverte de Spring REST & MVC

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

1. Un endpoint qui marche, ce n'est pas encore un contrat

Commençons par un scénario très classique. Le développeur sait déjà lancer une application Spring Boot et écrit quelque chose dans ce genre :

@RestController // On dit à Spring : c'est un contrôleur REST, les réponses seront sérialisées (généralement en JSON)
@RequestMapping("/api/v1/tasks") // Préfixe de base pour toutes les routes de ce contrôleur
class TaskController {

    private final TaskService taskService; // Dépendance vers le service : la logique métier/accès aux données ne vit pas dans le contrôleur

    TaskController(TaskService taskService) { // Injection par constructeur : la dépendance doit obligatoirement être fournie
        this.taskService = taskService; // On conserve le service pour l'utiliser dans les gestionnaires de requêtes
    }

    @GetMapping("/{taskId}") // GET /api/v1/tasks/{taskId} — récupération d'une ressource par son identifiant dans le path
    Task getById(@PathVariable String taskId) { // taskId est extrait de l'URL (ex. /api/v1/tasks/t-42)
        return taskService.findById(taskId); // On retourne directement le modèle interne (c'est là le problème potentiel de contrat)
    }
}

Du point de vue de Java et Spring Boot, tout semble normal ⚙️. Il y a un contrôleur, une route, un appel au service et un résultat. L'application démarre, l'endpoint répond, Postman affiche du JSON. À ce moment-là, il est facile de penser : « Voilà, l'API est prête. » Et c'est là que commence une erreur subtile mais très importante.

Le problème n'est pas que le code est « mauvais ». Le problème, c'est qu'à partir de ce fragment on ne voit absolument pas quel est ton contrat externe avec le client. Que se passe-t-il si taskId n'existe pas ? Que retournes-tu si le Task interne contient des champs que le client ne doit pas voir ? Quel sera le format de l'erreur ? Le client recevra-t-il 404, 500 ou simplement la chaîne "task not found" ? Les noms des champs sont-ils stables ?

Autrement dit, un endpoint fonctionnel et une API mature, ce n'est pas la même chose. Un endpoint fonctionnel répond à la question : « L'application peut-elle techniquement renvoyer quelque chose via HTTP ? » Une API mature répond à la question : « Le client peut-il dépendre de ce comportement de façon sûre et prévisible ? » C'est un tout autre niveau de conversation.

Il est utile de fixer immédiatement une chose essentielle. Spring Boot aide énormément à assembler rapidement une application web 🔮. Mais les décisions produit et de contrat, c'est toi qui les prends. Il ne décide pas quels champs tu promets au client, quel statut utiliser selon les scénarios, comment tu formates les erreurs, ni où se situe la frontière entre modèle interne et JSON externe. Le framework aide à construire la route, mais ne choisit pas l'itinéraire.

2. Ce que le client reçoit vraiment de ton API

Pour sentir la différence non pas abstraitement mais concrètement, il suffit de regarder non pas la méthode du contrôleur, mais les deux réponses que le client verra réellement. Prenons un scénario très simple : le client demande une tâche par son identifiant.

Si la tâche est trouvée, il peut recevoir quelque chose comme ça :

# Statut de succès : la ressource est trouvée et retournée
HTTP/1.1 200 OK
# Le serveur indique explicitement au client que la suite sera du JSON
Content-Type: application/json

# Corps de la réponse : champs publics sur lesquels le client va compter
{
  "id": "t-42",
  "title": "Fix validation error format",
  "status": "IN_PROGRESS"
}

Si la tâche n'est pas trouvée, une API mature doit savoir répondre non pas « comme ça vient », mais de façon structurée. Par exemple, comme ceci :

# Statut d'erreur : aucune ressource avec cet identifiant
HTTP/1.1 404 Not Found
# Format typique pour les erreurs selon RFC 7807 (Problem Details)
Content-Type: application/problem+json

# Corps de l'erreur : description structurée pour que le client comprenne la cause et réagisse de façon prévisible
{
  "type": "https://example.dev/problems/task-not-found",
  "title": "Task not found",
  "status": 404,
  "detail": "Task with id t-42 was not found"
}

Regarde ce qui est important ici 🍄. Le client ne sait pas si tu as utilisé TaskService, Map, JPA, if, switch ou cent lignes de refactoring. Pour lui, seul compte ce qui suit : en cas de succès, 200 OK et un JSON compréhensible ; si la ressource est absente — 404 Not Found et une description d'erreur claire. C'est sur ça qu'il construit son comportement. Le front-end affiche un écran. L'application mobile affiche un message. Un autre service comprend s'il faut relancer la requête. Le test automatisé vérifie le statut et la forme du JSON.

Et voici la première idée clé. L'API, ce n'est pas ton code. L'API, c'est le comportement observable à la frontière externe du produit. Le client s'intègre non pas avec la classe TaskController, mais avec un ensemble de promesses : quel chemin existe, quel format de réponse est stable, quelles erreurs sont possibles, ce que signifient les statuts.

En deux mots, la formule est la suivante : le client s'intègre avec le comportement, pas avec l'implémentation. C'est une idée simple, mais ses conséquences sont très coûteuses. Dès que tu commences à voir l'API ainsi, il devient évident pourquoi on ne peut pas traiter les erreurs comme des déchets, pourquoi les champs JSON ne peuvent pas être modifiés sans réfléchir, pourquoi un 500 aléatoire détruit la confiance dans le contrat plutôt que de simplement « rendre le log rouge ».

3. Contrôle qualité : un succès et une erreur

Au départ, inutile de connaître tous les subtilités de REST, la sémantique HTTP et les internals de MVC. Un seul exemple de réponse réussie et un seul exemple d'erreur montrent très rapidement à quel point une API semble mature 📉📈. C'est presque comme une radio : deux clichés suffisent à voir la fracture.

Compare deux approches :

Question du client Simple endpoint fonctionnel Contrat d'API mature
Que vais-je recevoir en cas de succès ? « Un JSON quelconque » Un statut clair et une forme de réponse prévisible
Que vais-je recevoir en cas de problème ? Une chaîne aléatoire, une erreur du framework ou 500 Un statut significatif et un error payload unifié
Quels champs sont stables ici ? Pas clair, dépend du modèle interne C'est clair, parce que le contrat est séparé de l'implémentation
Qu'est-ce que le serveur promet exactement ? « La méthode semble fonctionner » Un comportement précis dans les scénarios de succès et d'échec

C'est là que commence le niveau professionnel. Non pas là où tu connais plus d'annotations, mais là où tu sais maintenir les deux faces du contrat : la réussite et l'échec. Tant que le développeur ne pense qu'au scénario heureux, il écrit un endpoint pour la démo. Quand il conçoit avec le même sérieux la réussite et l'échec, il commence à écrire une API avec laquelle on peut vivre dans un système réel.

C'est particulièrement visible dans le problème typique des débutants 💥. L'application fonctionne souvent tant que toutes les requêtes sont parfaites. Mais dès qu'arrive une mauvaise entrée, un ID inexistant, un enum incorrect, un corps absent, un champ inattendu ou un format invalide, le comportement devient aléatoire. Quelque part 400, quelque part 500, quelque part un long message d'exception, quelque part une page HTML d'erreur, quelque part simplement une réponse vide. Le client commence à deviner. Et deviner, c'est le pire type d'intégration.

Pour l'instant, on n'a pas besoin de discuter comment exactement ce contrat d'erreur est construit dans Spring MVC. Il suffit de voir qu'il doit exister. Ce cours va construire, étape par étape, exactement cette discipline : pour que chaque endpoint ne soit pas « une méthode qui retourne quelque chose », mais une partie d'un contrat externe bien assemblé.

4. Parfois Spring Boot aide, parfois il se tait

Il est très important de ne pas tomber dans le faux conflit « Spring Boot contre le design d'API ». Il n'y a pas de guerre ici 🙂. Spring Boot fait un travail énorme et utile : il lance rapidement l'application, donne un démarrage prévisible, aide à assembler les dépendances, monte la couche web et évite de se noyer dans les rituels. Sans lui, ce cours serait méthodiquement bien plus lourd.

Mais Boot a une frontière naturelle de responsabilité. Il t'aide à obtenir rapidement un mécanisme web fonctionnel, et non automatiquement un contrat externe mature. Ce sont deux tâches différentes.

Voici la différence en une comparaison courte :

Ce que Spring Boot simplifie énormément Ce qu'il ne conçoit pas à ta place
Le démarrage de l'application et de la couche web La sémantique des URI et des opérations
Le wiring, la configuration, le runtime de base La forme de la success response
La possibilité technique de retourner du JSON La forme de l'error response
La connexion des bibliothèques standard La frontière entre modèle interne et DTO
Le chemin rapide vers le premier endpoint Les règles d'évolution du contrat public

Ce n'est pas un défaut de Boot. C'est une frontière honnête de son rôle. Un framework ne doit pas résoudre par magie les questions produit et de contrat. Sinon, on n'obtiendrait pas une plateforme, mais un générateur de décisions architecturales aléatoires. C'est pourquoi un cours séparé après Spring Boot de base est tout à fait logique : d'abord tu apprends à monter l'application, ensuite à rendre sa frontière externe mature.

En pratique, cela signifie une chose simple. Si tu sais déjà écrire @RestController, tu n'es pas arrivé trop tard dans ce cours. Au contraire, tu es arrivé au bon moment. Tu as maintenant une base technique, et on peut enfin parler non pas de comment faire répondre un endpoint, mais de comment exactement il doit répondre correctement.

5. Cinq questions du client pour n'importe quel endpoint

Pour que la conversation d'aujourd'hui ne reste pas une belle théorie, on en tire une grille d'analyse utile. Elle est pratique pour regarder n'importe quel endpoint — d'apprentissage ou de production. Elle est commode à garder à côté d'une requête .http, d'une ébauche de méthode de contrôleur, ou simplement en tête lors d'une code review.

Question Ce que tu vérifies avec les yeux du client
1. Quelle est cette ressource ou ce scénario ? Via method + path, on comprend ce que le client demande ou modifie
2. À quoi ressemble la réponse en cas de succès ? Il y a un statut clair, une forme de JSON compréhensible et des champs prévisibles
3. À quoi ressemble l'erreur ? L'erreur a un statut significatif et un corps de réponse structuré
4. Que contrôle le client, que contrôle le serveur ? On voit quelles données arrivent de l'extérieur, et lesquelles sont assignées par l'application elle-même
5. Qu'est-ce qui doit rester stable demain ? On comprend quels noms de champs, statuts et règles ne peuvent pas être modifiés par inadvertance

Cette table a une propriété agréable. Elle fonctionne avant le code, pendant le code et après le code. Avant le code, elle aide à ne pas écrire des méthodes de contrôleur à l'aveugle. Pendant le code, elle évite de laisser fuiter le modèle interne ou d'oublier les scénarios négatifs. Après le code, elle aide à vérifier rapidement que l'endpoint ne s'est pas transformé en « ça marche chez moi, mais je ne sais pas ce que j'ai promis au client ».

6. Appliquons la grille au projet Task Tracker API 🔎

Maintenant, appliquons cette grille à un futur endpoint de notre projet : GET /api/v1/tasks/{taskId}. On n'a pas besoin pour l'instant d'une carte complète de l'API ni de toute l'architecture du projet. Un seul angle suffit.

Question de la grille Réponse courte pour GET /api/v1/tasks/{taskId}
Quel est ce scénario ? Le client veut récupérer une tâche par son identifiant
À quoi ressemble le succès ? 200 OK et JSON avec les champs publics de la tâche
À quoi ressemble l'erreur ? 404 Not Found et une error response structurée
Que contrôle le client ? Seulement taskId dans le path ; le serveur décide lui-même comment et où récupérer les données
Qu'est-ce qui doit être stable ? Le chemin de la ressource lui-même, la forme du JSON public et le modèle d'erreurs

Ce qui est le plus intéressant ici, c'est ce dont on n'a pas parlé 🙃. On n'a pas dit où les données sont stockées. On n'a pas discuté de JPA. On n'a pas touché à la sécurité. On n'est pas entré dans les détails des internals de Spring MVC. Et pourtant on vient d'avoir une conversation tout à fait professionnelle sur l'API. C'est une sensation très importante pour débuter ce cours.

7. Erreurs typiques au premier regard 🚧

Erreur n°1 : regarder un endpoint uniquement à travers la méthode Java.
C'est le piège de départ le plus fréquent. Le développeur voit @GetMapping, la signature de la méthode, et considère que le tableau est complet. Mais le client n'a pas accès à ta signature. Pour lui n'existent que le status code, les headers et le body. Si on ne bascule pas vers ce regard externe, toute l'API sera conçue « pour l'auteur du code », pas pour son consommateur.

Erreur n°2 : considérer un JSON de succès comme un signe suffisant de qualité.
Il est très tentant de penser : « Eh bien, il y a une réponse, donc tout va bien. » Non. Une API ne devient mature que lorsque succès et erreur sont tous deux prévisibles. Si seul ton scénario de succès est soigné et que le scénario négatif est aléatoire, le contrat reste faible.

Erreur n°3 : penser que Spring Boot va « deviner » le contrat à ta place.
Le framework ne choisit pas à ta place ce que tu promets au client. Il aide à construire rapidement un mécanisme web, mais ne remplace pas la décision d'ingénierie sur où passe la frontière du contrat, quels champs sont publics et à quoi ressemble le modèle d'erreurs.

Erreur n°4 : percevoir l'error response comme un détail secondaire.
Pour le client, une erreur n'est pas un scénario poubelle, c'est une partie de la vie normale de l'API. Si elle n'est pas conçue, le code client commence à fonctionner sur des suppositions. Et les suppositions coûtent toujours plus cher qu'un contrat bien pensé une bonne fois pour toutes.

1
Mission
Spring REST & MVC, niveau 1, leçon 0
Bloqué
Carte des limites de la couche API
Carte des limites de la couche API
1
Mission
Spring REST & MVC, niveau 1, leçon 0
Bloqué
Surface externe de l'API et implémentation interne
Surface externe de l'API et implémentation interne
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION