Close

Der Umgang mit Maven-Abhängigkeiten bei der Umstellung auf Git

Porträtfoto von Nicola Paolucci
Matt Shelton

Developer Advocate


Wir wechseln jetzt also zu Git und finden Git-flow toll. Was nun? Probieren wir alle Funktionen aus! Mein Team ist großartig. Es hat auf Basis unserer Teamaktivitäten und allen seltsamen Dingen, die wir in Zukunft vielleicht machen werden, in Confluence eine Hitliste der Entwickler-Workflows zusammengestellt. Anschließend hat es in einer Projektstruktur, die unsere eigene widerspiegelt (aber ohne Code, nur mit einer pom.xml), jeden Workflow ausprobiert.

Dabei haben sich Maven-Abhängigkeiten als unser größtes Problem herausgestellt.

Bei der Maven-Build-Nummerierung produziert Maven 1.0.0-SNAPSHOT-Builds bis zum Release. Beim Release wird -SNAPSHOT entfernt und deine Versionsnummer lautet 1.0.0. Dein Build-Prozess muss in der Lage sein, deine Nebenversionen im Nachhinein hochzuzählen, sodass anschließend releaster Code beim nächsten Mal zu Builds wie 1.1.0-SNAPSHOT führen. Die Nummer muss nicht zwingend dreistellig sein. Das kannst du zu Beginn eines Projekts festlegen. Wir werden hier mit drei Stellen arbeiten. Jedenfalls ist es wirklich wichtig, das mit dem -SNAPSHOT zu verstehen. Das ist immer die aktuellste Momentaufnahme eines Projekts vor dem Release.

Artefakte


Wie du siehst haben wir uns bei allen diesen Workflows darum bemüht, sicherzustellen, dass unsere Projektversionen und projektübergreifenden Abhängigkeiten richtig verwaltet wurden.

Jedes Mal, wenn Maven-Abhängigkeiten abgerufen werden, wird standardmäßig ein Pull aus dem guten alten Internet durchgeführt. Diese Artefakte sind lokal gespeichert, sodass nachfolgende Builds schneller ausgeführt werden können. Eine Möglichkeit, dies ein wenig schmerzloser zu gestalten, ist die Verwendung eines Artefakt-Repositorys auf deinem lokalen Netzwerk, das als ein lokaler Cache für diese externen Abhängigkeiten fungiert. Ein LAN-Abruf ist fast immer schneller als ein Download, selbst vom schnellsten CDN. Wir verwenden Artifactory Pro als Artefakt-Repository. Zusätzlich speichern wir auch unsere eigenen Build-Artefakte in Artifactory, da unsere Struktur mehrmodular ist. Wenn wir eines unserer üblichen Pakete erstellen, können wir diese bestimmte Version über die Auflösung der Maven-Abhängigkeit pullen und das Artefakt direkt aus dem Artefakt-Repository abrufen.

Dies funktioniert bestens. Mit Artifactory lassen sich auch Artefakte zwischen Instanzen synchronisieren. Wenn du es also z. B. zur Replizierung deines Release-Repositorys in deine Rechenzentren für Produktions-Deployments verwenden möchtest, wäre dies ohne den Aufbau eines separaten Prozesses möglich.

Datenbanken
Zugehöriges Material

Verschieben eines vollständigen Git-Repositorys

Bitbucket-Logo
Lösung anzeigen

Git kennenlernen mit Bitbucket Cloud

Maven-Abhängigkeiten, Feature Branches und Pull-Requests


Alle unsere Builds fließen in Artifactory ein. Mit SVN haben wir ein Snapshot-Repository zum Speichern der beiden neuesten Snapshot-Builds, ein Staging-Repository für alle noch nicht genehmigten Release-Builds und ein Release-Repository nur für Builds zur Produktion verwendet.[1] Diese Builds werden wie zuvor beschrieben nummeriert und sind abrufbar über ein vorhersehbares URL-Muster basierend auf Repository und Version.

Der primäre Workflow jedes Entwicklers sah folgendermaßen aus: Erstellen eines Feature Branch vom Develop Branch für die jeweilige Aufgabe, Abschließen der Aufgabe, Absetzen eines Pull-Requests zum Mergen des Arbeitsergebnisses in den Develop Branch. In einem einzelnen Projekt funktioniert dies meist ohne Schwierigkeiten, aber wir sind mit vollem Karacho auf ein Problem gestoßen, das uns ernsthaft an der gesamten Migration zweifeln ließ:

Wie ich bereits erwähnte, verfügen wir über mehrere Abhängigkeitsebenen zwischen unseren Projekten. Dafür gibt es bei unseren Produkten gute Gründe – historisch gewachsene und strategische. Wir haben über die Einführung anderer Architekturen nachgedacht, die dieses Problem beseitigen würden, aber dadurch würden wieder neue Probleme entstehen. Wir können uns das Leben erleichtern (und haben dies auch getan, aber dazu mehr in einem anderen Beitrag), doch momentan ist es für uns strategisch besser, unsere Struktur beizubehalten.

Entwickler A – sagen wir, es ist Angela – beginnt, an einem Feature in Jira zu arbeiten. Dazu braucht sie zwei Branches: einen ausgehend vom gemeinsamen Projekt und einen ausgehend von ProduktX. Die Version des gemeinsamen Projekts lautet 2.1.0-SNAPSHOT. Die Version von ProduktX ist 2.4.0-SNAPSHOT. Sie arbeitet eine Zeit lang lokal und pusht dann zu Bitbucket Data Center. Bamboo greift diese Änderungen auf, erstellt das gemeinsame Paket, lädt common-2.1.0-SNAPSHOT zu Artifactory hoch und erstellt ProduktX mit einer Abhängigkeit zu common-2.1.0-SNAPSHOT. Dabei wird auch productX-2.4.0-SNAPSHOT hochgeladen. Die Unit-Tests sind erfolgreich.

Entwickler B – nennen wir ihn Bruce – beginnt, an einem anderen Feature in Jira zu arbeiten, das zu einem anderen Projekt gehört: ProduktY. Dazu braucht er ebenfalls zwei Branches: einen ausgehend von unserem gemeinsamen Projekt und einen ausgehend von ProduktY. Die Version des gemeinsamen Projekts ist, wie schon oben erwähnt, 2.1.0-SNAPSHOT. Die Version von Produkt Y ist 2.7.0-SNAPSHOT. Er arbeitet eine Zeit lang lokal und pusht seine Änderungen dann zu Bitbucket Data Center. Bamboo greift diese Änderungen auf, erstellt das gemeinsame Paket, lädt common-2.1.0-SNAPSHOT zu Artifactory hoch und erstellt ProduktX mit einer Abhängigkeit zu common-2.1.0-SNAPSHOT. Dabei wird auch productX-2.4.0-SNAPSHOT hochgeladen. Die Unit-Tests sind erfolgreich.

In der Zwischenzeit findet Angela einen kleinen Bug in ihrem ProduktX-Code und überprüft ihren Bugfix mit einem Unit-Test. Der lokal ausgeführte Test ist erfolgreich. Sie pusht ihre Änderungen zu Bitbucket. Bamboo greift die Änderung auf und führt den Build für ProduktX aus. Die Builds laufen erfolgreich, aber einige der Unit-Tests schlagen fehl. Schwierigkeiten machen nicht ihre neuen Tests, sondern die ersten Tests ihrer anfänglichen Änderungen des Features. Wurde im Bamboo-Build etwa eine Regression gefunden, die dem lokalen Build entgangen ist? Wie ist das möglich?

Der Grund: Ihre übliche Abhängigkeit – die, die Bamboo beim Erstellen von ProduktX übernommen hat – war keine Kopie mehr. Bruce hat common-2.1.0-SNAPSHOT in Artifactory beim Abschluss seines Feature-Builds überschrieben. Es gab keinen Quellcode-Konflikt, denn beide Entwickler haben isoliert voneinander in eigenen Branches gearbeitet. Jedoch war die Quelle, aus der Maven Artefakte abruft, beschädigt.

Da möchte man den Kopf gegen den Schreibtisch schlagen.

Nachdem wir das Problem ausgemacht hatten, haben wir rund einen Monat lang alles versucht, um eine Lösung zu finden. Über unseren TAM[2] haben wir uns an das Bamboo-Team gewendet, das mit Git-flow arbeitet, und haben mit dem Entwickler gesprochen, der Git-flow, eine Java-Implementierung von Git-flow, wartet. Sie waren alle sehr hilfsbereit, kannten aber keinen Prozess mit einzelnen manuellen Schritten, die jeder Entwickler jedes Mal beim Bearbeiten eines Features befolgen könnte. Wir konnten also keine vertretbare Lösung finden.

