Bonjour !
Dans notre dernière leçon, nous avons établi les fondations d'une application Spring Boot professionnelle en la structurant en couches : Présentation, Métier et Données. Vous avez vu comment cette séparation clarifie le rôle de chaque composant, une pratique qui a des parallèles avec le pattern MVC que vous connaissez de Laravel.
Aujourd'hui, nous allons formaliser les principes qui sous-tendent cette architecture. L'objectif de cette leçon est d'apprendre à appliquer les principes de Responsabilité Unique (SRP) et d'Inversion de Dépendances (DIP) dans un projet Spring Boot. Ces deux principes sont issus de l'acronyme SOLID, un ensemble de bonnes pratiques qui sont la marque d'un développement logiciel de haute qualité, particulièrement valorisé sur les marchés européens et américains que vous visez.
Nous verrons que le SRP est la raison d'être de l'architecture en couches, et que le DIP nous dicte la manière dont ces couches doivent interagir pour créer une application flexible et robuste.
1. Le Principe de Responsabilité Unique (SRP)
Le SRP est sans doute le plus simple à comprendre mais aussi l'un des plus fondamentaux. Comme son nom l'indique, il stipule qu'une classe ne devrait avoir qu'une seule raison de changer.
Pour une définition claire et des exemples en Java, je vous propose de lire la ressource suivante.
L'article "Principes SOLID en Java" sur DEV Community offre une explication très concise et efficace du SRP avec un exemple avant/après.
Lisez la première section intitulée "1️⃣ S - Principe de Responsabilité Unique (Single Responsibility Principle - SRP)". Analysez bien le "Mauvais exemple" et le "Bon exemple" pour saisir comment la responsabilité a été séparée.
Pour résumer, si une classe Employe gère à la fois les informations de l'employé ET le calcul de sa paie, elle a deux raisons de changer :
- Une modification dans la structure des données de l'employé (ajout d'une adresse).
- Une modification dans les règles de calcul de la paie (changement du taux de cotisation).
Le SRP nous pousse à scinder cette classe en deux : une classe Employe (un simple objet de données) et une classe PaieService (qui contient la logique de calcul).
Le SRP et l'architecture en couches
L'architecture en 3 couches que nous avons vue est une application directe du SRP à grande échelle :
@RestController(Présentation) : Sa seule responsabilité est la communication HTTP (gérer les requêtes, sérialiser/désérialiser les DTOs, retourner les codes de statut). Elle ne devrait pas changer si une règle métier évolue.@Service(Métier) : Sa seule responsabilité est d'implémenter la logique métier. Elle ne devrait pas changer si on décide de passer de JSON à XML dans l'API.@Repository(Données) : Sa seule responsabilité est la persistance des données. Elle ne devrait pas changer si la logique de validation d'un champ métier devient plus complexe.

