Il mondo Java, maturo già da anni per lo sviluppo di applicazioni enterprise e non, si avvicina a uno standard di programmazione modulare offerto dalla tecnologia OSGi. Si vedrà come, grazie ai framework realizzati tramite specifiche OSGi si arrivi ad una gestione del software altamente dinamica e modulare, con le basi per un miglioramento della manutenzione ed estensibilità del software.
OSGi(TM) o anche The Dynamic Module System for JavaTM è una specifica per la definizione di un framework per la gestione del modello del ciclo di vita del software. Definisce un insieme di standard con il quale si possa facilmente sviluppare software Java in modo modulare, dinamico e facile da mantenere. Tutti questi aspetti verranno trattati in questo primo articolo sulla tecnologia.
Il tutto è frutto di OSGi Alliance [1] (Open Service Gateway initiative), organizzazione fondata nel 1999 da IBM, Oracle, Ericsson e altri colossi del settore IT. L’idea nasce dal fatto di voler distribuire e amministrare dei servizi in rete locale su dispositivi di diversa natura.
La tecnologia OSGi si riferisce quindi ad un framework per sviluppare sistemi Java all’interno di un’architettura modulare orientata ai servizi.
Modularizzazione
Il concetto di modularizzazione risolve diversi problemi ben noti in tutti gli ambienti ingegneristici. Rendere un qualsiasi processo/prodotto una composizione di moduli ha degli importanti vantaggi come la diversificazione dei ruoli tra i moduli stessi, l’astrazione del modulo identificandone solo il compito che espleta e non la sua implementazione, il riutilizzo del modulo in diversi contesti, la facilità di manutenzione.
Per beneficiare delle caratteristiche dei moduli in ambiente software si deve quindi pensare a dei componenti che siano auto-contenuti, altamente specializzati e debolmente accoppiati.
Come si deduce anche dalle fonti pubbliche di OSGi, il framework si ripromette di risolvere delle problematiche intrinseche di Java alle quali la maggioranza degli sviluppatori si sono abituati nel tempo, non considerandole più come fattori di rallentamento della produttività.
I problemi di cui si sta parlando sono dovuti principalmente al sistema di class loading di Java e della modalità di distribuzione e deploy dei pacchetti, i famosissimi Java ARchives o più comunemente JAR.
I files JAR costituiscono lo standard per la distribuzione e il deploy di applicazioni in ambiente Java. Tutte le applicazioni Java sono infatti distribuite come insieme di JAR impacchettati opportunamente in diversi modi (EAR, WAR, …). Ogni JAR contiene parte delle funzionalità dell’applicazione monolitica che rappresenta. I problemi nascono in fase di sviluppo e manutenzione.
In un programma Java, i JAR sono considerati come una lista di classi globale, il famoso classpath. Le classi necessarie quindi sono considerate solo a deploy-time e build-time; non esiste un concetto di JAR a run-time.
Un’altra mancanza della gestione dei JAR è che essi non hanno uno standard per specificare le interdipendenze tra di loro. Alcuni JAR sono auto contenuti, nel senso che non dipendono da altre classi archiviate in altri JAR. Altri però usano funzionalità offerte da altre librerie. Le interdipendenze in questo caso non sono gestite direttamente dal linguaggio, quindi lo sviluppatore deve affidarsi alla documentazione a corredo delle librerie che sta utilizzando. Nel caso in cui il programmatore non includesse le librerie necessarie al corretto funzionamento di quelle di cui già dispone, sarebbe costretto a risolvere un problema di ClassNotFoundException.
Andando avanti con l’analisi delle problematiche di Java e della gestione delle librerie, si può notare come non esista un vero e proprio versionamento implicito dei JAR. Si supponga di avere una interdipendenza di librerie, la libreria A dipende dalla B. A questo punto bisogna scoprire quale versione di B è quella giusta, facendo ancora una volta affidamento alla documentazione, non essendo disponibile un’informazione di questo tipo nell’archivio stesso.
Un’altra situazione spiacevole è quella che si presenterebbe nel caso in cui A dipendesse da B in versione 1.0 e C dipendesse da B in versione 1.1. In questo caso si ha la necessità della stessa libreria ma in versioni diverse. Questo in un ambiente Java convenzionale non è possibile.
Infine, in Java manca un meccanismo per permettere il mascheramento di informazioni tra archivi JAR. I modificatori di accesso come public, protected, private, default si riferiscono alla visibilità tra packages e non tra JAR. Spesso capita che classi facenti parte di JAR abbiano bisogno di avere accesso a classi di altri package dello stesso JAR. Questo significa che lo sviluppatore è costretto a usare il modificatore public perdendo il controllo sul mascheramento dei dati.
Dopo questa analisi si può affermare che i JAR, così come intesi dalla specifica JAVA, non possono essere considerati dei veri e propri moduli in quanto sono fortemente accoppiati e difficilmente indipendenti tra di loro.
Il concetto di “bundle”
La tecnologia OSGi viene in aiuto degli sviluppatori in questo senso, rendendo un archivio JAR un componente indipendente con tutte le caratteristiche di modularità menzionate prima. Il concetto che racchiude le caratteristiche di modulo per OSGi è il bundle.
Esso non è altro che un archivio JAR corredato di MANIFEST.MF file, come da specifica Java, che provvede, oltre alle informazioni standard previste dal linguaggio, informazioni utili al framework OSGi per far si che l’archivio diventi un bundle e possa essere trattato come tale all’interno di un ambiente OSGi.
È facile intuire che qualsiasi JAR può diventare un bundle (con gli opportuni accorgimenti) e che un bundle può essere utilizzato anche come semplice JAR, in questo caso la JVM ignorerà le informazioni eventualmente elencate nel file MANIFEST.MF.
Punto chiave di questa prima fase di configurazione è il fatto che nel file si possano specificare due importanti parametri: Import-Package ed Export-Package. Anche in questo caso è facile intuire la funzionalità di entrambi i parametri: il primo fornirà al framework l’informazione su quali package avrà bisogno di importare il modulo per far sì che funzioni correttamente, il secondo invece specificherà quali package il modulo renderà visibili all’esterno.
Questo punto è molto importante e risolve i problemi di interdipendenza tra i moduli. Il framework OSGi conosce a priori quali moduli dovranno essere disponibili per il funzionamento di altri, dando subito informazioni di dipendenza ed evitando i classici problemi di class-loading possibili solo a build-time e deploy-time
Il framework permette anche di aggiungere criteri di visibilità all’esterno dei JAR. Come noto, se si vuole rendere visibile una classe Java tra diversi package e tra diversi JAR, bisogna interpellare il classico modificatore di accesso public obbligando lo sviluppatore ad usarlo anche quando non volesse rendere pubblico l’accesso alle classi in questione. Grazie al parametro Export-Package si aggiunge quindi un altro criterio di accesso al codice Java, reso possibile grazie al fatto che ogni bundle dispone di un class loader indipendente.
Un altro parametro chiave è il Bundle-Version. Esso permette di far convivere diverse versioni dello stesso JAR all’interno della stessa JVM. Un bundle ha la possibilità di specificare la versione della libreria richiesta nelle sue informazioni di importazione, sarà il framework quindi a gestire e risolvere le dipendenze a livello di package, ragionando anche per intervalli di versione e non vincolando il funzionamento del bundle ad una versione specifica del JAR in questione.
Da queste ultime battute si delinea la funzione del framework in relazione alla gestione dei bundle.
Il framework infatti è responsabile del ciclo di vita dei componenti fornendo funzionalità per installare, avviare, arrestare, disinstallare i bundles. La potenza della specifica permette tutte queste operazioni “a caldo” senza aver bisogno di arrestare e riavviare la JVM. Il tutto diventa estremamente dinamico e flessibile, permettendo allo sviluppatore di isolare gli eventuali problemi.
Finora si è visto il concetto di bundle, il quale rappresenta il componente principale (e per definizione) della tecnologia OSGi, e di come esso, per struttura e implementazione, ne permetta un funzionamento modulare.
Piattaforma per la gestione dei servizi
OSGi offre un’infrastruttura orientata ai servizi: si potrebbe definire come Service Oriented Architecture all’interno di una JVM. Questo approccio tratta l’argomento fornendo un registro di servizi (service registry) evoluto all’interno del framework.
I bundle espongono servizi identificati da un’interfaccia ed è possibile registrarli nel service registry. I servizi a loro volta possono usare altri servizi in modo da essere accoppiati nell’espletare la logica di business che li riguarda. In questo caso, mantenendo una forte dinamicità, i servizi si adattano automaticamente a quelli dai quali dipendono.
Un bundle può, per esempio, chiedere al registro un tipo di servizio (l’interfaccia che lo implementa) e quindi usarlo. Se il servizio non è disponibile, il bundle può aspettare fino a quando esso non si rende disponibile.
Può succedere anche che più servizi implementino la stessa interfaccia; in questo caso, grazie ad un sistema di proprietà registrate con il servizio stesso, si preserva la corretta funzionalità in modo da non creare equivoci nell’invocazione.
I servizi sono anche dinamici, nel senso che possono essere disinstallati anche mentre un bundle li sta utilizzando. I bundle che li usano, in questo caso, dovranno assicurarsi di non poter usare più tali servizi e chiudere i loro riferimenti al mondo esterno, senza ovviamente intaccare l’intero funzionamento dell’applicazione.
Le specifiche OSGi forniscono anche un compendio nel quale si elencano i servizi standard che dovrebbero essere disponibili all’interno del framework.
Architettura
Per completezza si fa riferimento anche all’architettura della specifica OSGi [2] il quale si identifica in un modello a strati che di seguito identifichiamo:
- Execution Evironment: definisce quali metodi e classi sono disponibili in una specifica piattaforma;
- Modules: definisce come un modulo può importare o esportare codice;
- Life Cycle: gestisce il ciclo di vita di un bundle con funzionalità di installazione/disinstallazione, avvio/arresto, aggiornamento dei bundle stessi;
- Services: gestisce la connessione dinamica dei bundle orientata ai servizi;
- Bundles: componenti OSGi, i mattoni della tecnologia;
- Security: gestione della sicurezza all’interno del framework.
Maturità della tecnologia e sue implementazioni
La specifica OSGi è ormai una realtà assodata e collaudata. Presente già da un decennio, prende in consegna un vasto numero di implementazioni, sia proprietarie che open source. In settembre 2009 è stata rilasciata la release R4 versione 2.
Un’implementazione della specifica molto significativa è Equinox [3]. Essa è usata in maniera massiva per lo sviluppo di Eclipse [4] dalla versione 3.0 in poi. Equinox ha permesso a Eclipse di diventare un vasto contenitore di plug-in con la possibilità di caricare i moduli dinamicamente all’avvio dell’IDE o quando se ne ha bisogno. Lo sviluppo dei plug-in, che altro non sono che bundles OSGi, permette altresì un’estensione esponenziale di Eclipse grazie alla comunità open-source che lo supporta e la loro facilità di integrarsi nell’ambiente grazie appunto alla tecnologia OSGi.
Un altro esempio di implementazione della specifica è quella di Apache chiamata Felix [5], usata come piattaforma di sviluppo nella più recente, ad oggi, versione del famoso application server della Sun Microsystems: Glassfish V3 [6].
Altri esempi di utilizzo o di integrazione di OSGi si possono trovare molto spesso nelle ultime versioni dei più utilizzati framework e middleware, come Spring, JBoss etc. La programmazione modulare e i problemi che risolve OSGi non sono trascurabili per la maggior parte degli sviluppatori, quindi si prevede che, in un prossimo futuro, molte tecnologie già esistenti migreranno a questo stile di programmazione a componenti, facile da mantenere ed estendere nel tempo.
Conclusioni
La specifica OSGi, come si è visto, pone un punto fermo sulla programmazione modulare in Java. Grazie ai framework che implementano le direttive OSGi si può raggiungere facilmente un ambiente dinamico a componenti, con la possibilità di gestione del ciclo di vita dei cosiddetti bundles permettendone l’attivazione, disattivazione, installazione, disinstallazione on-demand senza pregiudicare il corretto funzionamento dell’applicazione di cui fanno parte e senza, soprattutto, il necessario riavvio della JVM su cui stanno girando.
Tutto questo per venir meno a stili di programmazione Java standard che mettono in conto, implicitamente, problemi di class loading a deploy-time e build-time a cui lo sviluppatore è abituato.
OSGi e lo sviluppo per così dire “a bundle” si sta affermando pian piano come stile di programmazione anche negli ambienti enterprise dove il dinamismo offerto dai bundles e dai servizi offerti da essi trova il suo ambiente naturale.
Riferimenti
[1] OSGi Alliance
http://www.osgi.org
[2] OSGi Architecture
http://www.osgi.org/About/WhatIsOSGi
[3] Equinox
http://eclipse.org/equinox/
[4] Eclipse
http://eclipse.org
[5] Apache Felix
http://felix.apache.org/site/index.html
[6] Glassfish Community
https://glassfish.dev.java.net/