Git o SVN? In che modo Nuance Healthcare ha scelto un modello di creazione di branch Git
Matt Shelton
Developer Advocate
Guest post di Matt Shelton di Nuance Healthcare. Questo è il primo di una serie di post dedicata al passaggio del suo team da Subversion a Git, ai motivi alla base della transizione e agli ostacoli incontrati lungo il percorso. Matt terrà inoltre un intervento su questo argomento all'Atlassian Summit 2015. Questa serie include tutto ciò che non è riuscito a dire nei 30 minuti del suo intervento, con un contesto ulteriormente approfondito.
Background
Il mio team lavora nella divisione sanitaria di Nuance. Siamo distribuiti geograficamente e lavoriamo sia in ufficio che da remoto sulla costa orientale degli Stati Uniti e a Pune. Sviluppiamo servizi Web Java per distribuire soluzioni NLP[1] sul mercato sanitario.
Per la maggior parte, i consumatori dei nostri servizi sono altre società di software sanitari (inclusi noi stessi), come i fornitori di cartelle cliniche elettroniche e società di analisi sanitarie. Vendiamo alcuni prodotti direttamente agli ospedali e gli utenti finali delle nostre applicazioni spaziano dai medici al personale addetto alla fatturazione delle prestazioni mediche. Le persone "normali" come me e te non interagiscono mai con il software creato dal mio team.
Il nostro team ha provato piuttosto a lungo le combinazioni di prodotti di gestione del ciclo di vita delle applicazioni. Abbiamo iniziato con un mix di Rally Enterprise e Seapine TestTrack Pro, abbiamo usato per circa 14 mesi Rational Team Concert e alla fine abbiamo effettuato la migrazione completa allo stack Atlassian (Jira, Confluence, Bamboo, Crucible e Bitbucket). In generale, usavamo Subversion 1.4/1.5 come SCM con una struttura di trunk/branch/tag quasi normale. Utilizziamo Maven da sempre per gestire i progetti di build e le dipendenze, e qualche tempo fa siamo passati da Jenkins a Bamboo per la continuous integration (CI) per sfruttare le integrazioni più strette con Jira e le flessibili funzionalità di agenti di build e distribuzione. Tutti gli strumenti che utilizziamo (adesso) sono di installazione interna per vari motivi[2].
materiale correlato
Come spostare un repository Git completo
Scopri la soluzione
Impara a utilizzare Git con Bitbucket Cloud
Git o SVN?
Supportiamo circa dieci prodotti singoli in quattro famiglie di prodotti e i responsabili di questi prodotti sono sempre in lotta per stabilire priorità e tempistiche. È bello che il nostro lavoro sia molto richiesto, e la mia non è affatto una lamentela, ma tutta questa situazione richiede anche rilasci più brevi a cadenze strane e può imporre cambiamenti di direzione nel bel mezzo degli sprint[3].
Il nostro processo di sviluppo sembrava a volte davvero proibitivo. Io e il mio team ci ritrovavamo ad avere regolarmente la stessa discussione, che era più o meno questa:
Io: Ora dobbiamo rilasciare la versione 1.8.0 al team di controllo di qualità per i test di regressione in modo che il foo del cliente possa passare alla versione beta la prossima settimana. Sviluppo: Stiamo ancora lavorando su ABC-123, che si trova nel trunk. Non è ancora finito. Io: Per il foo non serve ABC-123. Possiamo inserirlo nel prossimo rilascio. Sviluppo: Ma ci stiamo lavorando da settimane. Non c'è un punto preciso da cui creare un branch per effettuare un rilascio. Io: Beh, dovrete eseguire un pull manuale di tutte le modifiche. Avete circa due ore o altrimenti il controllo di qualità non potrà finire in tempo.
Lo so, sembro un rompiscatole! Non era nelle mie intenzioni comportarmi così, e ovviamente sto esagerando un po' per rendere l'idea, ma dovevamo davvero capire come estrarre temporaneamente il codice da una posizione per effettuare un rilascio e poi inserirlo nuovamente al suo posto per il rilascio successivo[4]. E questo succedeva sempre.
Ora, so che si potrebbe pensare che le sottoversioni supportano i branch. E infatti è assolutamente così, e le abbiamo usate occasionalmente con SVN 1.4 e 1.5. La creazione di branch è un'ottima operazione in SVN; il merge può essere una scocciatura. Con il passare del tempo, SVN è sicuramente migliorato. Ma sapevamo che c'erano opzioni migliori per le nostre esigenze, quindi quando si è presentata la necessità di scegliere tra SCN o git, abbiamo deciso di scegliere git.
Una nota a margine: abbiamo dato un'occhiata veloce all'ultima versione di SVN (1.8 all'epoca) per vedere se era abbastanza potente da risolvere i nostri problemi, ma non siamo rimasti completamente soddisfatti. Uno dei team nostri colleghi ha una configurazione Perforce di grandi dimensioni e lo strumento aveva molto di ciò di cui avevamo bisogno, ma semplicemente non riuscivo a sopportare i costi delle licenze. Abbiamo anche pensato a Mercurial per un momento, ma alla fine, l'esposizione del team esistente a Git è stata sufficiente per dirci che era quella la direzione giusta.
Non ho intenzione di edulcorare la mia opinione: gli strumenti di Atlassian sono davvero utili per i team che usano git. Altri SCM offrono buone prestazioni; la nostra integrazione con SVN ha avuto prestazioni accettabili nel senso che ci ha collegato al punto in cui sono state apportate delle modifiche a una determinata storia utente. Le funzionalità di integrazione per i team che utilizzano Bitbucket[5], tuttavia, sono sia più forti che più intuitive per quanto riguarda l'interfaccia e l'esperienza di sviluppo di Jira Software e lo stesso vale per Bamboo.
Partendo da questa consapevolezza, e avendo visto alcune demo eccezionali al Summit 2013, ho fortemente incoraggiato il team ad adottare questo strumento. Nessuno si è opposto e avevamo già le licenze per affrontare il cambiamento.
Scelta di un modello di creazione di branch Git
Dopo aver deciso di cambiare, la prima sfida che abbiamo dovuto affrontare è stata decidere quale modello di creazione di branch Git implementare per il nostro team. Il microsito Git di Atlassian e questa fantastica presentazione del Summit 2013 spiegano più nei dettagli cos'è un modello di creazione di branch. La versione breve è che questo modello descrive il modo in cui vengono utilizzati i branch in git per potenziare il flusso di lavoro di sviluppo.
In SVN, era disponibile un modello di creazione di branch che chiamerò "crea un branch quando ti rendi conto che te ne serve uno disperatamente":
- Il codice più recente è nel
trunk
. I rilasci dal trunk saranno numerati in questo modo:A.B.0-{build}
. - Se serve una correzione per un rilascio basato su trunk (ad es. c'è un bug nel rilascio 1.2.0-64), viene creato un branch e da lì vengono effettuati i rilasci
A.B.C-{build}
, doveC
aumenta di rilascio in rilascio. Questi branch potrebbero non esistere mai per un determinato rilascioA.B
oppure potrebbe essercene anche più di uno. - Ogni rilascio viene inoltre taggato in una directory dei tag.
Un inciso sulle versioni Molti anni fa, quando mi stavo facendo le ossa con la gestione di un team di sviluppo, il nostro Release Engineer usava un sistema di controllo delle versioni che era... come posso dire?... Davvero poco intuitivo. In sostanza, ogni rilascio era una patch di quello precedente (A.B.n), senza alcuna considerazione per il punto da cui proveniva tale patch. Per capire da dove provenisse qualcosa e, in quasi tutti i casi, per comprendere l'ordine di rilascio, era necessario consultare svn log
. Avevamo stampato la struttura ad albero e l'avevamo appesa alla parete per usarla come riferimento. Inoltre, i nostri numeri di rilascio rivolti agli utenti tendono a seguire uno schema di denominazione simile a 3.0, 3.1, 3.5, 4.0 o essenzialmente uno schema facilmente prevedibile dai clienti. Ricorda, però, che il mio team compila servizi Web, non un prodotto confezionato. Le nostre API sono un contratto. Qualche anno fa ho stabilito che le build e, di conseguenza, i rilasci del mio team seguissero le regole del controllo delle versioni semantico. Ho dovuto mantenere la mia posizione un paio di volte con i vertici aziendali, ma ora è chiaro perché le regole sono quelle che sono e non siamo mai tornati indietro sui nostri passi. I partner apprezzano questo tipo di chiarezza.
Prima ho accennato a uno scenario in cui durante la preparazione di un rilascio (ad esempio il rilascio 1.2.0
) ci ritrovavamo con una funzione ancora in corso con l'avvicinarsi della data di rilascio. In questo caso, dovevamo eseguire un pull del codice, effettuare il rilascio, creare un branch a branches/1.2.1
e poi eseguire nuovamente il merge del codice, sperando che nel frattempo non si fossero verificati arresti anomali del disco rigido[6].
Rimuovere un'intera funzione da un trunk condiviso non è semplice. A nessuno piaceva farlo. svn blame
può essere utile, così come un potente strumento di rilevamento delle differenze, ma è comunque fastidioso utilizzarlo. Spesso l'ho presa sul personale, ritenendo che la mia cattiva pianificazione ci avesse portato a non avere tutte le carte in regola prima che arrivasse il momento di concludere un rilascio[7]. Il mio team si è ritrovato a gestire questo tipo di situazioni per un periodo di tempo abbastanza lungo.
A volte apportavamo un numero eccessivo di correzioni per evitare scocciature e chiedevamo agli sviluppatori di non andare avanti con il lavoro per un paio di giorni (una specie di blocco del codice, per così dire) solo per non inquinare il trunk prima di un rilascio.
Quindi sapevamo di aver bisogno, almeno, dei branch di funzioni. C'è un semplice modello di creazione di branch Git applicabile in questo caso: un branch principale per gli elementi in produzione e branch di funzioni per ogni funzione, bug e così via. I team devono gestire l'ordine di merge per assicurarsi che ciò che viene rilasciato nel branch principale sia ciò che deve effettivamente trovarsi nel rilascio. Questa è essenzialmente la stessa cosa di prima, con un migliore isolamento delle funzioni, ma volevamo essere in grado di usare il nostro potere in libertà.
Nel nostro ambiente, abbiamo spesso bisogno di mantenere alcune versioni in produzione, e potrebbe essere necessario correggere i difetti di un rilascio corrispondente a 2-3 revisioni minori precedenti rispetto a quello su cui stiamo lavorando attualmente. Quindi, oltre ai branch di funzioni, avevamo anche bisogno di una sorta di branch di rilascio che ci permettesse di correggere i problemi dei rilasci precedenti. Questo team apporta correzioni nei branch di assistenza di lunga esecuzione, per poi effettuarne il merge nel flusso del branch in modo che la correzione arrivi a tutti i flussi di assistenza.
Il modello del team sembrava davvero valido e abbiamo eseguito alcuni prototipi di interazioni con tale modello per vedere se fosse adatto alle nostre esigenze. Il punto di forza del team è il merge continuo delle correzioni nel branch di sviluppo. Anche se ci piaceva questo concetto, ogni volta che l'abbiamo provato, ci siamo imbattuti in un qualche problema a causa delle nostre dipendenze di Maven. Inoltre, in linea di massima, non potevamo avere la sicurezza di volere un merge diretto del lavoro da una versione all'altra perché in alcuni casi avevamo bisogno di implementare la stessa correzione in modi leggermente diversi tra le versioni.
Alcuni membri del team hanno fortemente preferito una variante di questo modello, nota come "git-flow". Git-flow è un insieme di convenzioni di denominazione dei branch e di linee guida per il merge scritto da Vincent Driessen. Adottare questo modello è stato un passo naturale e ne abbiamo inoltre apprezzato la struttura che ci ha consentito di eliminare molte delle domande procedurali: le risposte erano generalmente abbastanza ovvie. Leggi il tutorial di Atlassian per scoprire di più su git-flow.
L'unica lacuna di git-flow riguardava i rilasci a lunga esecuzione nell'ambiente di produzione. Poiché il branch principale non si arresta mai, non è stato possibile utilizzare il flusso di lavoro di correzione rapida di git-flow per la correzione dei bug di un rilascio precedente. D'altra parte, non abbiamo sempre voluto un branch di assistenza.
Il più delle volte dovrebbe bastare una correzione rapida, per applicare una patch soltanto all'ultimo rilascio nell'ambiente di produzione; il branch di assistenza è disponibile solo quando è necessario tornare ancora più indietro nei rilasci o se, per un motivo o per un altro, occorre mantenere la compatibilità. Abbiamo analizzato ulteriormente quest'ultimo caso d'uso e abbiamo individuato i criteri in base a cui si sceglie di utilizzare un branch di assistenza piuttosto che una correzione rapida e l'upgrade di una versione secondaria:
1. Non è possibile effettuare banalmente il merge di questo codice nell'ambiente di sviluppo.
2. Il partner/cliente non è in grado di gestire una modifica dell'interfaccia distribuita con l'ultimo rilascio.
3. È presente una dipendenza interna che non può essere modificata.[8]
Entrambi i pacchetti di estensione di git-flow[9] supportano il concetto del branch di assistenza, che non fa parte della bozza originale di git-flow, ma che è diventato abbastanza popolare da giustificarne l'inclusione.
Oltre a un flusso di lavoro adatto alle nostre esigenze, git-flow offriva anche l'assistenza per gli strumenti di cui avevamo bisogno. Nel prossimo post parlerò di cosa è successo quando abbiamo effettivamente provato a usarlo in un progetto POC utilizzato per rappresentare il nostro processo di sviluppo. È stata... un'esperienza di apprendimento!
[1]: Natural Language Processing (elaborazione del linguaggio naturale). POSSIAMO LEGGERE I TUOI PENSIERI. (No. Non proprio).
[2]: le offerte cloud di Atlassian presentano molti aspetti interessanti, ma per il momento dobbiamo tenere sotto stretto controllo i server e i dati. Anche se la nostra azienda non fa un grande uso delle informazioni sanitarie protette, il nostro software invece sì ed è importante mantenere queste informazioni il più al sicuro possibile.
[3]: shhhh... non dirlo a Ken Schwaber.
[4]: che si sarebbe comunque potuto verificare solo pochi giorni dopo.
[5]: in precedenza noto come Stash (prima del rebranding autunnale di Atlassian).
[6]: so che potevamo sempre eseguirne un pull dal commit precedente. Stavo scherzando.
[7]: di solito non era questo il caso: in genere il problema dipendeva dal fatto che le tempistiche di qualcun altro cambiavano e noi dovevamo reagire rapidamente.
[8]: questa è una di quelle cose che non posso approfondire sul mio blog. Fidati. "Motivi".
[9]: il pacchetto originale di Vincent Driessen non è più in manutenzione. Un nuovo fork, tuttavia, viene aggiornato regolarmente.
Footnotes
[1]: Natural Language Processing. WE CAN READ YOUR THOUGHTS. (No. Not really.)
[2]: There is a lot that is attractive about Atlassian's cloud offerings, but we need to keep our fingers wrapped tightly around our servers and data for the time being. While we don't personally need to do much with PHI data, our software does and it's important to keep it as secure as possible.
[3]: Shhhh... don't tell Ken Schwaber.
[4]: Which might have only been a few days later anyway.
[5]: Formerly known as Stash. Hello, Atlassian Fall Rebranding!
[6]: I know we could always pull it out of the previous commit. I was kidding.
[7]: This wasn't usually the case - generally it was because someone else's timeframe moved up and we had to react quickly.
[8]: This is one of those things I can't get into on my own blog. Just trust me. "Reasons".
[9]: The original package by Vincent Driessen isn't being maintained any longer. A new fork, however, is regularly updated.
Condividi l'articolo
Argomento successivo
Letture consigliate
Aggiungi ai preferiti queste risorse per ricevere informazioni sui tipi di team DevOps e aggiornamenti continui su DevOps in Atlassian.