Falls dich interessiert, was wir alles ausprobiert haben:

1. Modifiziere die Versionsnummer bei der Branch-Erstellung oder direkt danach.

  • Dazu können wir mit mvn jgitflow:feature-start einen Branch erstellen.
  • Wir können einen Bitbucket Data Center-Hook oder einen lokalen Git-Hook verwenden.
  • Mit mvn version:set-version können wir die Version manuell festlegen, nachdem wir den Branch erstellt haben.
  • Wir können die Änderung mit dem [maven-external-version]-Plug-in automatisieren.

2. Modifiziere die Versionsnummer, nachdem du den Branch abgeschlossen und ihn in den develop-Branch zurückgeführt hast.

  • Dazu können wir mit mvn jgitflow:feature-finish einen Branch abschließen.
  • Verwende einen "git merge"-Treiber, um POM-Konflikte zu bewältigen.
  • Verwende einen asynchronen post-receive-Hook in Bitbucket Data Center.

3. Mach alles manuell. (Kleiner Scherz. War nur eine kurze Überlegung.)

Mögliche Workflows


Vor dem Hintergrund dieses Kernkonzepts wirst du verstehen, dass submodule einige Workflows gut und andere weniger optimal unterstützen. Es gibt mindestens drei Szenarien, in denen Untermodule eine gute Wahl sind:

  • Wenn eine Komponente oder ein Unterprojekt sich zu schnell ändert oder bevorstehende Änderungen nicht mit der API kompatibel sind, kannst du zur eigenen Sicherheit den Code in einen bestimmten Commit sperren.

  • Wenn eine Komponente nicht sehr oft aktualisiert wird und du sie als Anbieterabhängigkeit verfolgen möchtest. Ich mache das zum Beispiel bei meinen Vim-Plugins.

  • Wenn du einen Teil des Projekts an eine Drittpartei delegierst und deren Arbeitsergebnisse zu einem bestimmten Zeitpunkt oder beim Release integrieren möchtest. Dies funktioniert bei nicht zu häufigen Updates.

Danke an Finch für die gut erklärten Szenarien.

Jede dieser Optionen hatte ihre eigenen negativen Nebenwirkungen. In erster Linie entstanden jedes Mal, wenn die Entwickler einen Feature Branch benötigten, manuelle Schritte. Wo wir doch möchten, dass sie ständig Feature Branches erstellen. In den meisten Fällen konnten wir außerdem Pull-Request nicht effektiv einsetzen – dies kam für uns nicht in Frage.

Damit waren ein bis zwei Mitarbeiter fast zwei Monate vollkommen ausgelastet, bis wir letztendlich in einem Moment der Erleuchtung feststellten, dass wir das Problem von der falschen Seite angegangen sind.

Eine Version für alle


Im Jahre 2020 werden wir sicher zurückblicken und denken: Unser größter Fehler war, uns zu sehr auf Git-flow-Tools zu konzentrieren, statt die Tools zu nutzen, die wir längst hatten, um den Workflow zu implementieren, den wir uns wünschten. Wir hatten:

  • Jira
  • Bamboo Data Center
  • Maven
  • Artifactory Pro

Wie es sich herausstellte, benötigten wir lediglich diese Tools.

Einer unserer Entwickler hatte eine clevere Idee: Da der Grund des Problems nicht beim Build-Management an sich lag, sondern eher bei den zu überschreibenden Artefakten, sollten wir den Fehler besser in Artifactory beheben. Er schlug vor, sich eine Maven-Eigenschaft zunutze zu machen und für die Repository-URL des Snapshots eine selbst festgelegte URL mit der Vorgangs-ID von Jira zu definieren. Anschließend wollte er die Artefakte mit einer individuellen Vorlage zu einem dynamisch erstellten Repository in Artifactory überführen. Bei der Auflösung von Abhängigkeiten in Maven sind Artefakte im Entwicklungs-Snapshot-Repository auffindbar, wenn wir für sie keine Branches erstellt haben, z. B. bei der Arbeit an nur einem Produkt und nicht am gemeinsamen Projekt.

