Close

Gestire le dipendenze di Maven durante il passaggio a Git

Primo piano di Nicola Paolucci
Matt Shelton

Developer Advocate


Stiamo affrontando il passaggio a Git e apprezziamo git-flow. E adesso? Testiamo tutto! I membri del mio team sono fantastici. Hanno creato una lista nera dei flussi di lavoro per sviluppatori in Confluence, basandosi interamente sul nostro lavoro come team e su tutte le cose folli che secondo loro avremmo potuto fare in futuro. Quindi, hanno provato ogni flusso di lavoro all'interno di una struttura di progetto che rispecchia la nostra (ma senza codice, solo un pom.xml).

Le dipendenze di Maven stavano per rivelarsi il nostro problema più grande in tutto questo.

Maven Build Numbering Maven produce 1.0.0-SNAPSHOT costruisce fino al rilascio. Quando rilasci, -SNAPSHOT viene rimosso e la tua versione è 1.0.0. Il tuo processo di creazione deve essere in grado di supportare l'incremento della tua versione secondaria in modo che i lavori successivi sul tuo prossimo tentativo producano build come 1.1.0-SNAPSHOT. Non sei vincolato dalle tre cifre: puoi definire questa opzione quando inizi un progetto, ma noi ne usiamo tre. Ad ogni modo, la parte -SNAPSHOT è davvero importante da capire. Rappresenterà sempre l'ultima versione precedente al rilascio di un progetto.

Artefatti


La nostra grande preoccupazione in tutti questi flussi di lavoro era come assicurarci che le versioni dei nostri progetti e le dipendenze tra progetti fossero gestite correttamente.

Ogni volta che le dipendenze di Maven vengono recuperate per una build, per impostazione predefinita, vengono estratte da Ye Olde Internet(e)TM. Questi artefatti sono archiviati a livello locale in modo che le build successive possano essere eseguite più rapidamente. Una soluzione per semplificare questo processo è usare un repository di artefatti sulla rete locale in modo che funga da cache locale per queste dipendenze esterne. Il recupero tramite LAN sarà quasi sempre più veloce dello scaricamento anche dal più veloce dei CDN. Usiamo Artifactory Pro come repository di artefatti. Inoltre, poiché abbiamo una struttura multimodulo, archiviamo i nostri artefatti di build anche in Artifactory. Quando creiamo uno dei nostri pacchetti comuni, possiamo estrarre una versione specifica tramite la risoluzione delle dipendenze di Maven e recuperare l'artefatto direttamente dal repository degli artefatti.

Funziona benissimo. Artifactory ti consente anche di sincronizzare i tuoi artefatti tra le istanze, quindi se volessi, ad esempio, utilizzarlo per replicare il tuo repository di rilascio nei tuoi data center per le implementazioni di produzione, potresti farlo senza dover compilare un processo separato.

Database
materiale correlato

Come spostare un repository Git completo

Logo di Bitbucket
Scopri la soluzione

Impara a utilizzare Git con Bitbucket Cloud

Dipendenze di Maven, branch di funzionalità e pull request


Tutte le nostre build sono salvate in Artifactory. Con SVN, utilizzavamo un repository di istantanee per conservare le ultime 2 build di snapshot, un archivio di staging per tutte le build di rilascio non ancora approvate e un repository di rilascio solo per le build che avevano la fortuna di essere messe in produzione. [1] Queste build sono numerate come ho descritto in precedenza e sono recuperabili mediante uno schema URL prevedibile basato sul repository e sulla versione.

Il flusso di lavoro principale per ogni sviluppatore consisteva nel creare un branch di funzionalità dal branch di sviluppo per il proprio lavoro, completarlo ed effettuare una pull request per riunire nuovamente tale lavoro nel branch di sviluppo. Per un singolo progetto, questo funziona per lo più senza problemi, ma vorrei illustrare il primo problema che abbiamo incontrato e che ci ha fatto riconsiderare seriamente l'intera migrazione:

Come ho detto prima, abbiamo più livelli di dipendenza tra i nostri progetti. Questo è dovuto a un ottimo motivo, sia storicamente che strategicamente, per i nostri prodotti. Abbiamo preso in considerazione architetture alternative per eliminare questo problema, ma queste introdurrebbero altre difficoltà. Possiamo semplificarci la vita (e l'abbiamo fatto, ma ne parleremo in un post successivo), ma per ora è di importanza strategica per noi mantenere la nostra struttura così com'è.

Quindi la sviluppatrice A, chiamiamola Angela, inizia a lavorare su una funzionalità di Jira. Ciò richiede due branch: uno dal nostro progetto comune e uno dal prodotto X. La versione comune è 2.1.0-SNAPSHOT. La versione per prodottoX è 2.4.0-SNAPSHOT. Angela lavora a livello locale per un po' e infine esegue il backup su Bitbucket Server. Bamboo raccoglie queste modifiche, crea il pacchetto comune e carica common-2.1.0-SNAPSHOT in Artifactory, quindi crea prodottoX con una dipendenza da common-2.1.0-SNAPSHOT, caricando anche prodottoX-2.4.0-Snapshot. Gli unit test vengono superati!

Lo sviluppatore B, chiamiamolo Bruce, inizia a lavorare su un'altra funzionalità di Jira, per un prodotto diverso: prodottoY. Anche questo richiede due branch: uno del nostro progetto comune e uno di prodottoY. La versione comune è, come prima, 2.1.0-SNAPSHOT. La versione del prodotto Y è 2.7.0-SNAPSHOT. Bruce lavora a livello locale per un po' e infine salva le sue modifiche su Bitbucket Server. Bamboo raccoglie queste modifiche, crea il pacchetto comune e carica common-2.1.0-SNAPSHOT in Artifactory, quindi crea prodottoX con una dipendenza da common-2.1.0-SNAPSHOT, caricando anche prodottoX-2.4.0-Snapshot. Gli unit test vengono superati!

Angela, nel frattempo, trova un piccolo bug nel codice di prodottoX e scrive un unit test per convalidare la sua correzione. Lo gestisce a livello locale e il test ha esito positivo. Passa le sue modifiche a Bitbucket e Bamboo raccoglie le modifiche e crea prodottoX. La build ha successo, ma alcuni unit test hanno esito negativo. Non si tratta dei nuovi test appena compilati, ma dei primi, tratti dalle sue modifiche iniziali alla funzione. In qualche modo la build Bamboo ha trovato una regressione non rilevata dalla sua build locale? Come è possibile?

La sua dipendenza comune, quella che Bamboo ha rilevato quando ha creato prodottoX, non era più la sua copia. Bruce ha sovrascritto common-2.1.0-SNAPSHOT in Artifactory quando la build della sua funzionalità è stata completata. Non c'è stato conflitto con il codice sorgente: entrambi gli sviluppatori lavoravano isolatamente sui propri branch, ma la fonte della verità per il recupero degli artefatti di Maven era danneggiata.

Head. Incontro con i tecnici.

Per circa un mese dopo aver scoperto questo problema, abbiamo fatto di tutto per aggirarlo. Tramite il nostro TAM[2], abbiamo parlato con persone del team Bamboo che usano git-flow e abbiamo parlato con lo sviluppatore che gestisce git-flow, un'implementazione java di git-flow. Sono stati tutti molto utili, ma, a eccezione di un processo che richiedeva un elenco di passaggi manuali per ogni sviluppatore ogni volta che lavorava su una funzionalità, non siamo riusciti a trovare una risoluzione tollerabile.

Se sei curioso di sapere cosa abbiamo considerato, ecco tutto ciò che abbiamo provato:

1. Modifica del numero di versione al momento della creazione del branch o subito dopo.

  • Possiamo farlo con mvn jgitflow:feature-start per creare il branch.
  • Possiamo usare un hook di Bitbucket Server o un githook locale.
  • Possiamo eseguire l'impostazione manuale con mvn version:set-version dopo aver creato il branch.
  • Possiamo automatizzare la modifica con il plugin [maven-version esterna].

2. Modificare il numero di versione quando si completa il branch ed eseguire nuovamente la fusione per svilupparlo.

  • Possiamo farlo con mvn jgitflow:feature-finish per completare il branch.
  • Usare un driver git merge per gestire i conflitti pom.
  • Usare un hook post-ricezione asincrono in Bitbucket Server

3. Fare tutto manualmente. (ovviamente non abbiamo considerato questa opzione molto a lungo).

Possibili flussi di lavoro


Tenendo a mente questo concetto fondamentale e riflettendoci sopra, puoi comprendere che i sottomoduli (submodule) supportano bene alcuni flussi di lavoro e altri in modo meno ottimale. Esistono almeno tre scenari in cui i sottomoduli rappresentano una scelta corretta:

  • Quando un componente o un sottoprogetto cambia troppo velocemente o le modifiche imminenti interromperanno l'API, puoi bloccare il codice su un commit specifico per ragioni di sicurezza.

  • Quando c'è un componente che non viene aggiornato molto spesso e che desideri monitorare come dipendenza del fornitore. Io procedo così per i plugin vim, ad esempio.

  • Quando deleghi una porzione del progetto a una terza parte e desideri integrare il suo lavoro in un momento o in un rilascio specifico. Ripeto: questo procedimento funziona quando gli aggiornamenti non sono troppo frequenti.

Ringrazio finch per le ottime spiegazioni degli scenari.

Ognuna di queste opzioni aveva una sorta di effetto collaterale negativo. Principalmente, si trattava di passaggi manuali per uno sviluppatore ogni volta che aveva bisogno di un branch di funzionalità. E volevamo che gli sviluppatori creassero continuamente branch di funzionalità. Inoltre, nella maggior parte dei casi non siamo riusciti a utilizzare efficacemente le pull request, il che è stato un problema.

Tutto questo ha divorato 1-2 persone per quasi due mesi fino a quando non abbiamo avuto una (strabiliante) rivelazione sul motivo per cui stavamo affrontando questo problema dalla direzione sbagliata.

Una versione per dominarli tutti


Con il senno di poi, ora vedo chiaramente che il nostro errore più grande è stato quello di concentrare la nostra attenzione sugli strumenti git-flow piuttosto che utilizzare gli strumenti che avevamo già per implementare il flusso di lavoro che volevamo. Avevamo:

  • Jira Software
  • Bamboo Server
  • Maven
  • Artifactory Pro

A quanto pare, quelli erano tutti gli strumenti di cui avevamo bisogno.

Uno dei nostri ingegneri ha avuto la brillante idea che, poiché il problema non era la gestione della build in sé ma piuttosto la sovrascrittura degli artefatti, avremmo dovuto invece sistemare Artifactory. La sua idea era di utilizzare una proprietà Maven per impostare l'URL del repository di istantanee su un URL personalizzato che includesse l'ID del problema Jira, e poi scriverne gli artefatti in un repository creato dinamicamente in Artifactory con un modello personalizzato. Il risolutore di dipendenze di Maven troverà gli artefatti nel repository degli snapshot di sviluppo se non abbiamo avuto bisogno di ramificarli, ad esempio se stiamo lavorando solo su un prodotto che non è anche comune.

Abbiamo impostato quella piccola e utile variabile di proprietà nel nostro file delle impostazioni di build e abbiamo scritto un plugin Maven per popolarla durante la prima parte del ciclo di vita della build di Maven. Sulla carta, sembrava incredibile e ha spronato nuovamente il team a lavorare di più per risolvere questo problema. Il problema era che in realtà non potevamo farlo. La prima fase del ciclo di vita di Maven è la «convalida». Quando i plugin destinati alla convalida sono stati eseguiti, gli URL del repository erano già stati risolti. Per questo motivo, la nostra variabile non è mai stata compilata e l'URL non ha un nome di branch, dopotutto. Anche se avevamo utilizzato un layout in un repository separato dalle nostre istantanee di sviluppo, questo non sarebbe stato isolato per lo sviluppo parallelo.

Head. Nuovo incontro con il tecnico: il tuo MIGLIORE AMICO.

Dopo una birra, il suddetto ingegnere ha fatto ulteriori ricerche e ricerche su un altro modo per aggiungere funzionalità a Maven: le estensioni.

«Alla birra: la causa e la soluzione di tutti i problemi della vita». - Homer Simpson

Le estensioni, come i plugin, ti offrono tutta la potenza necessaria per migliorare il tuo flusso di lavoro Maven, tuttavia vengono eseguite prima degli obiettivi del ciclo di vita e hanno un maggiore accesso ai componenti interni di Maven. Utilizzando il pacchetto RepositoryUtils, abbiamo costretto Maven a rivalutare i suoi URL utilizzando un parser personalizzato e poi a reimpostarli utilizzando i nostri valori aggiornati. [3]

Estensione in atto e testata, abbiamo iniziato a interrompere le attività pre-migrazione una dopo l'altra, passando da "questo non succederà mai" a "questo accadrà lunedì... così ora devo scrivere dieci pagine di documentazione entro domani". A breve pubblicherò un articolo su come gli strumenti collaborano per realizzare il nostro nuovo flusso di lavoro di sviluppo e su alcune delle lezioni che abbiamo appreso sul processo.

[1]: Uno svantaggio è che ho dovuto usare uno script che ho scritto per accedere all'API REST di Artifactory per "promuovere" le build dallo staging al rilascio. È abbastanza veloce, ma richiede maggiore automazione.

[2]: Technical Account Manager. Puoi trovare ulteriori informazioni qui.

[3]: Dopo gli sforzi iniziali di sviluppo, abbiamo scoperto che dovevamo fare ancora di più per farlo funzionare il 100% delle volte, ad esempio quando un'istantanea è più recente in Artifactory (di un altro ingegnere) rispetto alla tua istantanea locale, Maven afferra l'artefatto remoto perché è PIÙ RECENTE, quindi deve essere MIGLIORE, giusto?


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 è un sostenitore e professionista DevOps che supervisiona la fornitura di offerte di servizi DevOps (e altre offerte correlate) per Atlassian nelle Americhe. Ormai scrive raramente sul suo blog e punta a creare la prossima generazione di sostenitori di modi migliori di lavorare.


Condividi l'articolo

Letture consigliate

Aggiungi ai preferiti queste risorse per ricevere informazioni sui tipi di team DevOps e aggiornamenti continui su DevOps in Atlassian.

Le persone collaborano utilizzando una parete piena di strumenti

Blog di Bitbucket

Illustrazione su Devops

Percorso di apprendimento DevOps

Funzione Demo Den per demo con esperti Atlassian

Come Bitbucket Cloud funziona con Atlassian Open DevOps

Iscriviti alla nostra newsletter DevOps

Thank you for signing up