Introduzione

In questo articolo si prosegue con la presentazione dei componenti principali di Maven. In particolare l'attenzione è focalizzata sul repository server (più semplicemente repository). Si tratta di una sorta di file system strutturato disegnato per memorizzare i manufatti dei progetti. Questi non sono altro che le librerie utilizzate (file JAR), i "distribuibili" prodotti (JAR, WAR, EAR), plug-in vari, e così via. Come illustrato nei primi articoli (cfr. [8]), ogni manufatto è univocamente identificato dalle proprie coordinate ottenute per mezzo della tripla:

  • group id
  • artifact id
  • numero di versione

Sempre negli articoli precedenti, abbiamo anticipato come Maven preveda due tipologie di repository:

  • locali
  • remoti

I repository appartenenti alla prima categoria sono copie installate tipicamente negli dischi rigidi dei vari computer e svolgono il duplice compito di funzionare da cache, riducendo il numero di download remoti, e di memorizzare e rendere disponibili i manufatti temporanei risultato del processo di build (file non ancora distribuiti in versione stabile, ossia gli snapshot). I repository remoti, invece, sono nella maggior parte dei casi repository esterni alla propria organizzazione e quindi gestiti da terze parti. Si ricordi che Maven permette di definire in due file la lista dei repository che si desidera utilizzare:

  • POM: In questo caso è necessario impostare le sezioni <repositories> <repository>... </repositories> </repository>. Poiché è una sezione ereditabile, è possibile limitarsi a impostarla nel POM genitore e quindi ereditarla automaticamente in tutti i POM figli. Da notare che i repository di default sono già presenti nel POM standard di maven: il famoso super POM.
  • settings: In particolare, è necessario impostare l'apposita lista nel file dei settings memorizzato localmente (~/.m2/settings.xml; in macchine Windows, un tipico path è C:\Documents and Settings\<login id>\.m2).

Configurazione del proxy

Se si deve accedere ai repository remoti attraverso un proxy, è necessario configurarlo nel file settings.xml:

<proxies>
  <proxy>
    <active>true</active>
    <protocol>http</protocol>
    <host>proxy.somewhere.com</host>
    <port>8080</port>
    <username>proxyuser</username>
    <password>somepassword</password>
    <nonProxyHosts>www.google.com|*.somewhere.com</nonProxyHosts>
  </proxy>
</proxies>

Questo proxy può essere utilizzato per scaricare manufatti proprietari mantenuti nelle aziende proprietarie (questi non possono essere memorizzati nei repository centrali sempre per questioni di licenze). Da notare che qualora il proxy richieda le credenziali minime (user e password) queste vanno definite (in chiaro!) nel file delle impostazioni ($user.home/.m2/settings.xml), soluzione che probabilmente non è la più sicura in assoluto.

Il flusso tipico dal globale via via verso il locale è invertito qualora si desideri pubblicare i vari manufatti. Lo scenario tipico consiste nell'avere i manufatti prodotti da uno specifico progetto pubblicati a ritroso fino a raggiungere il repository del progetto o anche quello centrale dell'azienda. Qualora invece si dovesse lavorare in progetti open source non sarebbe infrequente che il flusso a ritroso giunga fino alla pubblicazione sul repository centrale globale. Come riportato negli articoli precedenti, la pubblicazione dei vari manufatti avviene attraverso il plug-in deploy che si occupa, tra l'altro di generare i vari file di checksum.

A questo punto dovrebbe essere chiaro che uno dei grandi vantaggi generati dal meccanismo dei repository consiste nell'avere una singola copia dei vari manufatti, indipendentemente da quanti progetti e sotto-progetti li utilizzino. Questo, tra l'altro, permette di eliminare tutta una serie di problemi legati alla garanzia di consistenza, integrità, etc.

Perché il repository è un concetto importante

Al fine di comprendere appieno l'importanza dei repository, è necessario far mente locale alla situazione tipica degli ambienti che non utilizzano Maven. In questi casi, il processo standard di condivisione dei manufatti tra i diversi team di sviluppo prevede, nella migliore delle ipotesi, la relativa memorizzazione nel SCM (Software Configuration Management, gestione della configurazione del software, come CVS, Subversion, IBM Rational ClearCase, etc) del progetto, mentre, nei casi più drammatici, è addirittura possibile assistere a un continuo scambio di file JAR via e-mail!

In ogni modo, questa pratica presenta una serie di problemi. Il primo è di ordine concettuale: i manufatti risultanti del processo di compilazione, in quanto sempre riproducibili, non dovrebbero essere memorizzati nel SCM. Oltre a questo problema strettamente di carattere filosofico, ne sono presenti altri decisamente più pratici. Per esempio, la continua memorizzazione dei manufatti nel SCM può dar luogo a una notevole occupazione di spazio su disco.

Poi vi è il problema della versione. Qualora non presente, è naturale avere a che fare con tutta una serie di problemi di allineamento, di controllo della sincronizzazione, etc. A tal proposito, vi dice nulla il seguente scambio di battute?

  • Sviluppatore Uno: "La libreria che avete rilasciato non funziona!"
  • Sviluppatore Due: "Quale versione stai utilizzando?"
  • Sviluppatore Uno: "Come si fa vedere la versione?"

E via discorrendo...

Qualora invece la versione sia gestita, potrebbe rendersi necessario un continuo aggiornamento delle dipendenze del proprio progetto (è il caso in cui al nome dei vari JAR sia aggiunta l'indicazione della versione) oppure si potrebbe ricadere in un caso simile alla mancanza di gestione della versione, qualora questa sia memorizzata all'interno dei vari file. Inoltre, spesso, la condivisione dei file richiede tutta una serie di passi manuali. Infine, la presenza del repository rende il processo di check out del progetto più veloce: non è necessario eseguire il check-out di file binari di grandi dimensioni. Questi sono scaricati, secondo un meccanismo di lazy loading, una volta soltanto attraverso il repository.

Struttura

Giacché il local repository non è altro che una sezione del file system (e quindi una struttura gerarchica di file e directory) organizzata secondo le direttive di Maven, è possibile ispezionarla con qualsiasi tool di esplorazione file, come il Windows Explorer. In figura 2 è riportata una classica struttura di un repository locale.

Figura 2 - Esempio di repository locale

Da un'attenta analisi di questa struttura risulta difficile comprendere il criterio alla base dell'organizzazione di tale repository... Questo è dovuto al fatto che si è passati da un'organizzazione iniziale (quella di Maven 1.x) spesso denominata legacy, a una più razionale e scalabile (quella di Maven 2); per mantenere la retrocompatibilità, principio sacro per gli ambienti Java, si è deciso di lasciare convivere le due organizzazioni.

Un altro elemento da notare è la presenza di diversi file. In particolare, è presente il file POM, i file relativi al controllo di integrità (checksum hash) relativi sia ai file POM sia ai vari manufatti, etc. Questi servono per verificare/assicurare l'integrità dei vari manufatti dopo il loro trasferimento tra i vari repository (il servizio che si occupa di ciò è denominato "dependency management engine"). In particolare, e molto ma molto brevemente, dato un file o un qualsiasi testo, il processo di hashing si occupa di generare una cifratura di lunghezza molto inferiore a quella del file iniziale, legata al contenuto del file stesso. Pertanto, avendo a disposizione il file originario è sempre possibile eseguire l'algoritmo di hashing e quindi confrontare la cifratura ottenuta con quella originaria. Se le due differiscono, allora si è verificato un errore. Ma può anche accadere di trovarsi nella situazione in cui il file di controllo sia corrotto. Nella nuova struttura di Maven, questi file (JAR, POM) vengono definiti "primary" per distinguerli dai "secondary" come JavaDoc, file di testo, etc., memorizzati in una struttura a parte.

La nuova struttura di Maven (dalla versione 2) non dovrebbe presentare troppe sorprese al pubblico di programmatori Java, poiché ci sono grosse similitudini con la convenzione utilizzata per i package. In particolare, la gerarchia del repository centrale prevede:

  • groupId: per esempio: org/hibernate
  • tipo di file (jars, javasource, javadoc, licenses, poms): per esempio: org/hibernate/jars
  • artifactId: per esempio: org/hibernate/jars/hibernate
  • versione: per esempio: org/hibernate/jars/hibernate-3.1.2.jar

Da notare che non tutti i repository rispettano esattamente la stessa struttura. Come già detto, il problema è che attualmente vi è una mescolanza di stili. Questo genera una serie di problemi; per esempio è possibile notare come la struttura dei manufatti Hibernate vari a seconda della release:

  • groupId=hibernate (Hibernate 1-3)
  • groupId=net.sf.hibernate (Hibernate 1-2)
  • groupId=org.hibernate (Hibernate 3)

Problemi del layout legacy

Il layout legacy di Maven 1.x presentava diversi problemi:

  • Ridotta scalabilità: Data l'organizzazione del file, era molto frequente il caso che diversi manufatti andassero a "bloccare" parti comuni. Questo è diventato evidentissimo dall'analisi dei flussi sul repository di iBiblio. In effetti, durante gli orari di maggiore uso, il repository diventava (e ahimè diviene ancora) praticamente inutilizzabile.
  • Layout meno razionale: La struttura portava ad avere moltissimi manufatti memorizzati nella stessa directory, creando problemi di fruizione.
  • Mancanza di distinzione: tra manufatti primari (jar e pom) e secondari (javadoc, testi, etc.)

Conclusioni

In questo articolo abbiamo analizzato i repository server di Maven, un componente fondamentale per la gestione delle dipendenze e la condivisione dei manufatti. Abbiamo visto come i repository permettano di centralizzare la gestione delle librerie, migliorare la consistenza del processo di build e semplificare la collaborazione tra team di sviluppo. Nei prossimi articoli approfondiremo altri aspetti avanzati di Maven.