Configuration du module Spring
1. Structure
📁 quizmaker-backend/
└── 📁 quizmaker-spring/
├── src/main/java/fr/nantes/universite/quizmaker/
│ ├── controller/ # Contrôleurs (points d'entrée de l'API REST)
│ ├── service/ # Implémentations des services
│ └── QuizmakerApplication.java # Point d'entrée Spring Boot
└── src/main/resources/
├── db/changelog/ # Scripts Liquibase de migration de BDD
├── application.yml # Configuration centrale (BDD, Liquibase, Swagger, etc.)
└── openapi/
└── openapi.yaml # Spécification OpenAPI des routes à générer
2. Configuration des paquets Java
Pour commencer, vous allez créer trois paquets Java qui vous aideront à organiser vos classes .
|
3. L’application
Pour lancer une application Spring, vous aurez besoin d’une classe principale,
qui doit être annotée comme une application Spring Boot : @SpringBootApplication.
|
4. Les contrôleurs
Le paquet fr.nantes.universite.quizmaker.data.controller
contient les contrôleurs REST, responsables de l’exposition des endpoints de l’API.
Dans le cadre de ce projet, les classes présentes ici implémentent les interfaces générées automatiquement par OpenAPI à partir du fichier openapi.yaml.
Chaque contrôleur correspond à un domaine fonctionnel (p.ex. : AuthApiController, UserApiController, etc.).
|
Les contrôleurs ne doivent contenir aucune logique métier. Ils se contentent de :
|
public class AuthApiController implements AuthApi {
private final AuthService authService;
public AuthApiController(AuthService authService) {
this.authService = authService;
}
@Override
public ResponseEntity<AuthResponse> login(LoginRequest request) {
return ResponseEntity.ok(authService.login(request));
}
}
5. Les services
Le paquet fr.nantes.universite.quizmaker.data.service
contient les classes qui implémentent les services déclarés dans le module API.
C’est ici que réside toute la logique métier : vérifications, appels aux référentiels,
traitement de données, règles métier, etc.
Chaque classe dans service/ implémente une interface définie dans le module API.
|
La logique métier ne doit jamais être placée dans les contrôleurs ou les repositories. Elle doit toujours être centralisée ici, dans les services métier. |
public class AuthServiceImpl implements AuthService {
private final UserRepository userRepository;
public AuthServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public AuthResponse login(LoginRequest request) {
User user = userRepository.findByEmail(request.email())
.orElseThrow(() -> new UnauthorizedException("Utilisateur non trouvé"));
// Exemple simple : vérification brute du mot de passe (à sécuriser)
if (!user.getPassword().equals(request.password())) {
throw new UnauthorizedException("Mot de passe invalide");
}
return new AuthResponse("token-exemple");
}
}
|
Le fichier |
6. Implémentation d’un service
Vous allez à présent coder la classe UserServiceImpl, qui implémente le service UserService.
|
Indication de code pour UserServiceImpl
public User login(String email, String password) {
PersistentStudent s = studentRepository.findAll().stream()
.filter(u -> email != null && u.getEmail() != null && u.getEmail().equalsIgnoreCase(email))
.filter(u -> password != null && u.getPassword() != null && u.getPassword().equals(password))
.findFirst()
.orElse(null);
if (s != null) {
return new User(
s.getId(),
s.getEmail(),
s.getName(),
s.getFirstName(),
ROLE_STUDENT
);
}
PersistentTeacher t = teacherRepository.findAll().stream()
.filter(u -> email != null && u.getEmail() != null && u.getEmail().equalsIgnoreCase(email))
.filter(u -> password != null && u.getPassword() != null && u.getPassword().equals(password))
.findFirst()
.orElse(null);
if (t != null) {
return new User(
t.getId(),
t.getEmail(),
t.getName(),
t.getFirstName(),
ROLE_TEACHER
);
}
throw new IllegalArgumentException("Invalid email or password.");
}
7. Configuration d’OpenAPI
OpenAPI est une norme qui permet la spécification d’interfaces REST en YAML, pour ensuite générer le code, côté serveur et côté client, en différents langages de programmation. Voici un exemple de configuration OpenAPI pour le projet QuizMaker :
Exemple de configuration OpenAPI
openapi.yamlopenapi: 3.0.1
info:
title: QuizMaker API
version: 1.0.0
paths:
/api/user/login:
post:
tags:
- User
summary: Login
description: Authentifie un utilisateur (étudiant ou enseignant) par email et mot de passe.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: Utilisateur authentifié
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Requête invalide
'401':
description: Identifiants invalides
components:
schemas:
LoginRequest:
type: object
properties:
email:
type: string
format: email
password:
type: string
required: [email, password]
User:
type: object
properties:
id:
type: integer
format: int64
email:
type: string
format: email
name:
type: string
firstname:
type: string
role:
type: string
description: "STUDENT ou TEACHER"
required: [id, email, name, firstname, role]
|
Lisez attentivement la configuration que vous venez de créer et identifiez les éléments suivantes :
-
Un objet (ou enregistrement) nommé
LoginRequest, contenant 2 propriétés ; -
Un objet nommé
User, contenant 5 propriétés ; -
La route
/api/user/logindécrivant :-
qu’elle accepte une requête POST avec un
LoginRequestcontenantemailetpassword. -
les réponses possibles (
200si succès,401si identifiants invalides). -
la réponse est relié à l’objet
User.
-
|
|
Explications
Lors de la compilation du projet, le plugin OpenAPI Generator :
|
8. Lier le contrôleur au service
Pour lier l’interface REST à vous services, vous devez implémenter l’interface UserApi,
qui a été générée automatiquement.
Squelette de la classe UserController
|
Indication de code pour UserController
public ResponseEntity<User> apiUserLoginPost(LoginRequest loginRequest) {
try {
var user = userService.login(loginRequest.getEmail(), loginRequest.getPassword());
User response = new User()
.id(user.id())
.email(user.email())
.name(user.name())
.firstname(user.firstname())
.role(user.role());
return ResponseEntity.ok(response);
} catch (IllegalArgumentException e) {
return ResponseEntity.status(401).build();
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
9. Configuration de la base de données avec Liquibase
Liquibase est une bibliothèque open source pour le suivi, la gestion et l’application des changements de schéma de base de données, indépendante du SGBD utilisé.
Dans notre projet, nous allons l’utiliser pour créer et versionner les tables de la base de données.
Exemple de configuration Liquibase
master.xml<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<!-- Séquence utilisée par JPA (@GeneratedValue(strategy=SEQUENCE)) -->
<changeSet id="1-seq" author="quiz-maker-author">
<createSequence sequenceName="hibernate_sequence"/>
</changeSet>
<changeSet id="2-student" author="quiz-maker-author">
<createTable tableName="student">
<column name="id" type="BIGINT">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="email" type="VARCHAR(255)"/>
<column name="password" type="VARCHAR(255)"/>
<column name="name" type="VARCHAR(255)"/>
<column name="firstname" type="VARCHAR(255)"/>
</createTable>
<addUniqueConstraint tableName="student" columnNames="email" constraintName="uk_student_email"/>
<createIndex tableName="student" indexName="idx_student_email">
<column name="email"/>
</createIndex>
</changeSet>
<changeSet id="3-teacher" author="quiz-maker-author">
<createTable tableName="teacher">
<column name="id" type="BIGINT">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="email" type="VARCHAR(255)"/>
<column name="password" type="VARCHAR(255)"/>
<column name="name" type="VARCHAR(255)"/>
<column name="firstname" type="VARCHAR(255)"/>
</createTable>
<addUniqueConstraint tableName="teacher" columnNames="email" constraintName="uk_teacher_email"/>
<createIndex tableName="teacher" indexName="idx_teacher_email">
<column name="email"/>
</createIndex>
</changeSet>
</databaseChangeLog>
|
Lisez attentivement le fichier de configuration master.xml.
Il définit les changements à appliquer (appelés changeSet).
-
création de la table
studentavec ses colonnes, -
création de la table
teacher.
|
Lors du démarrage de Spring Boot :
|
10. Conclusion
Voici l’enchaînement d’une requête de connexion :
-
Le client envoie une requête POST vers
/api/user/loginavec l’email et le mot de passe. -
Spring Boot redirige cette requête vers la méthode correspondante du contrôleur généré via OpenAPI.
-
Le contrôleur appelle le service
UserService. -
Le service vérifie dans les repositories si un étudiant ou un enseignant correspond à l’email et au mot de passe.
-
Si un utilisateur est trouvé :
-
un
UserDTO est créé et renvoyé au contrôleur, -
qui le renvoie en réponse JSON au client avec un code HTTP
200.
-
-
Sinon, une exception est levée, et la réponse HTTP est
401 Unauthorized.
Chaque couche a donc une responsabilité claire :
-
OpenAPI → définit la structure de l’API.
-
Contrôleur → reçoit et renvoie les données.
-
Service → applique la logique métier.
-
Repository → interagit avec la base.
-
Liquibase → gère la structure de la base.
11. Étendre le projet
Une fois la route /api/user/login fonctionnelle, il est possible d’y ajouter d’autres fonctionnalités :
-
créer de nouvelles routes OpenAPI (
/api/user/register,/api/quiz/create, etc.), -
ajouter de nouveaux
changeSetLiquibase pour les tables nécessaires, -
créer les DTO correspondants dans
quizmaker-api, -
implémenter les nouveaux services dans
quizmaker-spring.
Cette approche garantit que chaque ajout reste cohérent et documenté, sans duplication ni conflit entre modules.