Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 65

Architecture MVC en 3 couches : Controller, Service, Repository

Recommended resources

Bonjour ! Dans la leçon précédente, vous avez appris à créer une interface Repository avec Spring Data JPA, ce qui vous a donné un accès puissant et simplifié à votre base de données. Vous avez maintenant les entités (@Entity) pour modéliser vos données et les repositories (@Repository) pour les manipuler.

Cependant, une question d'architecture essentielle se pose : où placer la logique métier ? Par exemple, si vous voulez vérifier qu'un livre n'existe pas déjà avant de le créer, où ce code doit-il aller ? Le mettre directement dans le contrôleur qui gère la requête HTTP ? Ou dans le repository ? La réponse est "ni l'un ni l'autre".

Aujourd'hui, nous allons aborder une des conventions les plus importantes du développement backend : l'architecture en 3 couches. Vous découvrirez comment structurer votre application de manière professionnelle en séparant clairement les responsabilités entre les couches Controller, Service et Repository. Cette structure est la fondation de la plupart des applications Spring Boot robustes et maintenables.

1. Les Trois Couches : Rôles et Responsabilités

L'architecture en 3 couches (ou three-tier architecture) est un patron de conception logiciel qui divise une application en trois unités logiques et physiques distinctes. Dans le contexte de Spring Boot, ces couches sont :

  1. La Couche Présentation (Controller) : C'est la porte d'entrée de votre application. Son unique rôle est de gérer les requêtes et les réponses HTTP. Elle reçoit les requêtes, extrait les paramètres et les données, puis délègue le traitement à la couche de service. Elle ne doit contenir aucune logique métier.
  2. La Couche Métier (Service) : C'est le cœur de votre application. Elle contient toute la logique et les règles métier (par exemple, valider des données, orchestrer des opérations complexes, effectuer des calculs). C'est elle qui décide comment les données doivent être manipulées. Pour ce faire, elle collabore avec la couche de persistance.
  3. La Couche d'Accès aux Données (Repository) : C'est la couche la plus basse, responsable de la communication avec la base de données. Son seul but est de réaliser les opérations CRUD (Create, Read, Update, Delete). C'est ici que se trouvent vos interfaces JpaRepository.

Le flux de communication est toujours unidirectionnel : le Controller appelle le Service, qui lui-même appelle le Repository.

Ce diagramme illustre le flux classique d'une requête dans une application Spring Boot. Le client envoie une requête au Controller, qui la transmet au Service. Le Service utilise le Repository pour interagir avec la base de données, puis la réponse remonte la chaîne jusqu'au client.

La vidéo suivante explique très bien le rôle de chaque couche et pourquoi cette séparation est fondamentale.

Apprendre Spring Boot #2 HTTP POST & DTO : Controller → Service → JPA (RequestBody)

La chaîne JirAWS propose une série de vidéos très pédagogiques sur Spring Boot. Cet extrait introduit parfaitement l'architecture en couches.

Regardez attentivement le début de la vidéo, de l'introduction aux couches. L'auteur y décrit le rôle de chaque couche (Présentation, Métier/Service, Données/DAL) et leur interaction. Notez les différents noms qui peuvent être utilisés pour chaque couche (par ex. DAL, DAO, Persistance pour la couche de données).

Pour résumer, voici les responsabilités de chaque couche dans une table :

Couche Annotation Spring Rôle principal Ce qu'elle fait Ce qu'elle ne fait pas
Controller @RestController Gérer le trafic HTTP Valider les entrées, mapper les URL, appeler les services, formater les réponses JSON. Contenir de la logique métier, accéder directement au repository.
Service @Service Exécuter la logique métier Orchestrer les opérations, gérer les transactions, appliquer les règles de l'application. Manipuler les objets HttpServletRequest ou Response, connaître les détails de l'API REST.
Repository @Repository Communiquer avec la BDD Exécuter les requêtes de lecture/écriture sur les entités. Contenir de la logique métier, gérer les transactions complexes.

2. Pourquoi cette séparation ? Maintenabilité et Principe de Responsabilité Unique

Vous pourriez vous demander pourquoi ajouter cette complexité apparente, surtout pour un petit projet. La réponse réside dans la maintenabilité, la testabilité et l'évolutivité de votre code.

Cette structure applique le Principe de Responsabilité Unique (le "S" de l'acronyme SOLID), un pilier du génie logiciel. Chaque classe (et par extension, chaque couche) ne devrait avoir qu'une seule raison de changer.

  • Si vous devez changer la façon dont une URL est exposée, vous ne modifiez que le Controller.
  • Si une règle métier change (ex: le prix d'un produit dépend maintenant d'une promotion), vous ne modifiez que le Service.
  • Si vous changez de base de données (de MySQL à PostgreSQL), vous ne modifiez (idéalement) que la configuration et potentiellement le Repository.

Cette séparation rend également les tests unitaires beaucoup plus simples : vous pouvez tester la logique métier d'un service en "simulant" (en mockant) le repository, sans avoir besoin d'une vraie base de données.

La vidéo suivante explique très bien ces avantages.

Apprendre Spring Boot #2 HTTP POST & DTO : Controller → Service → JPA (RequestBody)

Continuons avec la même vidéo, qui aborde maintenant le "pourquoi" de cette architecture.

Regardez le segment sur le principe de responsabilité unique. L'auteur explique comment cette organisation aide à gérer des projets à grande échelle et facilite le débogage en sachant exactement où chercher un problème.

Pour graver ces règles dans le marbre, l'article ci-dessous fournit une excellente synthèse des "règles du jeu" pour chaque couche.

Bâtir une application Java Spring Boot avec Claude Code - Blog ...

Cet article de blog, bien que centré sur un outil d'IA, documente parfaitement les règles d'une architecture en couches stricte dans un projet Spring Boot.

Concentrez-vous sur la section "Architecture Layered documentée". Lisez attentivement les règles "INTERDIT" et "OBLIGATOIRE" pour chaque couche (Controller, Service, Repository). Jetez aussi un œil à la section "À ÉVITER" qui mentionne explicitement "Mélanger les couches" comme une pratique à proscrire.


3. Mise en Pratique : Câbler les Couches

Assez de théorie, passons au code ! Nous allons maintenant structurer notre application en respectant ce modèle. L'idée est de :

  1. Créer une classe BookService.
  2. Y injecter notre BookRepository.
  3. Déplacer toute logique qui était dans le contrôleur vers le service.
  4. Injecter le BookService dans notre BookController.
  5. Faire en sorte que le contrôleur appelle le service, et non plus le repository.

La vidéo que nous avons suivie montre exactement ce processus de refactoring.

Apprendre Spring Boot #2 HTTP POST & DTO : Controller → Service → JPA (RequestBody)

Cette démonstration pratique vous montrera comment réorganiser le code pour respecter l'architecture en 3 couches.

Suivez attentivement ces deux parties qui s'enchaînent : D'abord, la création du service, où la logique métier (ici, la vérification de l'existence d'un livre avant sa création) est déplacée du contrôleur vers une nouvelle classe BookService annotée avec @Service. Puis, l'injection du service dans le contrôleur. Observez bien comment le contrôleur ne dépend plus du BookRepository mais uniquement du BookService.


Exercice Pratique

Il est temps d'appliquer cette structure à votre projet.

  1. Dans votre projet, au même niveau que les packages controller et repository, créez un nouveau package nommé service.
  2. À l'intérieur du package service, créez une classe BookService. Annotez-la avec @Service.
    package com.votreprojet.service;
    
    import org.springframework.stereotype.Service;
    import com.votreprojet.repository.BookRepository;
    import com.votreprojet.model.Book;
    import java.util.List;
    
    @Service
    public class BookService {
    
        private final BookRepository bookRepository;
    
        // Injection de dépendances par constructeur (bonne pratique)
        public BookService(BookRepository bookRepository) {
            this.bookRepository = bookRepository;
        }
    
        public List<Book> getAllBooks() {
            return bookRepository.findAll();
        }
    
        // D'autres méthodes métier viendront ici...
        // public Book createBook(Book newBook) { ... }
    }
    
  3. Modifiez maintenant votre BookController. Supprimez l'injection du BookRepository et remplacez-la par une injection du BookService.
    package com.votreprojet.controller;
    
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.GetMapping;
    import com.votreprojet.service.BookService;
    import com.votreprojet.model.Book;
    import java.util.List;
    
    @RestController
    public class BookController {
    
        private final BookService bookService;
    
        // Injection de dépendances par constructeur
        public BookController(BookService bookService) {
            this.bookService = bookService;
        }
    
        @GetMapping("/books")
        public List<Book> getAllBooks() {
            // Le contrôleur délègue au service
            return bookService.getAllBooks();
        }
    }
    
  4. Relancez votre application. Vérifiez que tout démarre sans erreur. Si vous avez un endpoint comme /books, testez-le pour vous assurer qu'il fonctionne toujours. Le comportement externe n'a pas changé, mais l'organisation interne de votre code est maintenant beaucoup plus propre et professionnelle.

Conclusion

Félicitations ! Vous venez d'adopter l'une des pratiques les plus fondamentales pour construire des applications backend solides.

Les points clés à retenir sont :

  • Controller : Gère les requêtes/réponses HTTP. C'est la couche d'exposition de votre API.
  • Service : Contient la logique métier. C'est le cerveau de votre application.
  • Repository : Gère la persistance des données. C'est le lien avec votre base de données.
  • Le flux est toujours : Controller -> Service -> Repository.
  • Cette séparation améliore la maintenabilité, la testabilité et l'évolutivité de votre code.

Maintenant que nous avons une architecture saine et bien structurée, nous sommes prêts à construire notre API REST complète.

Dans la prochaine leçon, nous allons mettre à profit cette nouvelle structure pour implémenter l'ensemble des endpoints CRUD (Create, Read, Update, Delete) pour notre entité Book, en utilisant les différentes méthodes HTTP (POST, GET, PUT, DELETE).