CodeGym /Corsi /JAVA 25 SELF /Suddivisione del progetto in moduli: best practice

Suddivisione del progetto in moduli: best practice

JAVA 25 SELF
Livello 60, Lezione 3
Disponibile

1. Quando e perché suddividere il progetto in moduli

Perché non conviene fare tutto in un unico modulo

Se state scrivendo un piccolo laboratorio o "Hello, World!", il sistema modulare può sembrare eccessivo. Ma con la crescita del progetto — decine e centinaia di classi, molti package, librerie di terze parti — il caos diventa inevitabile. È come una biblioteca senza scaffali: finché i libri sono pochi — va bene, ma poi diventa difficile trovare qualcosa. I moduli sono i vostri scaffali: aiutano a mettere ordine e nascondono la «cucina» (l’implementazione), lasciando all’esterno solo la «vetrina» (l’API).

Perché suddividere in moduli

  • Separazione delle responsabilità: ogni modulo è responsabile del proprio ambito (per esempio, DB, logica di business, UI).
  • Riutilizzo del codice: un modulo può essere collegato a un altro progetto.
  • Migliore testabilità: i moduli si testano in modo indipendente.
  • Sicurezza e incapsulamento: verso l’esterno è visibile solo l’API, l’implementazione è nascosta.
  • Semplificazione della manutenzione: meno collegamenti «magici», mappa delle dipendenze chiara.
  • Build e deploy più rapidi: si ricompilano solo i moduli modificati.

Quando suddividere in moduli

  • Il progetto diventa troppo grande per un solo sviluppatore (oppure l’IDE inizia a «rallentare»).
  • Si delineano chiaramente le parti: core, ui, utils, api, impl.
  • Si prevede di riutilizzare il codice in altri progetti.
  • Ci sono dipendenze esterne necessarie solo a una parte del progetto.
  • Occorre nascondere i dettagli di implementazione (algoritmi, classi interne).

2. Schemi tipici di modularità

Di seguito — schemi di suddivisione popolari, adatti sia a progetti didattici sia a quelli reali.

Architettura «a cipolla» (Onion Architecture)

Lo strato esterno dipende da quello interno, ma non viceversa.

[ app (UI) ]
     ↓
[ core (logica) ]
     ↓
[ utils (utility) ]
  • app — modulo esterno: interfaccia grafica, applicazione web, punto d’ingresso (main).
  • core — logica di business, modelli, servizi.
  • utils — classi di supporto.

Regola: lo strato interno non deve dipendere da quello esterno. Così core può essere riutilizzato in interfacce diverse (console, web, desktop).

Moduli per API e implementazione

Per le librerie è comodo separare le interfacce dalla loro implementazione:

[ mylib.api ]  ← esporta solo le interfacce
[ mylib.impl ] ← contiene le implementazioni, non viene esportato

Moduli per i test

Spesso i test vengono spostati in un modulo separato, per non finire nell’artefatto di produzione.

[ app ]
[ core ]
[ core.tests ]

Schema per un progetto didattico

myeditor/
  ├─ app/         ← punto d’ingresso, avvio dell’applicazione
  ├─ core/        ← logica di business (lavoro con file e testo)
  └─ utils/       ← utility (logging, parsing)

3. Dipendenze tra moduli

Nei moduli Java le dipendenze sono indicate esplicitamente in module-info.java con la parola chiave requires. Questo aumenta la leggibilità e permette al compilatore/JVM di controllare l’accessibilità dell’API tramite exports.

Esempio di dipendenza

core/module-info.java

module myeditor.core {
    exports myeditor.core.api; // è visibile all'esterno solo il pacchetto api
    requires myeditor.utils;   // usiamo le utility
}

app/module-info.java

module myeditor.app {
    requires myeditor.core;    // usiamo core
    requires myeditor.utils;   // possiamo usare le utility direttamente
}

