CodeGym /Cours /Spring REST & MVC /Données d'amorçage pour l'état initial de l'API

Données d'amorçage pour l'état initial de l'API

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

1. Référentiel vide : ça marche, mais ça n'enseigne pas

Lorsque vous créez un API REST d'apprentissage, il est très facile de tomber dans le piège : « le contrôleur a répondu, le statut 200 est présent, donc tout va bien ». Et puis l'étudiant (ou vous‑même une semaine plus tard) ouvre le projet, envoie une requête GET /tasks et voit… rien. Formellement, c'est honnête : il n'y a pas de données. Mais cela n'aide pas le cerveau — rien à vérifier, rien à comparer, rien à expliquer. C'est comme s'entraîner à nager avec des images : l'eau semble exister quelque part, mais vous n'êtes pas mouillé.

Les données d'amorçage (seed data) sont un petit ensemble de données préparées à l'avance qui apparaît au démarrage de l'application. Elles ne sont pas destinées à créer une « belle démo », mais à rendre le comportement de l'API reproductible. Il est important que chaque démarrage de l'application commence dans le même état : les mêmes tâches, les mêmes identifiants, le même ordre. Vous pouvez alors écrire tranquillement des requêtes .http, montrer des exemples en cours et ne pas jouer à la loterie « quels identifiants ai‑je obtenus aujourd'hui ».

Si vous pensez : « je peux d'abord créer 5 tâches manuellement via POST » — vous le pouvez. Mais cela transforme chaque vérification de l'API en une quête distincte avec des étapes manuelles. Or nous construisons un projet pour qu'il soit pratique pour l'apprentissage : lancez‑le et les données sont déjà là, vous pouvez immédiatement tester list/detail, voir la différence entre diverses tâches, discuter pourquoi la structure du projet est importante.

Données d'amorçage dans notre projet

