Miriade utilizza con successo Liferay da diversi anni, in vari scenari e per diversi clienti, sia come soluzione di portale istituzionale sia come strumento di lavoro interno.
Liferay è una piattaforma software open source basata su Java, progettata per la creazione di portali web aziendali e applicazioni digitali personalizzabili. È utilizzata principalmente per sviluppare intranet, extranet, siti pubblici e soluzioni di digital experience.
Originariamente noto come un portal framework, permette di integrare contenuti, servizi e applicazioni in un unico ambiente centralizzato. La sua architettura modulare consente di aggiungere funzionalità tramite plugin o moduli OSGi.
Le funzionalità principali includono gestione dei contenuti (CMS), gestione utenti e permessi avanzati, workflow, motore di ricerca interno, supporto per multilingua e responsive design. Liferay supporta anche l’integrazione con sistemi esterni (CRM, ERP, LDAP) e fornisce API REST e GraphQL per lo sviluppo headless.
Viene distribuito in due versioni:
- Liferay CE (Community Edition): gratuita e open source
- Liferay DXP (Digital Experience Platform): a pagamento, con supporto commerciale e funzionalità enterprise avanzate.
Il Contesto
Uno dei punti di forza di Liferay è la disponibilità di una SDK che permette di:
- sviluppare moduli custom, sia di back end sia di front end;
- sviluppare personalizzazioni di moduli o servizi core;
- sviluppare estensioni per alcuni dei suoi framework, come ad esempio:
- modalità e protocolli di autenticazione;
- modalità e protocolli di pagamento (per la parte commerce);
- tipologie di repository documentali;
- altro;
- sviluppare temi e layout grafici custom.
Ogni modulo che implementi logiche di back end, cioè tutti quei moduli nei quali sono presenti classi Java, deve poter dichiarare quali sono le sue dipendenze rispetto a librerie terze, come qualsiasi altro progetto Java.
Liferay ha scelto di utilizzare Gradle come strumento principale di automazione della compilazione. In altre parole, Gradle viene usato per eseguire automaticamente tutte le attività coinvolte nella compilazione del progetto (come trasformare il codice sorgente in file eseguibili) e nel suo impacchettamento (cioè preparare il progetto finito in un formato pronto per essere distribuito o installato). In particolare, Gradle è utilizzato anche per la gestione delle dipendenze, cioè per gestire automaticamente le librerie e i pacchetti esterni di cui il progetto ha bisogno per funzionare correttamente, evitando che il programmatore debba installarli o configurarli manualmente. (Fonte: https://it.wikipedia.org/wiki/Gradle)
Liferay ha, inoltre, creato dei plugin specifici per Gradle. Questi plugin sono degli estensioni o componenti aggiuntivi che servono a rendere più semplice e uniforme il processo di gestione e compilazione della maggior parte dei diversi tipi di moduli software usati in Liferay. Servono ad automatizzare e standardizzare molte operazioni per evitare che ogni sviluppatore debba configurare tutto da zero.
Sebbene molti sviluppatori Java siano abituati a usare Gradle ordinariamente, questa modalità standard può, però, essere complicata dall’alto grado di modularità di Liferay, che si basa su OSGi (Open Service Gateway initiative).
OSGi (Open Service Gateway Initiative) è un framework di modularità (per Java) che permette di creare applicazioni dinamiche e modulari. Il sistema è basato sul concetto di "bundle", che sono moduli JAR estesi contenenti codice Java, risorse e metadati descrittivi. Ogni bundle può essere installato, avviato, fermato e disinstallato indipendentemente durante l'esecuzione dell'applicazione, senza necessità di riavvio del sistema.
OSGi gestisce le dipendenze tra i moduli attraverso un sistema di versioning sofisticato e controlli di visibilità, permettendo a diverse versioni della stessa libreria di coesistere. Il framework fornisce un registro di servizi che consente ai bundle di pubblicare, scoprire e utilizzare servizi in modo dinamico.
Lo strumento è particolarmente utile in ambienti enterprise dove la modularità, l'isolamento e la capacità di aggiornamento a caldo sono cruciali. Esempi di implementazioni includono Apache Felix, Eclipse Equinox e Knopflerfish.
OSGi promuove le best practice di sviluppo software come l'incapsulamento, la separazione delle responsabilità e l'inversione delle dipendenze. Rappresenta pertanto una soluzione matura per gestire la complessità delle applicazioni Java enterprise moderne.
Il Problema
In questo contesto non è sempre facile capire (e gestire) cosa succede durante la fase di build, in cui le dipendenze compile di Java vengono gestite abbastanza agilmente da gradle, e quello che invece succede durante le fasi di pacchettizzazione o di start dei bundle.
In queste due fasi il compilatore prima e il portale poi devono essere in grado di avere tutte le dipendenze necessarie per potere eseguire il codice del modulo a cui stiamo lavorando.
La Soluzione
La gestione delle dipendenze durante le fasi di build, pacchettizzazione e avvio dei bundle in Liferay può risultare complessa a causa della natura modulare dell’ecosistema basato su OSGi. Per garantire che ogni modulo disponga delle librerie e risorse necessarie sia in fase di compilazione che di esecuzione, è fondamentale adottare un approccio strutturato nella dichiarazione e risoluzione delle dipendenze. In particolare, la soluzione si articola in due aree principali: la gestione delle dipendenze tra moduli OSGi interni e quella verso librerie di terze parti esterne al portale.
1. Dipendenze tra moduli OSGi
Per far sì che un modulo A possa usare classi pubbliche di un modulo B in Liferay, è necessario:
- Esportare i pacchetti nel modulo B tramite la proprietà
Export-Package
nel bnd.bnd. - Importare i pacchetti nel modulo A tramite
Import-Package
o lasciando che il sistema OSGi li risolva automaticamente in base all'uso. - Dichiarare la dipendenza nel file
build.gradle
concompileOnly project(':modules:modulo-B')
, assicurandosi che il modulo dipendente sia visibile a livello di build.- In alcuni casi, soprattutto nei casi di service builder o moduli che espongono interfacce, potrebbe essere sufficiente dichiarare la dipendenza con api anzichè usare
compileOnly
.
- In alcuni casi, soprattutto nei casi di service builder o moduli che espongono interfacce, potrebbe essere sufficiente dichiarare la dipendenza con api anzichè usare
2. Dipendenze verso librerie di terze parti
Quando un modulo OSGi in Liferay dipende da una libreria esterna non già disponibile nel classpath globale del portale, ci sono due possibilità:
A. Usare una libreria già fornita da Liferay
- Verificare se la libreria è già presente nel classpath globale di Liferay.
- Suggerimento: se scaricate i sorgenti del portale, anche nella sua versione CE, troverete il file lib/version-complete.xml che vi sarà utile per sapere quali librerie sono già disponibili all’interno del global classpath ed in quali versioni.
- In tal caso, è sufficiente usare
compileOnly group: '...', name: '...', version: '...'
inbuild.gradle
, e il classloader del portale risolverà le classi.
B. Incorporare la libreria nel bundle (shading)
- Se la libreria non è presente nel portale, si può incorporare nel bundle stesso.
- Si usa include o embed nel bnd.bnd:
-exportcontents: com.example.lib.*
- oppure via Gradle con il plugin
biz.aCute.bnd
:
instructions = [
'Bundle-ClassPath': '.,lib/terze-parti.jar',
'-includeresource': '@path/to/jar',
'-exportcontents': 'com.example.lib.*'
]
}
C. Registrare la libreria come modulo OSGi separato
- Una pratica più modulare consiste nell’impacchettare la libreria come un modulo OSGi standalone.
- Si crea un modulo che espone i pacchetti della libreria tramite Export-Package e si installa nel runtime Liferay.
- Altri moduli possono poi importare i pacchetti OSGi pubblicati da questo modulo.
3. Risoluzione dei Conflitti
In presenza di conflitti tra versioni:
- OSGi può gestire versioning preciso dei pacchetti, specificando
version="[1.0,2.0)"
nelle direttive di import/export. - È raccomandato evitare classi duplicate tra bundle: usare il più possibile i moduli condivisi.
Best Practice
- Usare compileOnly per evitare di includere classi duplicate nei bundle.
- Condividere le librerie comuni come moduli OSGi separati.
- Esporre solo ciò che è necessario tramite Export-Package, per minimizzare il coupling.
- Controllare sempre con gogo shell o la console web di Liferay (http://localhost:8080/o/modules) quali bundle sono attivi e quali pacchetti esportano.
Esempio Pratico: Apache POI
Apache POI è una popolare libreria di manipolazione di file (di tipo Excel), ed in Miriade ci è successo molte volte di dover implementare moduli per gestire file Excel.
Abbiamo scelto in questo caso la soluzione C, cioè quella di creare un modulo che includesse le librerie necessarie e le mettesse a disposizione di altri moduli. In questo modo molteplici moduli possono usare le librerie POI linkando solamente il modulo ed includendo i jar una sola volta.
Vediamo i due file principali:
build.gradle
implementation (group: "org.apache.poi", name: "poi", version: "5.2.2"){ transitive = false}
implementation (group: "org.apache.poi", name: "poi-ooxml", version: "5.2.2"){ transitive = false}
implementation (group: 'org.apache.poi', name: 'poi-scratchpad', version: '5.2.2'){ transitive = false}
implementation (group: 'org.apache.poi', name: 'poi-ooxml-lite', version: '5.2.2'){ transitive = false}
implementation (group: 'org.apache.logging.log4j', name: 'log4j-1.2-api', version: '2.17.1'){ transitive = false}
implementation (group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1'){ transitive = false}
implementation (group: 'commons-codec', name: 'commons-codec', version: '1.15')
implementation (group: 'org.apache.xmlbeans', name: 'xmlbeans', version: '5.0.3')
implementation (group: 'org.apache.commons', name: 'commons-compress', version: '1.21')
implementation (group: 'org.apache.commons', name: 'commons-collections4', version: '4.4')
}
Come si può vedere, sono state incluse le librerie principali ed alcune dipendenze fondamentali, senza le quali apache poi non funzionerebbe a runtime.
bnd.bnd
Bundle-SymbolicName: it.miriade.liferay.poi.utils
Bundle-Version: 2.0.0
Import-Package: \
!com.sun.*,\
!com.microsoft.schemas.*,\
!com.github.*,\
!com.graphbuilder.*,\
!com.zaxxer.sparsebits.*,\
!net.sf.saxon.*,\
!de.rototor.pdfbox.graphics2d.*,\
!org.etsi.uri.x01903.v14.*,\
!org.apache.batik.*,\
!org.apache.maven.*,\
!org.apache.commons.collections4.*,\
!org.apache.commons.compress.*,\
!org.apache.commons.math3.*,\
!org.apache.jcp.*,\
!org.apache.tools.ant.*,\
!org.bouncycastle.*,\
!org.brotli.dec.*,\
!org.openxmlformats.schemas.schemaLibrary.x2006.*,\
!org.tukaani.xz.*,\
!org.w3.x2000.*,\
!org.w3c.dom.svg.*,\
!javax.jms.*,\
*
Export-Package: \
org.apache.poi.*
Include-Resource:\
@poi-5.2.2.jar,\
@poi-ooxml-5.2.2.jar,\
@poi-ooxml-lite-5.2.2.jar,\
@poi-scratchpad-5.2.2.jar,\
@xmlbeans-5.0.3.jar,\
@commons-codec-1.15.jar,\
@commons-compress-1.21.jar,\
@commons-collections4-4.4.jar,\
@log4j-1.2-api-2.17.1.jar,\
@log4j-api-2.17.1.jar
-fixupmessages "Classes found in the wrong directory"; restrict:=error; is:=warning
-removeheaders: Bnd-LastModified
-reproducible: true
Anche in questo caso alcune considerazioni.
- Tramite “
Import-Package
” escludiamo l’import di alcuni package. Questo perché l’elenco delle dipendenze indirette è in realtà piuttosto lungo, ma vogliamo evitare di includere decine di jar. In mancanza di queste direttive dovremmo importare tutte le dipendenze transitive, altrimenti al momento dello start del bundle Liferay darebbe un errore perché non riuscirebbe a risolvere l’import di quei package. - Tramite “E
xport-Package
” stiamo dichiarando che questo modulo esporta quel package, quindi tutte le classi in quel package saranno disponibili per altri moduli. - Tramite “
Include-Resource
” andiamo ad istruire il plugin di gradle che in fase di build dovrà includere quei jar all’interno del nostro modulo.
È possibile anche usare la direttiva gradle “compileInclude
”, che ci eviterebbe il punto 3 del precedente elenco, ma è preferibile avere il controllo completo di quello che succede e soprattutto di quali jar vengono effettivamente inclusi.
Conclusioni sulla gestione dipendenze OSGi su Lifery
OSGI è sicuramente un framework molto potente, di fatto un sistema di dependency injection multi modulo di livello enterprise. Tuttavia permane una certa “delicatezza” nell’uso delle dipendenze terze parti, e non sempre è semplice capire quale sia la strada migliore da seguire, o quali siano le cause degli errori a runtime.
Ti è piaciuto quanto hai letto? Iscriviti a MISPECIAL, la nostra newsletter, per ricevere altri interessanti contenuti.
Iscriviti a MISPECIAL