1. Quand et pourquoi découper un projet en modules
Pourquoi il ne faut pas tout mettre dans un seul module
Si vous écrivez un petit TP ou "Hello, World!", le système de modules peut sembler superflu. Mais à mesure que le projet grandit — des dizaines et des centaines de classes, de nombreux packages, des bibliothèques tierces — le chaos devient inévitable. C’est comme une bibliothèque sans étagères : tant qu’il y a peu de livres, ça passe, mais ensuite il devient difficile de trouver quoi que ce soit. Les modules sont vos étagères : ils aident à mettre de l’ordre et cachent la « cuisine » (l’implémentation), en ne laissant visible que la « vitrine » (l’API).
Pourquoi découper en modules
- Séparation des responsabilités : chaque module est responsable de son domaine (par exemple, base de données, logique métier, UI).
- Réutilisation du code : un module peut être branché dans un autre projet.
- Amélioration de la testabilité : les modules sont testés indépendamment.
- Sécurité et encapsulation : seule l’API est visible, l’implémentation est cachée.
- Facilitation de la maintenance : moins de liaisons « magiques », carte des dépendances claire.
- Compilation et déploiement rapides : seuls les modules modifiés sont reconstruits.
Quand découper en modules
- Le projet devient trop grand pour un seul développeur (ou l’IDE commence à « ramer »).
- Des parties distinctes se dégagent nettement : core, ui, utils, api, impl.
- Vous prévoyez de réutiliser le code dans d’autres projets.
- Il existe des dépendances externes nécessaires uniquement à une partie du projet.
- Il faut cacher les détails d’implémentation (algorithmes, classes internes).
2. Schémas types de modularité
Ci‑dessous, des schémas populaires de découpage, adaptés aux projets d’étude comme aux projets en production.
Architecture « en oignon » (Onion Architecture)
La couche externe dépend de la couche interne, mais pas l’inverse.
[ app (UI) ]
↓
[ core (logique) ]
↓
[ utils (utilitaires) ]
- app — module externe : interface graphique, application web, point d’entrée (main).
- core — logique métier, modèles, services.
- utils — classes utilitaires.
Règle : la couche interne ne doit pas dépendre de l’externe. Ainsi, core peut être réutilisé dans différentes interfaces (console, web, desktop).
Modules pour l’API et l’implémentation
Pour les bibliothèques, il est pratique de séparer les interfaces de leur implémentation :
[ mylib.api ] ← exporte uniquement les interfaces
[ mylib.impl ] ← contient les implémentations, n’est pas exporté
Modules dédiés aux tests
On place souvent les tests dans un module séparé afin qu’ils n’entrent pas dans l’artefact de production.
[ app ]
[ core ]
[ core.tests ]
Schéma pour un projet pédagogique
myeditor/
├─ app/ ← point d’entrée, lancement de l’application
├─ core/ ← logique métier (travail avec les fichiers, le texte)
└─ utils/ ← utilitaires (journalisation, parsing)
3. Dépendances entre modules
Dans les modules Java, les dépendances sont déclarées explicitement dans module-info.java à l’aide du mot-clé requires. Cela améliore la lisibilité et permet au compilateur/JVM de contrôler l’accessibilité de l’API via exports.
Exemple de dépendance
core/module-info.java
module myeditor.core {
exports myeditor.core.api; // seul le package api est visible publiquement
requires myeditor.utils; // on utilise les utilitaires
}
app/module-info.java
module myeditor.app {
requires myeditor.core; // on utilise core
requires myeditor.utils; // nous pouvons utiliser les utilitaires directement
}
Règles et bonnes pratiques
- Évitez les dépendances circulaires. Si A requires B et B requires A — c’est un défaut de conception. En général, on le résout en extrayant un common/api commun.
- Réduisez les dépendances au minimum. N’ajoutez pas un module s’il n’est réellement pas nécessaire.
- Exportez les packages utilisés. Les classes doivent se trouver dans des packages déclarés via exports, sinon il y aura une erreur de compilation.
- Utilitaires — aussi indépendants que possible. utils ne doit pas dépendre de la logique métier.
4. Pratique : exemple de découpage d’un projet pédagogique en 3 modules
Structure des dossiers
myeditor/
├─ app/
│ ├─ src/
│ │ └─ myeditor/app/Main.java
│ └─ module-info.java
├─ core/
│ ├─ src/
│ │ ├─ myeditor/core/api/TextService.java
│ │ └─ myeditor/core/impl/TextServiceImpl.java
│ └─ module-info.java
└─ utils/
├─ src/
│ └─ myeditor/utils/Logger.java
└─ module-info.java
Exemples de module-info.java
core/module-info.java
module myeditor.core {
exports myeditor.core.api;
requires myeditor.utils;
}
app/module-info.java
module myeditor.app {
requires myeditor.core;
requires myeditor.utils;
}
utils/module-info.java
module myeditor.utils {
exports myeditor.utils;
}
Exemple de code (TextService)
myeditor/core/api/TextService.java
package myeditor.core.api;
public interface TextService {
String toUpperCase(String text);
}
myeditor/core/impl/TextServiceImpl.java
package myeditor.core.impl;
import myeditor.core.api.TextService;
public class TextServiceImpl implements TextService {
@Override
public String toUpperCase(String text) {
return text.toUpperCase();
}
}
myeditor/app/Main.java
package myeditor.app;
import myeditor.core.api.TextService;
import myeditor.core.impl.TextServiceImpl;
public class Main {
public static void main(String[] args) {
TextService service = new TextServiceImpl();
System.out.println(service.toUpperCase("hello, modules!"));
}
}
À quoi cela ressemble dans IntelliJ IDEA
- Chaque répertoire est un Module distinct dans la structure du projet.
- Chaque module possède son module-info.java à la racine de src.
- Au lancement de main depuis app, l’IDE choisira automatiquement le module-path.
- Toute tentative d’utiliser une classe depuis un package non exporté se soldera par une erreur de compilation.
5. Impact sur la construction : Maven/Gradle et modules
Maven
Un projet multi‑module se compose d’un projet « parent » (parent) et de plusieurs modules « enfants ».
myeditor/
├─ pom.xml ← parent
├─ app/
│ └─ pom.xml
├─ core/
│ └─ pom.xml
└─ utils/
└─ pom.xml
Exemple de parent pom.xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>myeditor</groupId>
<artifactId>myeditor-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>app</module>
<module>core</module>
<module>utils</module>
</modules>
</project>
Particularités :
- Maven tient compte de module-info.java lors de la compilation.
- Pour l’exécution, on utilise --module-path au lieu de --classpath.
- Si vous oubliez exports ou requires — vous aurez une erreur de compilation.
Gradle
La multi‑modularité se configure via settings.gradle et des build.gradle séparés pour les modules.
settings.gradle
rootProject.name = 'myeditor'
include 'app', 'core', 'utils'
build.gradle pour un module
plugins {
id 'java'
}
java {
modularity.inferModulePath = true
}
IntelliJ IDEA
- IDEA sait créer module-info.java lors de la création d’un module Java.
- Avec Maven/Gradle, la structure des modules est détectée automatiquement.
- Au lancement de main depuis app, l’IDE configurera le module-path.
- Les boîtes de dialogue d’import/export indiquent la visibilité des packages et des modules.
Erreurs courantes lors du découpage en modules
Erreur n° 1 : Dépendances circulaires entre modules. Si deux modules déclarent requires l’un envers l’autre, le compilateur émettra une erreur. C’est généralement le signe d’une architecture « qui a dérivé ». Solution — extraire un module api commun ou revoir les frontières.
Erreur n° 2 : Utilisation de classes issues de packages non exportés. Une classe peut être public, mais si le package n’est pas mentionné dans exports dans module-info.java, un autre module ne la verra pas. Résultat : erreur de compilation.
Erreur n° 3 : Oubli d’ajouter requires pour le module utilisé. Un import depuis un autre module sans l’entrée correspondante dans module-info.java ne compilera pas. Déclarez toujours les dépendances explicitement.
Erreur n° 4 : Duplication des noms de modules. Les noms de modules doivent être uniques au sein de la build (surtout avec Maven/Gradle). Les doublons cassent la build.
Erreur n° 5 : Structure de répertoires incorrecte. Le fichier module-info.java doit se trouver à la racine de src du module concerné. Sinon, le compilateur ne trouvera pas le module.
Erreur n° 6 : module-path incorrect au lancement. Lors d’un lancement manuel, indiquez --module-path au lieu de --classpath, sinon vous obtiendrez «module not found».
GO TO FULL VERSION