Regole e best practice

  • Evitate le dipendenze cicliche. Se A requires B e B requires A — è un difetto di design. Di solito si risolve estraendo un common/api comune.
  • Minimizzate le dipendenze. Non aggiungete un modulo se non è realmente necessario.
  • Esportate i pacchetti utilizzati. Le classi devono trovarsi nei pacchetti dichiarati tramite exports, altrimenti si avrà un errore di compilazione.
  • Le utility — il più indipendenti possibile. utils non deve dipendere dalla logica di business.

4. Pratica: esempio di suddivisione di un progetto didattico in 3 moduli

Struttura delle cartelle

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

Esempi di 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;
}

Esempio di codice (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!"));
    }
}

Come appare in IntelliJ IDEA

  • Ogni cartella è un Module separato nella struttura del progetto.
  • Ogni modulo ha il proprio module-info.java alla radice di src.
  • Avviando main da app l’IDE imposterà automaticamente il module-path.
  • Il tentativo di usare una classe da un pacchetto non esportato si concluderà con un errore di compilazione.

5. Impatto sulla build: Maven/Gradle e moduli

Maven

Un progetto multi-modulo è un progetto «genitore» (parent) e diversi moduli «figli».

myeditor/
  ├─ pom.xml        ← parent
  ├─ app/
  │    └─ pom.xml
  ├─ core/
  │    └─ pom.xml
  └─ utils/
       └─ pom.xml

Esempio di 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>

Particolarità:

  • Maven tiene conto di module-info.java in fase di compilazione.
  • Per l’esecuzione si usa --module-path invece di --classpath.
  • Se dimenticate exports o requires — otterrete un errore di compilazione.

Gradle

La multi-modularità si configura tramite settings.gradle e build.gradle separati per i moduli.

settings.gradle

rootProject.name = 'myeditor'
include 'app', 'core', 'utils'

build.gradle per un modulo

plugins {
    id 'java'
}

java {
    modularity.inferModulePath = true
}

IntelliJ IDEA

  • IDEA può creare module-info.java durante la creazione di un modulo Java.
  • Con Maven/Gradle, la struttura dei moduli viene rilevata automaticamente.
  • Avviando main da app, l’IDE configurerà il module-path.
  • Le finestre di dialogo di import/export suggeriscono la visibilità di pacchetti e moduli.

Errori tipici nella suddivisione in moduli

Errore n. 1: dipendenze cicliche tra moduli. Se due moduli dichiarano requires l’uno dell’altro, il compilatore darà un errore. Di solito è il segno di un’architettura deteriorata. Soluzione — estrarre un modulo api comune oppure rivedere i confini.

Errore n. 2: utilizzo di classi da pacchetti non esportati. Una classe può essere public, ma se il pacchetto non è indicato in exports in module-info.java, un altro modulo non la vedrà. Risultato — errore di compilazione.

Errore n. 3: dimenticato di aggiungere requires per il modulo usato. Un import da un altro modulo senza la voce corrispondente in module-info.java non verrà compilato. Dichiarate sempre esplicitamente le dipendenze.

Errore n. 4: duplicazione dei nomi dei moduli. I nomi dei moduli devono essere univoci all’interno della build (soprattutto con Maven/Gradle). I duplicati rompono la build.

Errore n. 5: struttura delle cartelle errata. Il file module-info.java deve trovarsi nella radice di src del relativo modulo. Altrimenti il compilatore non troverà il modulo.

Errore n. 6: module-path errato in fase di esecuzione. Nel lancio manuale indicate --module-path invece di --classpath, altrimenti otterrete «module not found».

1
Compito
JAVA 25 SELF, livello 60, lezione 3
Bloccato
Apertura di un canale per il registro degli eventi 📢
Apertura di un canale per il registro degli eventi 📢
1
Compito
JAVA 25 SELF, livello 60, lezione 3
Bloccato
Architetto della rubrica digitale 🗺️
Architetto della rubrica digitale 🗺️
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION