Uno sguardo ad Apache Maven

Se c’è una cosa che ogni programmatore ama, più di ogni altra, è, ovviamente, scrivere codice. Utilizzare una tecnologia interessante nel proprio progetto. Risolvere elegantemente un problema.

Come purtroppo sappiamo, però, lo sviluppo di un’applicazione non si limita alla sola “fase divertente”, ma si compone di diversi passi, alcuni dei quali ripetitivi e piuttosto noiosi.

È in questi momenti che uno strumento come Apache Maven ci può essere davvero utile. Vediamo come.

Cos’è Maven?

Inutile negarlo, molti di noi (me compreso) avranno cominciato ad utilizzare Maven scambiandolo per un “semplice” dependency manager. Un modo facile per reperire velocemente le dipendenze richieste dal progetto.

In realtà Maven è molto più di questo.

Apache Maven

Nato nel 2004, sulla scia di progetti più stagionati come Ant e Ivy, Apache Maven è un ottimo strumento di build automation (o, volendo, di build management).

Oltre al già citato gestore di dipendenze (elemento centrale del tool), abbiamo la possibilità di gestire in maniera integrata diversi processi, tra cui:

  • compilazione del codice sorgente;
  • esecuzione dei test di unità (ad esempio, tramite JUnit);
  • generazione dei pacchetti, a partire dal codice binario;
  • deployment dell’applicazione in ambiente di test o di produzione.

Inoltre, tutte le funzionalità di Maven sono implementate sotto forma di plug-in: questo permette di personalizzare il processo di sviluppo e, all’occorrenza, estendere le funzionalità di base. Qui trovate la lista completa dei plug-in disponibili.

Perché utilizzare Maven?

Ora però, un dubbio che potrebbe sorgere è: cosa ci faccio con Maven quando ho già un IDE?

Domanda lecita.

Una risposta piuttosto chiara la troviamo su Stack Overflow (e dove, sennò?)

Le motivazioni per utilizzare Maven sono differenti. A mio parere, però, possiamo riassumerle in tre punti:

  • Introduce uniformità: Maven permette di uniformare il processo di “costruzione” (build) del nostro progetto. Indipendentemente dall’IDE che stiamo utilizzando o dalle configurazioni applicate allo stesso, la nostra applicazione verrà compilata, testata ed infine deployata esattamente nello stesso modo.

  • Utilizza convenzioni: alla base di Maven c’è un approccio convention over configuration. Possiamo dunque sfruttare una serie di configurazioni di default, a patto di rispettare determinare convenzioni definite dallo strumento. Queste convenzioni generano (di nuovo) uniformità e coerenza nella gestione dei progetti.

  • Gestisce le dipendenze: eh sì, torniamo al tema iniziale. Se avete lavorato su progetti Java di una certa complessità, sapete bene che recuperare tutte le dipendenze necessarie, gestendo nel contempo la compatibilità tra le differenti versioni, può diventare un vero inferno). Dunque, perché non semplificarci la vita?

Ma come fa Maven a fare tutto ciò?

La chiave di tutto sta nel POM.

Il Project Object Model

Il fulcro di un progetto Maven è il cosiddetto Project Object Model (o POM), ovvero il modello del progetto che raccoglie tutti i metadati necessari a Maven per gestire la build.

Il POM viene definito tramite il file pom.xml, presente nella root del progetto. Un esempio basilare è il seguente:

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.davioooh</groupId>
  <artifactId>example-app</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>Applicazione finta</name>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.0.2</version>
      </plugin>
    </plugins>
  </build>
</project>

All’interno del file è possibile distinguere differenti sezioni, alcune delle quali opzionali.

Gli elementi fondamentali, definiti coordinate del progetto, sono tre: groupId, artifactId e version. Essi identificano il progetto ed ogni sua versione.

Altri due elementi comunemente utilizzati in un progetto Maven sono: dependencies e build. Il primo, come prevedibile, permette di definire le dipendenze del progetto; il secondo consente di gestire aspetti legati alla generazione del codice binario e alla configurazione dei plug-in.

Per una descrizione completa e approfondita della struttura del POM e delle configurazioni disponibili vi rimando alla documentazione ufficiale.

Il Super POM

Se esaminiamo la struttura completa di un file pom.xml, ci rendiamo conto che gli elementi disponibili sono davvero tanti. Fortunatamente però, per la maggior parte di questi Maven fornisce dei valori di default.

Ogni POM infatti eredita i suoi valori predefiniti dal cosiddetto Super POM, un modello “padre” che definisce molte delle convenzioni e linee guida accennate precedentemente.

Una tra le tante, e forse una delle più utili, è la definizione di una struttura standard per un progetto Java. Il Super POM dichiara un’alberatura convenzionale da utilizzare per il nostro codice. Le directory principali sono:

  • src/main/java: per il codice sorgente del progetto;
  • src/main/resources: contenente le risorse statiche e le configurazioni;
  • src/test/java: per il codice sorgenti dei test d’unità.

La lista completa delle directory standard la trovate qui.

Repository centrale e repository locale

Come già detto, Maven integra un’utile gestore di dipendenze.

L’elemento dependencies definito nel pom.xml include la lista dei pacchetti richiesti dal nostro progetto. Ma dove sono i pacchetti?

Banalmente, in un repository. Più precisamente, Maven si basa su due tipologie di repository: remoti e locali.

Un repository locale viene creato nella propria macchina nel momento in cui installiamo Maven. Ogni volta che lanciamo la compilazione di un progetto, Maven legge dal pom.xml le dipendenze dichiarate e cerca di recuperarle prima di tutto dal repository locale.

Se i jar richiesti non sono presenti localmente, Maven si collega al repository centrale (http://repo.maven.apache.org per gli USA e http://uk.maven.org/ in UK), e scarica i pacchetti nel repository locale.

Come avrete capito, il repository locale è una sorta di cache che replica solo le librerie richieste (direttamente o indirettamente) dai nostri progetti. Questo ci consente di lavorare offline fin tanto che non decidiamo di aggiungere una nuova dipendenza alla nostra applicazione.

Vista la mole di pacchetti messa a disposizione, navigare nei repository alla ricerca della dipendenza desiderata è impensabile. Esistono quindi dei servizi di ricerca che consentono di trovare rapidamente quel che ci serve. Ne segnalo due: The Central Repository e MVN Repository.

In conclusione

Pur non essendo un fan sfegatato degli strumenti di build management, ritengo importante avere almeno una conoscenza generare di tool quali Maven o Gradle che, sempre più, vengono utilizzati anche contestualmente a framework piuttosto diffusi.

L’obiettivo di questo articolo, ovviamente, non era quello di trattare in maniera approfondita l’utilizzo di uno strumento complesso come Maven. Spero però di aver fornito sufficienti elementi e riferimenti per crearvi un’idea piuttosto completa di come Maven possa essere applicato ad un progetto Java.

Come al solito, nei commenti, raccontatemi le vostre esperienze e fatemi sapere cosa ne pensate.

Alla prossima,

David