Quand ce principe n'est pas respecté, on se retrouve avec du code "fourre-tout" difficile à maintenir. La vidéo ci-dessous montre un exemple concret de violation du SRP dans un projet Spring.
L'Architecture Hexagonale par la pratique, le live coding qui rendra vos applications pl...
Dans cette présentation sur l'architecture hexagonale, l'orateur commence par illustrer les problèmes des architectures classiques, notamment la violation du SRP. C'est un excellent exemple de ce qu'il faut éviter.
Regardez le passage de 04:51 à 06:30. L'orateur montre une classe "métier" qui est polluée par des annotations techniques de deux frameworks différents : JPA/Hibernate (pour la base de données) et Jackson (pour l'API REST). Cette classe a donc plusieurs raisons de changer, ce qui est une violation flagrante du SRP.
2. Le Principe d'Inversion des Dépendances (DIP)
Maintenant que nos responsabilités sont bien séparées en couches, comment ces couches doivent-elles dépendre les unes des autres ? C'est là qu'intervient le DIP.
Le principe énonce deux choses :
- Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions.
- Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.
Cela peut paraître abstrait, mais l'idée est simple : dépendre d'interfaces, pas de classes concrètes.
Le Problème : Le Couplage Fort
Imaginons un ServiceCommande qui a besoin de sauvegarder des commandes en base de données. L'approche naïve serait de dépendre directement d'une classe MySQLDatabase.
Architecture Hexagonale : comment venir à bout du code spaghetti ? (Kévin LLOPIS)
Cette vidéo expose très clairement le problème du couplage fort. L'orateur présente une situation où la couche métier est directement liée à une implémentation technique de base de données.
Regardez de 02:55 à 04:16. Vous y verrez un TalkService (la couche métier) qui dépend directement de MySqlDatabaseService (la couche de données). Notez les problèmes que cela engendre : le service métier est pollué par des détails techniques (ResultSet) et toute migration devient un cauchemar.
Ce couplage fort est un problème majeur : si demain vous devez migrer de MySQL à PostgreSQL, ou même utiliser MongoDB (ce que vous connaissez déjà), vous devrez modifier votre ServiceCommande, qui contient pourtant la logique métier qui, elle, n'a pas changé.
La Solution : Inverser la Dépendance
Le DIP nous invite à introduire une abstraction (une interface) entre le service et la base de données.
Pour la définition formelle et un exemple simple, la ressource sur SOLID est de nouveau très pertinente.
La section sur le DIP de l'article sur les principes SOLID illustre parfaitement comment utiliser une interface pour découpler les composants.
Lisez la section "5️⃣ D - Principe d'Inversion des Dépendances (Dependency Inversion Principle - DIP)". Comparez le mauvais exemple (dépendance directe) et le bon exemple (dépendance via une interface).
Au lieu que ServiceCommande dépende de MySQLDatabase, on crée une interface BaseDeDonnees.
ServiceCommandedépend maintenant deBaseDeDonnees.MySQLDatabaseimplémente l'interfaceBaseDeDonnees.
Le sens de la dépendance a été inversé ! Le service (haut niveau) ne dépend plus de la base de données (bas niveau). Les deux dépendent de l'abstraction BaseDeDonnees.
Le Mécanisme : L'Injection de Dépendances (DI) de Spring
C'est bien beau de dépendre d'une interface, mais à un moment, il faut bien une implémentation concrète ! Comment notre ServiceCommande obtient-il son instance de MySQLDatabase ?
C'est précisément le rôle du conteneur d'Inversion de Contrôle (IoC) de Spring, qui met en œuvre le DIP via un mécanisme appelé Injection de Dépendances (DI).
L'article suivant fait un excellent travail pour lier ces concepts.
L'inversion de contrôle — Spring Framework / Spring Boot
Cet article de David Gayerie explique comment le principe DIP est au cœur de Spring, et comment le framework le met en œuvre via l'injection de dépendances.
Lisez d'abord la courte introduction qui lie IoC et DIP (section 0). Ensuite, lisez la section "Injection de la dépendance par le constructeur" (section 4), qui est la méthode recommandée. Voyez comment la responsabilité de créer et de "brancher" les objets est déléguée au framework.
En résumé, au lieu de faire new MySQLDatabase() dans votre service (ce qui recréerait un couplage fort), vous déclarez simplement votre dépendance à l'interface dans le constructeur, et Spring se charge de vous "injecter" l'implémentation concrète qu'il a trouvée au démarrage.
@Service
public class ServiceCommande {
private final BaseDeDonnees db; // Dépendance à l'interface
// Spring va injecter une classe qui implémente BaseDeDonnees
public ServiceCommande(BaseDeDonnees db) {
this.db = db;
}
public void traiterCommande(String commande) {
// ... logique métier
db.sauvegarderCommande(commande);
}
}
La Preuve par l'Exemple : Une Migration sans Douleur
Revenons à la vidéo de Devoxx. Après avoir introduit l'interface, l'orateur démontre la puissance du DIP en migrant sa couche de persistance sans modifier une seule ligne de sa couche métier.
Architecture Hexagonale : comment venir à bout du code spaghetti ? (Kévin LLOPIS)
C'est le moment "eurêka". Vous allez voir la refactorisation en action et son bénéfice immédiat.
Regardez attentivement de 04:16 à 10:20. D'abord (jusqu'à 07:13), il refactorise le code pour introduire une interface TalkRepository (le port) et fait dépendre le TalkService de cette interface. Ensuite (à partir de 07:13), il crée une nouvelle implémentation basée sur Spring Data JPA (TalkRepositoryAdapter) et, grâce à Spring, il la substitue à l'ancienne implémentation. Le point crucial est à 09:45 : il confirme qu'il n'y a rien à changer dans le TalkService.
Cette démonstration est la quintessence du DIP : votre logique métier est protégée des changements techniques. Vous pouvez changer de base de données, de fournisseur d'email, de système de paiement... tant que la nouvelle implémentation respecte le contrat défini par l'interface, votre logique métier reste intacte.
Conclusion
Aujourd'hui, nous avons ajouté deux outils essentiels à votre arsenal d'architecte logiciel. Ces principes ne sont pas de la théorie obscure ; ce sont des guides pratiques pour écrire du code professionnel, maintenable et évolutif.
Les points clés à retenir :
- Principe de Responsabilité Unique (SRP) : Une classe doit avoir une seule raison de changer. C'est le principe qui motive l'architecture en couches (
Controller,Service,Repository). - Principe d'Inversion de Dépendances (DIP) : Dépendre d'abstractions (interfaces), et non d'implémentations concrètes. Cela permet de découpler les couches et de rendre le système flexible.
- Spring et le DIP : Le framework Spring est conçu autour de ce principe. Il facilite son application grâce à son conteneur IoC et au mécanisme d'injection de dépendances, de préférence par constructeur.
Dans la prochaine leçon, nous nous concentrerons sur une application spécifique de ces principes : justifier l'utilisation du pattern Repository pour l'abstraction de l'accès aux données. Vous verrez que Spring Data JPA est lui-même une magnifique illustration du DIP, vous offrant une interface (JpaRepository) pour masquer la complexité de l'implémentation sous-jacente.