Il est important de se mettre d'accord sur les termes, sinon tout se mélangera dans un grand « ensemble de données ». Les données d'amorçage dans Task Tracker API sont un ensemble initial d'enregistrements qui vit à l'intérieur de l'application et se charge au démarrage. Ce n'est pas une base de données, ni des migrations, ni des données « de production », ni « comme en vrai production » (en production, vous ne voulez pas que quelqu'un ajoute à chaque déploiement une tâche « Vérifier le bouton »). Nous faisons un service pédagogique, et les données d'amorçage sont un outil méthodologique.

Les données d'amorçage ne sont pas non plus obligées d'être volumineuses. Au contraire : plus vous en ajoutez, plus il est difficile de les lire et de les expliquer. De bonnes données d'amorçage sont quelques enregistrements qui se différencient les uns des autres par leur sens. Si vous avez 20 tâches identiques « Tâche test 1…20 », ce ne sont pas des données, c'est du bruit. En revanche, 6 à 10 tâches avec des statuts, des priorités et des délais différents sont déjà utiles : vous lancez le projet et voyez immédiatement « différentes situations de vie ».

Autre point : les données d'amorçage ne doivent pas casser l'architecture. Une erreur très fréquente : « je vais simplement créer une liste de tâches dans le contrôleur pour avoir quelque chose à renvoyer ». Ainsi, vous transférez la responsabilité du stockage et de l'initialisation des données dans la couche web. Le service et le référentiel deviennent décoratifs et le contrôleur gonfle. Les données d'amorçage doivent être chargées là où réside le stockage, ou dans un point de démarrage technique distinct de l'application, mais certainement pas dans le contrôleur.

2. Déterminisme et identifiants fixes

Les données d'amorçage ne valent pas par leur simple existence, mais par le fait qu'elles sont identiques à chaque fois. Ce mot semble ennuyeux, mais il vous rend heureux : déterminisme. Lorsque les données d'amorçage sont déterministes, vous n'avez qu'à écrire une fois une requête GET /api/v1/tasks/{taskId} et elle fonctionnera aujourd'hui, demain et dans une semaine. Avec des données d'amorçage « aléatoires », vous cherchez un nouvel identifiant à chaque fois, réécrivez les requêtes et perdez du temps.

Le code « presque correct » le plus typique ressemble à ceci : vous créez des tâches et leur donnez un identifiant via UUID.randomUUID(). Cela a l'air joli, « de vrais UUID », ça rend fier. Mais au prochain démarrage de l'application, les identifiants changent et tous les exemples, les requêtes .http et les explications cessent de correspondre à la réalité.

Fixons cela dans un petit tableau — il aide à comprendre pourquoi nous insistons sur des identifiants stables :

Approche des identifiants dans les données d'amorçage Ce qu'on obtient à chaque démarrage Plus Moins
UUID.randomUUID() nouveaux identifiants « ressemble à la prod » casse la répétabilité, les exemples et les vérifications
chaînes UUID fixes les mêmes identifiants reproductibilité et testabilité il faut définir les valeurs une fois manuellement
task-1, task-2 les mêmes identifiants plus simple à lire correspond moins à notre convention « chaîne UUID »

Dans le projet, nous avons fixé « chaîne UUID ». Cela signifie que l'identifiant public ressemble à un UUID, même si nous le stockons sous le type String. Par conséquent, la solution pédagogique la plus confortable consiste à utiliser des chaînes UUID fixes.

Exemple d'une énumération avec des statuts (elle nous servira pour des tâches « variées ») :

package com.example.tasktracker.domain.model;

// Statuts de la tâche : pratiques à utiliser à la fois dans le modèle domaine et dans les données d'amorçage.
public enum TaskStatus {
    TODO,
    IN_PROGRESS,
    BLOCKED,
    DONE,
    ARCHIVED
}

Avec un tel ensemble, il est facile de créer des tâches qui « vivent dans des états différents », plutôt que de rester identiquement dans TODO comme des étudiants un lundi matin.

3. Stockage des données d'amorçage

Lorsque des données d'amorçage apparaissent dans le projet, la main est tentée de les ajouter « là où c'est pratique ». Résultat : après quelques jours, vous trouvez les enregistrements initiaux à trois endroits : une partie dans le constructeur du référentiel, une autre dans le service, une autre dans un étrange util/DataGenerator. Puis quelqu'un modifie un champ à un endroit, oublie à un autre, et les données deviennent… « intéressantes ». Pas dans le bon sens.

Dans un projet pédagogique, ce qui importe n'est pas « l'architecture idéale pour tous les temps », mais la clarté et la prévisibilité. Un bon motif de base est donc le suivant : une seule classe qui décrit les données initiales. Elle n'est pas un bean Spring, elle ne contient pas de magie, c'est simplement une fabrique « liste des tâches de départ ».

Par exemple, dans le paquet infrastructure.repository.inmemory (ou à côté pour être facile à trouver), on peut placer TaskSeedData. Il pourrait ressembler à ceci (je montre de manière compacte, sans dizaines de champs, pour rester lisible) :

package com.example.tasktracker.infrastructure.repository.inmemory;

import com.example.tasktracker.domain.model.Task;
import com.example.tasktracker.domain.model.TaskStatus;
import java.util.List;

public final class TaskSeedData {

    public static List<Task> initialTasks() {
        // Important : identifiants fixes = reproductibilité des exemples et des requêtes .http.
        // Important : l'ordre des éléments dans la liste est également fixé intentionnellement.
        return List.of(
            task("11111111-1111-1111-1111-111111111111", "Assembler le squelette du projet", TaskStatus.DONE),
            task("22222222-2222-2222-2222-222222222222", "Créer le référentiel in-memory", TaskStatus.IN_PROGRESS)
        );
    }

    private TaskSeedData() {
        // Classe utilitaire : pas besoin d'instances.
    }
}

Un détail important ici est la méthode task(...). Elle permet de garder la liste courte et de ne pas transformer initialTasks() en un mur de texte sur trois écrans. Une version simple de la méthode helper pourrait être :

import com.example.tasktracker.domain.model.Task;
import com.example.tasktracker.domain.model.TaskStatus;

private static Task task(String id, String title, TaskStatus status) {
    // Création de l'objet de la façon la plus directe possible pour que les données d'amorçage soient lisibles.
    Task task = new Task(id, title);

    // On définit explicitement le statut pour que les données d'amorçage contiennent différents « scénarios de vie ».
    task.setStatus(status);

    return task;
}

Oui, nous utilisons ici un setter. Dans un monde idéal, vous auriez peut‑être rendu la tâche plus immuable, mais notre priorité actuelle est la transparence pédagogique. Les données d'amorçage doivent se lire d'un coup d'œil. Si pour cela il est plus pratique de créer un objet et de le « paramétrer » avec des setters, c'est normal à ce stade.

4. Chargement des données d'amorçage au démarrage

Une fois les données d'amorçage décrites, il reste la question d'ingénierie principale : quand et où les charger dans notre référentiel in-memory ? Nous voulons que les données apparaissent automatiquement au lancement de l'application, sans actions manuelles. Mais en même temps, nous ne voulons pas casser la direction des dépendances : controller -> service -> repository.

Le chemin le plus simple (et souvent idéal pour un projet pédagogique) est de charger les données d'amorçage dans le constructeur du référentiel. Le référentiel est créé par Spring une seule fois au démarrage, et à ce moment on peut remplir la Map interne avec les tâches initiales. Exemple (version raccourcie) :

package com.example.tasktracker.infrastructure.repository.inmemory;

import com.example.tasktracker.domain.model.Task;
import java.util.LinkedHashMap;
import java.util.Map;

public class InMemoryTaskRepository {
    // LinkedHashMap conserve l'ordre d'insertion – important pour un GET /tasks stable.
    private final Map<String, Task> tasks = new LinkedHashMap<>();

    public InMemoryTaskRepository() {
        // L'initialisation des données a lieu exactement une fois au démarrage de l'application.
        TaskSeedData.initialTasks().forEach(t -> tasks.put(t.getId(), t));
    }
}

Ici, LinkedHashMap n'est pas utilisé « parce que c'est à la mode », mais parce qu'il préserve l'ordre d'insertion. Cela aide lorsque vous faites un GET /tasks et attendez de voir les tâches dans un ordre stable. Dans les exemples pédagogiques, cela épargne vraiment les nerfs : vous ne discutez pas avec l'ordinateur pour savoir pourquoi aujourd'hui « la première tâche » est devenue une autre.

Si l'initialisation concerne ensuite plusieurs référentiels, on la déplace souvent dans un ApplicationRunner séparé dans config. Mais pour notre baseline actuel, c'est une couche supplémentaire inutile : l'important est de voir immédiatement d'où vient l'ensemble initial de tâches.

5. Données d'amorçage : diversité sans surcharge

Les données d'amorçage sont particulièrement utiles lorsque les enregistrements diffèrent les uns des autres de manière à permettre de montrer différents scénarios de lecture. Même si pour l'instant votre GET /tasks se contente de renvoyer une liste, il est déjà bon d'avoir des tâches « dans différents états », avec des noms différents, plutôt que des copies d'une même tâche.

Par exemple, on peut ajouter des tâches qui reflètent différentes étapes de la vie d'une tâche. Même si vous n'implémentez pas encore les règles métier de changement de statut, vous pouvez déjà stocker le statut à l'intérieur du Task, car cela fait partie du modèle domaine du projet. Ainsi les données d'amorçage deviennent vivantes et visuellement compréhensibles.

Un petit exemple de ce à quoi peuvent ressembler des données d'amorçage variées :

return List.of(
    // UUID fixes : ainsi il est facile d'écrire des requêtes déterministes dans .http.
    task("11111111-1111-1111-1111-111111111111", "Assembler le squelette du projet", TaskStatus.DONE),
    task("22222222-2222-2222-2222-222222222222", "Ajouter la séparation service/repository", TaskStatus.IN_PROGRESS),
    task("33333333-3333-3333-3333-333333333333", "Préparer les données d'amorçage", TaskStatus.TODO),
    task("44444444-4444-4444-4444-444444444444", "Comprendre l'API Attachment", TaskStatus.BLOCKED)
);

Ici nous n'entrons pas dans « comment trier correctement » ou « comment filtrer ». Nous créons simplement un ensemble initial qui sera ensuite pratique à utiliser dans n'importe quelle discussion. Vous lancez l'application — et dans la première réponse de GET /tasks vous pouvez voir d'un coup d'œil : « tiens, il y a du DONE, du BLOCKED ». Cela augmente fortement la capacité d'explication du projet.

6. Vérification manuelle

Vérification manuelle via .http

Une fois les données d'amorçage chargées, on veut vérifier qu'elles fonctionnent vraiment. La façon la plus simple est d'envoyer une requête à votre endpoint de liste. C'est un bon moment pour renforcer la pratique de « vérifier l'API à la main » sans tests ni infrastructure complexe.

Exemple de requête (à placer dans le fichier requests/tasks.http ou selon la convention du projet) :

### Liste des tâches (données d'amorçage)
# Vérifier que l'application démarre déjà avec des données (sans POST manuel avant la vérification).
GET http://localhost:8080/api/v1/tasks
Accept: application/json

Si tout est fait correctement, vous devriez obtenir une liste non vide. Et voilà un effet agréable : la même requête fonctionne de manière stable à chaque démarrage. Vous n'avez pas besoin de « cliquer au préalable pour produire des données ». Et si vous montrez le projet à quelqu'un d'autre, il le lance et voit immédiatement quelque chose de pertinent.

7. Erreurs typiques avec les données d'amorçage

Erreur n°1 : les données d'amorçage sont créées dans le contrôleur.
C'est très tentant : « je vais juste renvoyer une liste de trois tâches — et voilà ». Mais alors le contrôleur commence à stocker l'état, et le service et le référentiel deviennent des décors. Dans une telle architecture, vous allez inévitablement ajouter « une autre variable », « une autre liste », et le contrôleur se transformera en débarras. Les données d'amorçage doivent vivre dans le référentiel ou dans la configuration du démarrage de l'application, mais pas dans la couche web.

Erreur n°2 : des identifiants aléatoires via UUID.randomUUID() dans les données d'amorçage.
Au début, cela semble « plus correct ». Mais dès le lendemain, vous allez réécrire la moitié des requêtes .http parce que les identifiants ont changé, puis vous chercherez ces identifiants dans la réponse de l'endpoint list, les copierez manuellement et deviendrez nerveux. Pour un API pédagogique et pour des exemples reproductibles, des valeurs fixes sont nécessaires.

Erreur n°3 : des données d'amorçage trop volumineuses, impossibles à lire d'un coup d'œil.
Si TaskSeedData occupe 300 lignes, vous avez perdu. Les données d'amorçage doivent être petites pour être explicables. Mieux vaut 6 à 10 bonnes tâches qui diffèrent par le sens que 50 tâches identiques. Ce n'est pas une base de données, c'est la « vitrine » de départ du projet.

Erreur n°4 : les données d'amorçage sont dispersées dans le projet et dupliquées.
Quand une partie des tâches est créée dans TaskSeedData, une autre dans le constructeur du référentiel, et une autre dans un quelconque « assistant », vous obtiendrez tôt ou tard une désynchronisation. Par exemple, vous changez le titre d'une tâche à un endroit et oubliez à un autre. Résultat : au démarrage apparaissent deux enregistrements similaires ou les données se contredisent. Gardez les données d'amorçage dans un seul endroit et chargez‑les d'une manière unique.

Erreur n°5 : ordre instable de la liste renvoyée.
Même avec des identifiants fixes, on peut stocker accidentellement les données dans une structure qui ne garantit pas l'ordre, puis s'étonner que la liste « danse » entre les démarrages. Pour un référentiel in-memory dans un projet pédagogique, une bonne habitude est d'utiliser LinkedHashMap et de le remplir dans le même ordre. Ainsi GET /tasks est identique et prévisible.

1
Mission
Spring REST & MVC, niveau 9, leçon 3
Bloqué
Liste des films avec donnees initiales
Liste des films avec donnees initiales
1
Mission
Spring REST & MVC, niveau 9, leçon 3
Bloqué
Endpoint de détail du cours avec id déterministe
Endpoint de détail du cours avec id déterministe
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION