Konfliktmanagement mit Maven

Hallo Spaß-Coder.

Nachdem wir im ersten Teil dieser Artikelreihe Maven ein wenig näher kennenlernen durften, gehen wir nun einen Schritt weiter. Wir zeigen weitere Eigenschaften von Maven auf, die unseren Entwicklungsalltag erleichtern.

In dieser Artikelreihe möchten wir ein bisschen Licht ins Dunkel um das Mysterium von Apache Maven bringen. Fragen wie „Was ist Maven?“, „Wobei hilft mir Maven?“ oder „Wann soll ich Maven verwenden?“ werden wir beantworten. Dabei starten wir ganz am Anfang und versuchen die Einstiegshürde so klein wie möglich zu halten. Wer jetzt noch ängstlichen Respekt vor Maven hat, sollte die Angst nach dieser Artikelreihe abgelegt haben und nur noch anerkennenden Respekt für Maven empfingen.
Unserer Ansicht nach ist Maven im Umfeld der Java-Programmierung ein unverzichtbar hilfreiches Werkzeug.

 

Raus aus der DLL… äh…JAR-Hölle!

Wer von euch schon mal an großen Projekten gearbeitet hat, in dem die Abhängigkeiten nicht mit einem Werkzeug verwaltet wurden, kennt wahrscheinlich die Situation: Es gibt eine neue Version der Software, ist aber auf Teufel komm raus nicht zum Laufen zu bringen. Warum nicht? Irgendwas fehlt! Kompilierfehler, ClassNotFoundException, FileNotFoundException, … ärgerlich! Irgendwer hat mal wieder vergessen alles rüber zu kopieren. *grummel*

Und dieser Irgendwer konnte gar nicht mal so viel dafür. Er hat selbst lange Zeit gebraucht herauszufinden, dass für die neue Bibliothek A die er gebraucht hat eine zweite Bibliothek B erforderlich ist. Als es dann darum ging, die Anwendung den anderen Entwicklern (oder auf der Testumgebung) zur Verfügung zu stellen, hatte er diese zweite Bibliothek schlicht vergessen.

Ich möchte das mal anhand eines Beispiels verdeutlichen. Nehmen wir mal an, wir wollen unser TicTacToe aus dem ersten Teil der Artikelreihe um eine Highscore erweitern, die wir über einen Rest-Service ansprechen. Was wir dafür brauchen verrät uns Google recht schnell: Spring-Web und Jackson-Databind. Wir können uns diese Bibliotheken nun herunterladen, an einer zentralen Stelle ablegen in unserer IDE den Classpath unserer Anwendung erweitern… oder unsere POM.xml entsprechend um diese Abhängigkeiten ergänzen. Wir wählen natürlich den zweiten Weg:

Lassen wir uns nun mal den Abhängigkeitsgraphen dazu anzeigen, erleben wir eine Überraschung:

maven-transitive-dependency

Erstellt mit IntelliJ IDEA UML-Plugin.

Statt den erwarteten zwei Abhängigkeiten erhalten wir in Summe unglaubliche elf! Dabei manuell den Überblick zu behalten ist sicher nicht einfach.

Durch das Prinzip der transitiven Abhängigkeiten aber nimmt Maven uns diese Last von den Schultern. Jedes Artefakt kennt seine Abhängigkeiten, welche wieder um seine Abhängigkeiten kennt, usw. Dadurch weiß Maven ganz genau, welche Artefakte benötigt werden.

Das Ganze funktioniert natürlich nur dann, wenn jedes der Artefakte ein Maven-Modul ist und eine pom.xml besitzt, die es beschreibt. Das ist aber für die wichtigsten Java-Projekte im Open-Source Umfeld der Fall.

 

Versionskonflikte

Ein weiteres Problem sind unterschiedliche Versionen der gleichen Bibliothek. Hat ein anderer Entwickler in unserem Team etwa bereits mal einen Rest-Service konsumiert und ebenfalls Spring-Web dazu verwendet, hat er dazu ggf. eine andere Version verwendet. Da wir das nicht unbedingt wissen, führen wir eine neue Version ein.

Auch für dieses Problem bietet Maven eine Lösung an. Über Dependency Management bietet Maven die Möglichkeit, die Ermittlung der Version zu zentralisieren.

Als Entwickler gebe ich also nicht mehr an, welches Artefakt ich in welcher Version haben möchte, sondern nur noch welches Artefakt ich benötige. Um die Version kümmert sich dann das Dependency Management von Maven.

Das obige Beispiel noch einmal mit Dependency Management:

Wir geben also keine Version bei unseren Artefakten mehr an. Woher aber wird diese nun ermittelt? Über unseren Parent. Wir haben am Anfang der pom.xml einen Eintrag ergänzt, der ein Artefakt mit dem Namen invid-parent als unseren Parent definiert. Schauen wir doch mal in die pom.xml von invid-parent.

Hier haben wir also eine neue Sektion mit dem Tag dependencyManagement, in dem wir für alle unterliegenden POMs die Versionen festlegen.

Wichtig: Durch den Eintrag im Bereich dependencyManagement, haben wir keine Abhängigkeit festgelegt. Wird dieses Artefakt nicht noch einmal als dependency deklariert, wird Maven es niemals herunterladen und Verwenden. Eine Abhängigkeit im dependencyManagement sagt also nur aus: „für den Fall, das wir dieses Artefakt brauchen, und keine Version angegeben ist, verwende die folgende Version.“

Treten solche Konflikte über eine transitive Abhängigkeit auf, bekommen wir sie damit natürlich nicht in den Griff. Maven hat aber eine ausgefeilte Strategie, die beste Version für das Projekt zu bestimmen. Bislang hat uns diese noch keine Probleme bereitet. Von der Apache-Maven Webseite: „[…]by default Maven resolves version conflicts with a nearest-wins strategy.“

 

Solang du deine Füße unter meinen Tisch stellst…

Aber was war das den da mit dem Parent? Wir geben in einem parent ein Artefakt an und dieses sagt uns dann was zu tun ist? Genauso wie ein strenger Vater dies tut? Nicht ganz. Genau wie die dargestellte Erziehungsmethode außer Mode ist, lässt sich auch unser Projekt nicht vorschreiben, was es zu tun oder zu lassen hat.

Maven arbeitet nach dem Prinzip Convention over Configuration und macht sich das auch hier zunutze. Geben wir im vorgenannten Beispiel bei Spring-Web in unserer pom.xml eine andere Version an, als im dependencyManagement des Parent angegeben ist, gilt diese. Der Vater kann hier also nur einen Rat geben; die Entscheidung trägt immer noch das Kind, in diesem Fall also unser Projekt.

Das als parent angegebene Artefakt muss existieren. Es muss dabei aber nicht einmal ein lauffähiges Java-Projekt sein. Wir sehen in unserem Parent folgende Angabe: <packaging>pom</packaging> Dies bewirkt, dass eben keine JAR-Datei herauskommt (das ist der Standardwert, wenn nichts angegeben ist, also die Convention), sondern wie angegeben (Configuration) eine pom.xml. Dabei sprechen wir diese mit den Maven-Koordinaten für ein Artefakt an, der GAV.

Die Vererbung der Konfiguration erfolgt dabei über beliebig viele Stufen. Jede pom.xml kann einen Parent haben, von dem die Konfigurationen verwendet werden, wenn keine abweichenden angegeben wurden.

Dazu nochmal ein Beispiel. Im Standard von Maven (Convention) ist die Kompatibilität mit dem Java-Compiler Version 1.3. Möchten wir nun eine andere Kompiler-Version, z.B. 1.8 verwenden, erfolgt diese Änderung explizit in der pom.xml:

Alle darunterliegenden Module verwenden von da an die Kompiler-Version 1.8, bis wieder eine anders lautende Konfiguration vorgenommen wird. Was recht kompliziert klingt führt im Alltag dazu, dass ich mich in meinem aktuellen Projekt auf das Wesentliche konzentrieren kann. Alles globale wurde einmal für das Projekt sinnvoll festgelegt und muss von mir nun nicht erneut bedacht werden.

Faustregel: Fehlt eine Angabe, schau in den Parent.

 

Strukturelle Integrität

Zum Abschluss möchte ich noch eine Konvention vorstellen, die grundlegend ist, wenn wir neue Module anlegen, nämlich die Verzeichnisstruktur eines Maven-Projekts. Hierin sind viele Festlegungen enthalten, die Maven verwendet um das Modul richtig zu bauen.

Die Struktur unseres Projekts TicTacToe sieht folgendermaßen aus:

maven-folder-structure
  • src für Modul Sourcen
    • main für eigentliche Sourcen
    • test für Software-Tests (junit, Selenium, …)
      • java für Sourcen, die mit javac kompiliert werden
      • resources für Dateien, die einfach in das Artefakt kopiert werden
  • target für die Ausgabe der Module nach dem Kompilieren und Bauen
  • pom.xml beschreibt unser Modul

Ohne weitere Konfiguration ist somit klar, was das Artefakt enthalten wird und welche Tests es validieren. Darüber hinaus sind auch die Klassenpfade für Kompilier- und Testphase eindeutig festgelegt. Durch die Angabe der Sprache (hier java) wird Maven angewiesen, javac für das Kompilieren zu verwenden. Alles was im Ordner resources abgelegt wird, landet nach dem Bauprozess im Root unseres JARs oder im passenden Ressourcen-Ordner unseres WARs.

Diese vordefinierte Struktur kann – wie alles in Maven – auch konfigurativ anders festgelegt werden. Da wir dies aber nicht empfehlen, fehlt hier die entsprechende Anleitung dazu. Für Legacy-Projekte, die auf Maven umgestellt werden sollen, kann es aber sinnvoll sein, wenigstens für eine Übergangsphase die alte Struktur beizubehalten.

 

Zusammenfassung

Mit den gezeigten Möglichkeiten bietet uns Maven einige Hilfsmittel um immer wiederkehrende Probleme bei größer werdenden Projekten elegant in den Griff zu bekommen. Durch die Erfahrungen aus der großen Open-Source-Community sind die meisten von Maven verwendeten Conventions gleichzeitig Best-Practices, mit denen man die Aufgaben in aller Regel sehr gut bewältigen kann. Sind doch mal spezielle Einstellungen notwendig, können diese ebenfalls sehr flexibel verwendet werden.

In Maven steckt aber noch viel mehr. Insbesondere durch Plugins lässt sich Maven flexibel erweitern und ist auch für spezielle Aufgaben gerüstet. Darauf werden wir im nächsten Artikel der Reihe näher eingehen.

Eure Spaß-Coder

Dieser Artikel basiert neben unseren Erfahrungen auf den Ausführungen aus:

  • Maven-Training von René Gielen – IT Neering (nahezu alles, was wir über Maven wissen, wissen wir direkt oder indirekt von ihm. Danke René!)
  • http://maven.apache.org/
  • Hüttermann, Michael – Agile ALM, Manning Publications Co., 2011

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.