L'integrazione continua implica un repository VCS monolitico?

7

Io faccio parte di un piccolo team che sviluppa diverse app interne per la nostra azienda. Siamo in procinto di diventare più Agili, questo include in particolare molti test automatizzati. Ora ci troviamo in una situazione in cui, per una o due app su cui abbiamo lavorato più di recente, generalmente apportiamo piccole modifiche reversibili, eseguiamo numerosi test e distribuiamo abbastanza rapidamente con un intervento non molto umano.

Considererei che siamo ancora molto lontani dal fare CI "reali". Per una o due app sopra menzionate probabilmente ci avvicineremo molto presto. Tuttavia, sto avendo difficoltà a immaginare come sarà il nostro setup, a volte in futuro dove abbiamo iniziato a utilizzare CI per la maggior parte del nostro codice legacy.

Supponiamo di avere diverse app standalone, che possono essere distribuite in modo indipendente su server diversi. Abbiamo anche del codice condiviso che è usato da molti di loro e che vogliamo usare in modo coerente tra loro. Ciò include funzioni di utilità, codice che applica elementi come un'interfaccia coerente su alcune parti di tutte le app e le definizioni ORM per il nostro database (condivisa tra tutte le app).

Vedo due alternative, nessuna delle due sembra molto semplice o elegante:

  • Tutto il codice viene unito in un enorme repository. Implementazione significa eseguire tutti i test per tutto il codice, test di integrazione per tutto e test di accettazione per tutte le app, prima di lanciare tutto in una volta sola. Questo sembra rendere l'implementazione un affare molto più grande di quanto non fosse in precedenza, contrariamente alla filosofia di CI, che suggerisce che dovrebbe essere veloce e facile. Significa anche che non abbiamo alcuna separazione tra parti diverse della nostra base di codice, con cose che non funzionano mai insieme nello stesso repository per sempre, solo perché entrambi dipendono da una terza cosa.
  • Manteniamo separate ciascuna app e ogni componente del codice condiviso. Implementazione significa testare la versione più recente di un componente estensivamente, prima di "rilasciarlo" su un sistema funzionante costituito da tutti gli altri componenti. Questo sembra un design più pulito. Tuttavia, sembra implicare che dobbiamo gestire le dipendenze e il controllo delle versioni per tutte queste cose. Ogni test di integrazione / accettazione deve avere una certa conoscenza di quali versioni degli altri componenti verrà utilizzata e tollerabile. In altre parole, anche se ogni componente diventa molto più affidabile, dobbiamo preoccuparci molto di più su come i pezzi si incastrano tra loro e sui bug di integrazione. Quando i pezzi che dipendono da tutto il resto cambiano, c'è potenziale per la rottura e ovunque.

La via d'uscita da questo dilemma è probabilmente quella di fare una versione limp dell'IC, dove i singoli componenti vengono testati estensivamente e distribuiti rapidamente, ma ci limitiamo ad avere grandi versioni "flag day" delle utility e dello schema del database. Ma sembra davvero che ci siano molti vantaggi di un corretto CI / CD che ci staremmo perdendo. Non è una soluzione esteticamente piacevole, e non stiamo cercando di soddisfare i nostri capi che abbiamo spuntato una scatola, ma vogliamo migliorare le nostre pratiche di lavoro.

Come dovremmo organizzare il codice per un CI corretto e quali sono le lezioni chiave per imparare a pianificare e progettare l'architettura, sia per il codice legacy che per il futuro codice appena scritto?

    
posta jwg 25.09.2015 - 00:55
fonte

3 risposte

6

Immagina il tuo codice non come un sistema monolitico, ma piuttosto come una serie di pacchetti. Alcuni pacchetti dipendono dagli altri. Alcuni, come jQuery, sono esterni alla tua azienda; altri sono sviluppati dalla tua azienda e resi pubblici; altri, sviluppati dalla tua azienda, vengono infine resi privati.

Ad esempio, se sviluppi un'applicazione web in Python, potresti avere una dipendenza da Flask, un popolare framework web Python, e da una serie di altri pacchetti, sia esterni che interni.

Che cosa succede al tuo CI quando gli sviluppatori di Flask rilasciano una nuova versione? Giusto, niente. Spetta a te andare e modificare il tuo file di progetto che dice che d'ora in poi, non stai usando la versione x di Flask, ma piuttosto la versione y . Una volta che lo fai, CI considera che il progetto tuo è cambiato, e lancia tutti i test che assicurano che l'applicazione funzioni ancora con la nuova versione di Flask. In caso contrario, probabilmente correggerai la tua applicazione, tranne in casi molto rari in cui in realtà rivelerai un bug in Flask stessa.

