CodeGym /Cours /JAVA 25 SELF /Découpage du projet en modules : bonnes pratiques

Découpage du projet en modules : bonnes pratiques

JAVA 25 SELF
Niveau 60 , Leçon 3
Disponible

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».

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION