Close

Omgaan met Maven-afhankelijkheden bij de overstap naar Git

Headshot van Nicola Paolucci
Matt Shelton

Developer Advocate


Dus we stappen over op Git en we houden van git-flow. En nu? Laten we het allemaal testen! Mijn team is geweldig. Ze hebben een lijst van populaire workflows voor ontwikkelaars in Confluence samengesteld, allemaal gebaseerd op wat we als team hadden gedaan en alle rare dingen die ze dachten dat we in de toekomst zouden moeten doen. Vervolgens hebben we in een projectstructuur die onze eigen structuur weerspiegelt (maar zonder code erin, gewoon een pom.xml), elke workflow geprobeerd.

De Maven-afhankelijkheden stonden op het punt te bewijzen dat ze ons grootste probleem in dit alles waren.

Maven-buildnummering Maven produceert 1.0.0-SNAPSHOT builds totdat je deze uitbrengt. Wanneer je de release uitbrengt, wordt -SNAPSHOT verwijderd en is jouw versie 1.0.0. Jouw build moet verhoging van jouw kleinere versie achteraf ondersteunen, zodat volgend werk bij je volgende poging resulteert in builds zoals 1.1.0-SNAPSHOT. Je bent niet gebonden aan drie cijfers: je kunt dit definiëren wanneer je een project start, maar we gebruiken er drie. Hoe dan ook, het gedeelte -SNAPSHOT is erg belangrijk om te begrijpen. Dit zal altijd de laatste prerelease van een project zijn.

Artefacten


Bij al deze workflows was onze grootste zorg hoe we ervoor konden zorgen dat onze projectversies en afhankelijkheden tussen projecten naar behoren zouden worden beheerd.

Elke keer dat Maven-afhankelijkheden worden opgehaald voor een build, worden deze standaard gepulld uit Ye Olde Internet(e)TM. Die artefacten worden lokaal opgeslagen, zodat volgende builds sneller kunnen worden uitgevoerd. Een oplossing om dit wat minder lastig te maken, is een repository voor artefacten in je lokale netwerk te gebruiken als lokale cache voor die externe afhankelijkheden. Opvragen via een LAN is bijna altijd sneller dan downloaden, zelfs van de snelste CDN's. We gebruiken Artifactory Pro als repository voor artefacten. Omdat we een structuur met meerdere modules hebben, slaan we bovendien onze eigen gemaakte artefacten ook op in Artifactory. Wanneer we een van onze gebruikelijke pakketten bouwen, kunnen we die specifieke versie pullen via de afhankelijkheidsresolutie van Maven en het artefact rechtstreeks uit de repository voor artefacten ophalen.

Dit werkt vlekkeloos. Met Artifactory kun je ook je artefacten synchroniseren tussen installaties. Dus als je Artifactory bijvoorbeeld wilt gebruiken om je releaserepository te repliceren naar je datacenters voor productie-implementaties, dan zou je dat kunnen doen zonder dat je een apart proces hoeft te bouwen.

Databases
gerelateerd materiaal

Een volledige Git-repository verplaatsen

Logo Bitbucket
Oplossing bekijken

Git leren met Bitbucket Cloud

Maven-afhankelijkheden, feature-branches en pull-aanvragen


Al onze builds gaan naar Artifactory. Met SVN hadden we een repository voor snapshots gebruikt voor het bewaren van de laatste 2 snapshot-builds, van een stagingopslagplaats voor alle release-builds die nog niet zijn goedgekeurd, en van een releaserepository alleen voor de builds die klaar waren om in productie te gaan.[1] Deze builds zijn genummerd, zoals ik eerder heb beschreven, en zijn terug te vinden aan de hand van een voorspelbaar URL-patroon op basis van repository en versie.

De primaire workflow voor elke ontwikkelaar was om vanuit de develop-branch een feature-branch voor hun werk te creëren, deze te voltooien en een pull-aanvraag in te dienen om dat werk terug samen te voegen in de develop-branche. Voor één project werkt dit meestal zonder problemen, maar laat me je een beeld schetsen van het eerste probleem dat we tegenkwamen, en een probleem waarbij we de hele migratie serieus opnieuw moesten overwegen:

Zoals ik al eerder zei, zijn er meerdere afhankelijkheidslagen tussen onze projecten. Daar is voor onze producten een heel goede reden voor, zowel historisch als strategisch gezien. We hebben alternatieve architecturen overwogen om dit probleem op te lossen, maar ze zouden andere architecturen introduceren. We kunnen het ons makkelijker maken (en dat hebben we gedaan, maar dat is voor een latere post), maar op dit moment is het voor ons van strategisch belang om onze structuur te behouden zoals deze is.

Dus ontwikkelaar A, laten we haar Angela noemen, begint te werken aan een functie in Jira. Hiervoor zijn twee branches nodig: een van ons gemeenschappelijke project en een van product X. De versie voor het gemeenschappelijke project is 2.1.0-SNAPSHOT. De versie voor ProductX is 2.4.0-SNAPSHOT. Ze werkt een tijdje lokaal en pusht dan uiteindelijk terug naar Bitbucket Server. Bamboo pikt deze wijzigingen op, stelt het gemeenschappelijke pakket samen en uploadt common-2.1.0-SNAPSHOT naar Artifactory, bouwt vervolgens productX met een afhankelijkheid van common-2.1.0-SNAPSHOT en uploadt ook ProductX-2.4.0-SNAPSHOT. De unittests zijn geslaagd!

Ontwikkelaar B, laten we hem Bruce noemen, begint te werken aan een andere functie in Jira, voor een ander product: productY. Ook hiervoor zijn twee branches nodig: een van ons gemeenschappelijke project en een van productY. De versie voor het gemeenschappelijke project is 2.1.0-SNAPSHOT, zoals hiervoor. De versie van product Y is 2.7.0-SNAPSHOT. Hij werkt een tijdje lokaal en pusht dan uiteindelijk de wijzigingen naar Bitbucket Server. Bamboo pikt deze wijzigingen op, stelt het gemeenschappelijke pakket samen en uploadt common-2.1.0-SNAPSHOT naar Artifactory, bouwt vervolgens productX met een afhankelijkheid van common-2.1.0-SNAPSHOT en uploadt ook ProductX-2.4.0-SNAPSHOT. De unittests zijn geslaagd!

Angela vindt ondertussen een kleine bug in haar productX-code en schrijft een unittest om haar oplossing valideren. Ze voert deze lokaal uit en de test slaagt. Ze pusht haar wijzigingen naar Bitbucket en Bamboo, pakt de wijziging op en bouwt productX. De build is geslaagd, maar sommige van haar unittests zijn mislukt. Het zijn niet de nieuwe die ze schreef, maar de eerste van haar aanvankelijke wijzigingen in het functiebeheer. Op de een of andere manier heeft de Bamboo-build een regressie gevonden die haar lokale build niet heeft gevonden? Hoe kan dit?

Dat is omdat haar gemeenschappelijke afhankelijkheid (die Bamboo naar binnen pullde om productX te bouwen) niet langer haar exemplaar was. Bruce heeft common-2.1.0-SNAPSHOT overschreven in Artifactory toen zijn functiebuild voltooid was. Er was geen bronconflict: beide ontwikkelaars werkten geïsoleerd aan hun eigen branches, maar de bron van waarheid voor het ophalen van het Maven-artefact was beschadigd.

Kop. Ontmoet het bureau.

Ongeveer een maand nadat we dit probleem hadden ontdekt, hebben we alles geprobeerd om dit op te lossen. Via onze TAM[2] spraken we met mensen van het Bamboo-team die git-flow gebruiken, en we spraken met de ontwikkelaar die git-flow onderhoudt, een Java-implementatie van git-flow. Ze waren allemaal erg behulpzaam, maar bij gebrek aan een proces waarbij elke ontwikkelaar elke keer dat ze aan een functie werkten een lijst met handmatige stappen moest uitvoeren, konden we geen aanvaardbare oplossing vinden.

Als je benieuwd bent wat we hebben overwogen, is hier een overzicht van alles wat we geprobeerd hebben:

1. Wijzig het versienummer bij het aanmaken van de branch of onmiddellijk daarna.

  • We kunnen dit doen met mvn jgitflow:feature-start om de branch aan te maken.
  • We kunnen een Bitbucket Server-hook of een lokale githook gebruiken.
  • We kunnen dit handmatig instellen met mvn-version:set-version nadat we de branch hebben aangemaakt.
  • We kunnen de wijziging automatiseren met de plug-in [maven-external-version].

2. Pas het versienummer aan wanneer je klaar bent met de branch en deze weer samenvoegt om te ontwikkelen.

  • We kunnen dit doen met mvn jgitflow:feature-finish om de branch te voltooien.
  • Gebruik een git merge-driver om pom-conflicten af te handelen.
  • Gebruik een asynchrone post-receive hook in Bitbucket Server

3. Doe alles handmatig. (Grapje! We hebben deze optie niet heel lang overwogen.)

Mogelijke workflows


Door dit kernconcept te onthouden en erover na te denken, kun je begrijpen dat die submodule sommige workflows goed ondersteunt en andere minder optimaal. Er zijn minstens drie scenario's waarin submodules een redelijke keuze zijn:

  • Als een component of subproject te snel verandert of als er komende wijzigingen de API defect maken, kun je voor je eigen veiligheid de code aan een specifieke commit vastkoppelen.

  • Als je een component hebt die niet vaak wordt bijgewerkt en je dit wilt bijhouden als leveranciersafhankelijkheid. Ik doe dit bijvoorbeeld voor mijn vim-plug-ins.

  • Wanneer je een deel van het project delegeert aan een externe partij en je hun werk op een bepaald moment of in een specifieke release wilt integreren. Nogmaals, dit werkt als er niet te vaak updates zijn.

Met dank aan Finch voor de goed toegelichte scenario's.

Elk van deze opties had een soort van negatieve bijwerking. Vooral de handmatige stappen voor ontwikkelaars, telkens wanneer ze een feature-branch nodig hadden. En we wilden dat ze voortdurend feature-branches zouden aanmaken. In de meeste gevallen konden we ook niet effectief gebruik maken van pull-aanvragen, wat een spelbreaker was.

Dit heeft 1-2 mensen bijna twee maanden lang beziggehouden, totdat we het (verbijsterende) inzicht kregen dat we dit probleem vanuit de verkeerde richting benaderden.

Eén versie om alles te beheersen


Achteraf bezien snap ik dat onze grootste fout was dat we onze aandacht richtten op de git-flow-tools in plaats van de tools te gebruiken die we al hadden om de workflow te implementeren die we wilden. We hadden:

  • Jira Software
  • Bamboo Server
  • Maven
  • Artifactory Pro

Het bleek dat dat alle tools waren die we nodig hadden.

Een van onze ingenieurs kreeg het zeer briljante idee dat, aangezien het probleem niet aan het buildbeheer zelf lag, maar aan de overschreven artefacten, dat we in plaats daarvan Artifactory moesten repareren. Zijn idee was om een eigenschap van Maven te gebruiken om de URL van de snapshot-repository in te stellen op een aangepaste URL met de Jira-issue-ID, en vervolgens de artefacten te schrijven naar een dynamisch aangemaakte repository in Artifactory met een aangepast sjabloon. De afhankelijkheidsoplosser van Maven zal artefacten vinden in de repository voor ontwikkelingssnapshots vals we ze niet hoeven te branchen, bijvoorbeeld als we alleen aan een product werken en niet ook aan een gemeenschappelijk product.

