Dependencies…
Marcel Offermans | 29-05-2007Dependency management is een onderwerp wat de Java community de laatste jaren steeds meer bezig houdt. Meestal ontstaat de behoefte hieraan wanneer men in een project gebruik maakt van een aantal externe, vaak open source libraries. Nu zijn er, afhankelijk van de tooling die je gebruikt, verschillende mogelijkheden om met deze dependencies om te gaan.In Ant kun je bijvoorbeeld werken met een property file waarin per library een property is opgenomen die verwijst naar de locatie waar die library (lokaal) geïnstalleerd staat. De library zelf wordt in sommige gevallen zelfs in dezelfde source repository opgenomen als het project. Wil je meer, dan kun je Ivy gebruiken, een uitbreidbaar systeem om dependencies te managen.
Gebruik je Maven 2, dan definieer je in je project welke externe dependencies je hebt. Maven zorgt er dan zelf voor dat die libraries gedownload en gebruikt worden. De repositories die daarbij gebruikt worden, kun je zelf bepalen, maar standaard is er een grote set in de globale Maven repository te vinden.
Dit zijn allemaal voorbeelden van systemen die tijdens het bouwen van de software de dependencies beheren. Als je OSGi gebruikt, dan zul je ook tijdens runtime dependencies moeten beheren.
Een van de bekendste voorbeelden van een applicatie die gebruik maakt van OSGi is Eclipse. Er zijn twee mogelijkheden om dependencies te definieren: via imports en exports van packages of via de require bundle constructie. Eclipse gebruikt de laatste en is daarmee conceptueel gelijk aan Maven 2.
Het probleem met het managen van dependencies op “JAR files” is dat je dependencies definieert door harde verwijzingen naar implementaties. Het is dus niet zomaar mogelijk om een andere implementatie te gebruiken, ook al doet die functioneel het zelfde. Neem als voorbeeld een applicatie die een web server nodig heeft om een servlet te draaien. Twee bekende webservers in Java zijn Tomcat en Jetty. Wanneer je nu keihard een dependency maakt op bijvoorbeeld Tomcat, dan kun je later niet meer zomaar switchen naar Jetty, terwijl dat conceptueel gezien prima zou moeten kunnen (en er op dat moment misschien zelfs goede redenen zijn om dat ook te doen).
Daarnaast zijn JAR files als eenheid van deployment en meestal vrij “grof”: ook al gebruik je uit een bepaalde library maar één enkele functie, je krijgt toch de hele library, alle classes en packages, en al zijn dependencies, waarvan die library er misschien ook maar een paar gebruikt. Kortom, de kans dat je uiteindelijk een grote hoeveelheid code krijgt waarvan je maar een klein deel daadwerkelijk gebruikt, is aanzienlijk.
Een heel mooi voorbeeld uit de praktijk is Eclipse. De vele plugins daar hebben dependencies op (specifieke versies van) implementaties en daardoor heb je vaak de grootst mogelijke moeite om alle dependencies bij een bepaalde plugin voor jouw versie van Eclipse te installeren. Dat probleem is zo groot dat er vanuit Eclipse nu een standaard “big bang” distributie gemaakt is onder de naam Callisto. In feite is dit een zwaktebod en symptoombestrijding: omdat het lastig is om dependencies te managen als je ze naar implementaties laat verwijzen, doen we maar één grote release zodat we in feite maar één versie van elke implementatie hebben. Daarmee komen de voordelen die een componenten architectuur heeft niet meer volledig tot zijn recht.
Wat je eigenlijk moet doen is dependencies definiëren op package niveau en alleen maar depencencies maken tussen interfaces.
Er wordt wel eens schertsend geroepen dat Eclipse het best bekende en slechtste voorbeeld van OSGi is. Dat is misschien een beetje overdreven, maar er zijn zeker dingen die ze beter hadden kunnen en moeten regelen. Ter verdediging van Eclipse, ze zijn pas later overgestapt van een eigen framework naar OSGi en hadden daarmee een zeer complex migratie traject waardoor de ideale oplossing vanuit ontwerp perspectief in de praktijk misschien niet meer zo makkelijk te realiseren was zonder backward compatibility te verliezen. Wanneer je echter een nieuw project op basis van OSGi start en daar dus geen rekening mee hoeft te houden, zou ik je zeker adviseren om dependencies te managen op package niveau, middels imports en exports.





