Architecture du back-end

Dans cette section, vous découvrirez l’architecture du back-end utilisée pour le projet transversal.

Principe général

Le back-end repose sur une architecture API REST. Cette approche permet une communication structurée entre le front-end et le back-end. Lorsque l’utilisateur interagit avec une fonctionnalité du site nécessitant un traitement côté serveur, une requête HTTP est envoyée vers une route spécifique, associée à un contrôleur.

Fonctionnement des contrôleurs

Un contrôleur agit comme un point d’entrée. Il est responsable de la réception de la requête, mais ne contient aucune logique métier.

Un contrôleur ne doit contenir aucun traitement métier. Son seul rôle est de déléguer le travail aux services appropriés.

Rôle des services

Les services contiennent l’ensemble de la logique métier. Ils sont responsables de l’exécution des opérations demandées, comme l’accès aux données, la validation, ou la coordination entre plusieurs composants.

Cette séparation permet d’assurer un code modulaire, testable et maintenable.

Principe de responsabilité unique

Chaque composant du back-end doit respecter le principe de responsabilité unique (Single Responsibility Principle). Cela signifie qu’un module (par exemple un service ou un contrôleur) doit avoir une seule responsabilité claire.

Le respect de ce principe améliore la lisibilité du code, facilite la maintenance, et limite les effets de bord.

Organisation du projet

Dans le cadre du projet transversal, une architecture simple et pédagogique a été choisie. Elle permet de comprendre facilement les rôles de chaque composant tout en gardant l’implémentation accessible.

Voici la structure adoptée (multi-modules Maven) :

quizmaker-backend/
 ├── quizmaker-api/
 │   └── src/main/java/fr/universite/nantes/quizmaker/common/dto/
 │       └── ...                # Objets de transfert (records Java)
 │   └── pom.xml
 ├── quizmaker-domain/
 │   └── src/main/java/fr/universite/nantes/quizmaker/data/
 │       ├── entity/            # Entités JPA persistées
 │       ├── repository/        # Interfaces d’accès aux données (Spring Data JPA)
 │       └── service/           # Interfaces métier (contrats de service)
 │   └── pom.xml
 └── quizmaker-spring/
     ├── src/main/java/fr/universite/nantes/quizmaker/
     │   ├── controller/        # Contrôleurs (points d’entrée de l’API REST)
     │   ├── implService/       # Implémentations concrètes 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
     └── pom.xml

Le dossier dto/ se trouve dans le module quizmaker-api et contient des record Java servant à transporter les données entre les couches. Exemple :

public record UserDto(Long id, String email, String name, String firstname, String role) {}

Le fichier openapi.yaml est situé dans quizmaker-spring/src/main/resources/openapi/ afin de centraliser les spécifications de l’API REST. Ce fichier est utilisé pour générer automatiquement les contrôleurs et les modèles, ou comme documentation interactive via Swagger.

Description des répertoires principaux

controller/ (module quizmaker-spring)

Ce répertoire 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 (ex. : AuthApiController, UserApiController, etc.).

Les contrôleurs ne doivent contenir aucune logique métier. Ils se contentent de :

  • recevoir les requêtes HTTP,

  • extraire les données nécessaires,

  • déléguer le traitement à un service via son interface,

  • retourner la réponse au client (souvent un DTO).

Exemple simplifié :

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));
    }
}

service/ (module quizmaker-domain)

Ce dossier regroupe toutes les interfaces de service, c’est-à-dire les contrats métier proposés par chaque domaine (authentification, utilisateur, etc.).

Les interfaces définissent les fonctionnalités de haut niveau sans se préoccuper de leur implémentation.

Cela permet de :

  • découpler la logique métier de sa mise en œuvre,

  • faciliter le testing (mocking),

  • permettre plusieurs implémentations possibles si besoin.

Exemple :

public interface AuthService {
    AuthResponse login(LoginRequest request);
}

implService/ (module quizmaker-spring)

Ce répertoire contient les implémentations concrètes des interfaces situées dans quizmaker-domain/service/.

C’est ici que réside toute la logique métier : vérifications, appels aux repositories, traitement de données, règles fonctionnelles, etc.

Chaque classe dans implService/ implémente une interface définie dans quizmaker-domain/service/.

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.

Exemple :

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");
    }
}

repository/ (module quizmaker-domain)

Le répertoire repository/ contient les interfaces d’accès aux données. Elles utilisent Spring Data JPA pour manipuler les entités en base de données sans avoir besoin d’implémenter manuellement les requêtes SQL.

Ces interfaces étendent JpaRepository et permettent d’accéder à des opérations courantes comme :

  • findById(…​)

  • findAll()

  • save(…​)

  • deleteById(…​)

Grâce à Spring Data, ces méthodes sont automatiquement générées à l’exécution, ce qui réduit considérablement le code nécessaire pour interagir avec la base.

Une interface repository est liée à une entité. On lui donne généralement un nom clair reflétant son usage, souvent basé sur le nom de l’entité manipulée (ex : UserRepository pour l’entité User).

Exemple :

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

Un repository ne contient aucune logique métier. Il sert uniquement à interagir avec la base de données, et doit être utilisé exclusivement au sein des services métier (implService/).

entity/ (module quizmaker-domain)

Le répertoire entity/ contient les entités JPA représentant les tables de la base de données. Chaque entité est une classe annotée avec @Entity et mappée à une table via @Table.

Ces entités définissent les champs qui seront persistés, ainsi que les métadonnées associées (nom de colonne, stratégie d’identifiant, etc.).

Dans ce projet, les entités peuvent être annotées avec @Cache pour activer la mise en cache de second niveau avec Hibernate.

Voici un exemple d’entité User :

import jakarta.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Table(name = "user")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(nullable = false)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "firstname")
    private String firstname;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    // Getters & Setters
}

Les entités ne doivent jamais être utilisées dans les contrôleurs pour retourner des réponses API. Utilisez des DTO pour exposer les données nécessaires au client, et ainsi éviter les fuites de structure interne ou de données sensibles.