We hebben die handige kleine eigenschapsvariabele in ons bestand met build-instellingen gezet en een Maven-plug-in geschreven om die te vullen tijdens het eerste deel van de buildcyclus van Maven. Op papier klonk dit ongelooflijk en het gaf het team een nieuwe impuls om harder te werken om dit probleem op te lossen. Het probleem was dat we dit eigenlijk niet konden doen. De vroegste fase van de levenscyclus van Maven is 'valideren'. Tegen de tijd dat de te valideren plug-ins al beschikbaar waren, waren de URL's van de repository al afgesloten. Daarom werd onze variabele nooit ingevuld en heeft de URL helemaal geen branchnaam. Hoewel we een lay-out hadden gebruikt in een andere repository dan de ontwikkelingssnapshots, zou die niet op zichzelf staan voor parallelle ontwikkeling.

Terug naar het bureau, je BESTE VRIEND.

Na een biertje deed de eerdergenoemde ingenieur nog wat diepgravend zoekwerk naar een andere manier om functionaliteit aan Maven toe te voegen: extensies.

„Leve het bier: de oorzaak van en oplossing voor alle problemen in het leven.” - Homer Simpson

Extensies bieden je, net als plug-ins, een hele reeks mogelijkheden om je Maven-workflow te verbeteren, maar ze worden uitgevoerd vóór de levenscyclusdoelen en hebben een betere toegang tot de interne functies van Maven. Door het pakket RepositoryUtils te gebruiken, hebben we Maven gedwongen de URL's opnieuw te evalueren met behulp van een aangepaste parser en ze vervolgens opnieuw in te stellen op basis van onze bijgewerkte waarden.[3]

Nadat we de extensie hadden ingevoerd en getest, begonnen we de ene na de andere premigratietaak uit te voeren, variërend van 'dit gaat nooit gebeuren' tot 'dit gaan we maandag ZEKER doen... dus moet ik uiterlijk morgen tien pagina's aan documentatie schrijven'. Ik zal binnenkort meer schrijven over hoe de tools samenwerken om onze nieuwe ontwikkelingsworkflow te realiseren, en meer vertellen over enkele van de lessen die we over het proces hebben geleerd.

[1]: Een minpunt was dat ik een script moest gebruiken dat ik had geschreven om toegang te krijgen tot de Artifactory REST API om builds te 'promoveren' van staging naar release. Het is snel genoeg, maar ik smeek om meer automatisering.

[2]: Technisch accountmanager Meer informatie vind je hier.

[3]: Na de eerste ontwikkelingspogingen kwamen we erachter dat we nog meer moesten doen om dit 100% van de tijd te laten werken, bijvoorbeeld als een snapshot nieuwer is in Artifactory (van een andere engineer) dan je lokale snapshot, dan pakt Maven het artefact op afstand op, want hé, het is NIEUWER, dus het moet BETER zijn, toch?


Footnotes

[1]: One downside here was that I had to use a script I wrote to hit the Artifactory REST API to "promote" builds from staging to release. It's fast enough, but begging for more automation.

[2]: Technical Account Manager. More information here.

[3]: After the initial development efforts, we found that we had to do even more to make this work 100% of the time, like when a snapshot is newer in Artifactory (from another engineer) than your local snapshot, Maven grabs the remote artifact because hey, it's NEWER, so it must be BETTER, right?

Matt Shelton
Matt Shelton

Matt Shelton is een voorstander en beoefenaar van DevOps die toeziet op de levering van DevOps-diensten (en andere gerelateerde) diensten voor Atlassian in Amerika. Hij blogt nog maar zelden en richt zich op het creëren van de volgende generatie voorstanders voor een betere manier van werken.


Deel dit artikel

Aanbevolen artikelen

Bookmark deze resources voor meer informatie over soorten DevOps-teams of voor voortdurende updates over DevOps bij Atlassian.

Mensen die samenwerken met een muur vol tools

Bitbucket-blog

Toelichting DevOps

DevOps-leertraject

Demo Den Feature-demo's met Atlassian-experts

Hoe Bitbucket Cloud werkt met Atlassian Open DevOps

Meld je aan voor onze DevOps-nieuwsbrief

Thank you for signing up