La stessa logica può essere applicata a qualsiasi codice prodotto all'interno dell'azienda. Una libreria condivisa diventa un pacchetto Python, condiviso attraverso pypi -il repository di pacchetti pubblici per Python, o conservato su un server pip privato che può essere usato solo all'interno della tua azienda.

Quindi rende particolarmente chiaro cosa ha rotto la build. Quando pubblichi la nuova versione di un pacchetto A, CI esegue i test corrispondenti e indica se la versione contiene regressioni o meno. Quindi, se si verifica un problema nella fase in cui si chiede a un pacchetto B di utilizzare la nuova versione del pacchetto A, è il pacchetto B che ha rotto la build essendo incompatibile con la nuova versione del pacchetto A: allo stesso modo , la tua app potrebbe non essere compatibile con una versione più recente di Flask o jQuery.

Si noti che non è necessario gestire realmente le dipendenze da soli: il sistema di confezionamento lo fa per voi. L'unico problema che richiede il tuo intervento è l'aggiornamento dei riferimenti, ovvero l'azione di dire che un determinato pacchetto utilizzerà una versione diversa di un altro pacchetto: se hai modificato un pacchetto che è molto usato nella tua base di codice, potrebbe prenditi un po 'di tempo per tracciare e modificare tutti i progetti che lo utilizzano.

Per quanto riguarda il controllo della versione, non importa. Potresti avere un team che lavora con Git e un altro che lavora con SVN. Non appena i due team concordano sull'uso di pypi e scegliendo pip server (s) specifici per i pacchetti interni, tutto andrà bene. Allo stesso modo, non ti importa se gli sviluppatori di Flask o jQuery usano Git, SVN, SourceSafe o TFS.

Nota: ho usato un esempio dal mondo Python, ma la stessa logica può essere applicata anche ad altre lingue. C'è npm per Node.js, NuGet per i linguaggi .NET Framework, ecc. Puoi anche configurare gestori di pacchetti privati.

Ulteriori letture: Pacchetti, dipendenze e interazione tra team , il mio articolo basato su questa domanda.

¹ Nulla ti obbliga ad avere un unico server di pacchetti nella tua azienda. Diversi team possono implementare i propri server; l'unico vincolo è che gli altri team dovrebbero conoscere la posizione del server per poter utilizzare i pacchetti.

    
risposta data 25.09.2015 - 01:25
fonte
1

I sistemi CI supportano l'organizzazione del lavoro che svolgono nei progetti. Quindi, puoi creare tanti diversi progetti di integrazione di cui hai bisogno e ognuno di essi avrà un insieme completamente diverso di preferenze, tra cui, ovviamente, quale root del repository da cui prelevare, dove distribuire, ecc.

Detto questo, se si dispone di moduli con dipendenze strette l'uno sull'altro, questi moduli dovrebbero idealmente essere nello stesso repository.

Per "dipendenze strette" non intendo qualcosa di specifico, ma come regola per il campo di gioco direi che se hai bisogno di eseguire test di integrazione (al contrario dei test unitari) tra di loro, allora probabilmente vale la pena chiamarlo una stretta dipendenza .

Con "idealmente" intendo che i vantaggi di tenerli nello stesso repository di solito superano gli svantaggi. Ma ovviamente conosci meglio il tuo codice, quindi spetta a te decidere.

    
risposta data 25.09.2015 - 01:40
fonte
1

Potresti anche lavorare con una "scaletta" di tutti i tuoi repository di singoli componenti (la tua seconda alternativa) in modo monolitico (vedi la mia risposta a questo Q per un possibile approccio), trattandoli come un singolo mega-progetto, che in un certo senso ti permetterebbe di ignorare il problema della gestione le dipendenze tra componenti (IMHO molto più complesso).

Ogni modifica ai componenti di codice condivisi verrebbe considerata influenzando tutti i componenti che dipendono da essi. Ciò ti consentirebbe di concentrarti sul test delle app reali invece di eseguire test approfonditi sui componenti condivisi (che IMHO non può sempre garantire per le app che usano quelle condivisioni condivise).

    
risposta data 27.09.2015 - 05:55
fonte