Wir haben diese praktische kleine Eigenschaftsvariable in unsere Build-Einstellungsdatei aufgenommen und ein Maven-Plug-in erstellt, um die Variable im frühsten Abschnitt des Maven-Build-Lebenszyklus einzusetzen. Auf dem Papier hörte sich das unglaublich gut an und hat das Team wieder darin bestärkt, härter an der Lösung des Problems zu arbeiten. In der Realität konnten wir das allerdings nicht umsetzen. Die früheste Phase des Maven-Lebenszyklus ist das Validieren. Zu dem Zeitpunkt, an dem Plug-ins zur Validierung ausgeführt werden, sind die Repository-URLs bereits aufgelöst worden. Daher wurde unsere Variable nie eingesetzt und die URL enthält schließlich nicht den Namen des Branches. Selbst wenn wir ein Layout in einem Repository verwendet hätten, das von unseren Entwicklungs-Snapshots getrennt gewesen wäre, wäre es nicht derart isoliert gewesen, um eine Parallelentwicklung zu ermöglichen.

Und wieder einmal schlagen wir unsere Köpfe gegen den Tisch.

Nach einem Bier suchte der eben erwähnte Entwickler nach einem anderen Weg, Maven mit zusätzlichen Funktionen auszustatten: Erweiterungen.

"Auf den Alkohol, den Ursprung und die Lösung sämtlicher Lebensprobleme." – Homer Simpson

Erweiterungen, wie auch Plug-ins, sind starke Werkzeuge zur Verbesserung deines Maven-Workflows. Sie werden jedoch vor dem Lebenszyklus-Ziel ausgeführt und greifen besser in die Maven-Mechanismen. Wir haben in Maven mit dem Paket RepositoryUtils erzwungen, dass die URLs mit einem speziellen Parser neu ausgewertet und dann mit unseren aktualisierten Werten neu festgesetzt werden.[3]

Nachdem die Erweiterung vorhanden und auch getestet war, arbeiteten wir uns zügig durch die Vorbereitung der Migration und "Das wird niemals Wirklichkeit werden" wurde zu "Das WIRD am Montag Wirklichkeit ... und ich muss bis morgen zehn Seiten Dokumentation verfassen". Ich werde in Kürze noch mehr darüber schreiben, wie die Tools in unserem neuen Entwicklungs-Workflow zusammenarbeiten und was wir während des gesamten Prozesses gelernt haben.


Fußnoten

[1]: Eine Kehrseite dieser Medaille: Ich musste ein Skript erstellen, damit die Artifactory-REST-API die Builds vom Staging in den Release "beförderte". Die Geschwindigkeit ist ausreichend, aber eine Automatisierung wäre dringend angebracht.

[2]: Technical Account Manager. Weitere Informationen findest du hier.

[3]: Nach den anfänglichen Entwicklungsarbeiten dachten wir, wir müssten mehr Arbeit investieren, damit alles immer funktioniert. Wenn z. B. ein Snapshot in Artifactory (von einem anderen Entwickler) neuer ist als der lokale Snapshot, dann schnappt sich Maven das Remote-Artefakt, weil es NEUER ist – und NEUER heißt BESSER, oder etwa nicht?

Matt Shelton
Matt Shelton

Matt Shelton ist DevOps-Fürsprecher und -Anwender. Er leitet die Bereitstellung von DevOps- (und ähnlichen) Serviceangeboten für Atlassian auf dem amerikanischen Kontinent. Er bloggt nur noch selten und konzentriert sich darauf, die nächste Generation von Fürsprechern besserer Arbeitsweisen heranzubilden.


Diesen Artikel teilen

Lesenswert

Füge diese Ressourcen deinen Lesezeichen hinzu, um mehr über DevOps-Teams und fortlaufende Updates zu DevOps bei Atlassian zu erfahren.

Mitarbeiter arbeiten mit unzähligen Tools zusammen

Bitbucket-Blog

Abbildung: DevOps

DevOps-Lernpfad

Demo Den: Feature-Demos mit Atlassian-Experten

So funktioniert Bitbucket Cloud mit Atlassian Open DevOps

Melde dich für unseren DevOps-Newsletter an

Thank you for signing up