Introduzione
In questo articolo, e negli altri che saranno pubblicati in futuro, affronteremo degli argomenti con un taglio “trasversale” che tenga in considerazione gli aspetti tecnologici, quelli organizzativi e di processo, e anche quelli metodologici legati allo sviluppo rapido di software. Per iniziare, parleremo brevemente di TDD e, soprattutto, di Continuous Integration valutandone l’impatto sui diversi dipartimenti di una grande azienda.
In situazioni enterprise laddove la complessità organizzativa sia decisamente maggiore rispetto alla microimpresa in cui lavorano magari solo 10 persone, dobbiamo spesso confrontarci con il problema dei dipartimenti. Chi ha esperienza di questa situazione, sa bene che in certi ambiti, come ad esempio istituti bancari e assicurativi, industrie manifatturiere con uffici di progettazione, logistica e vendite, nonche’ grandi gruppi dell’hi-tech o dell’IT, succede spesso che intere sezioni di un’azienda quasi “non sappiano” quel che fa il dipartimento accanto…
Esula da questo articolo esaminare le ragioni per cui ciò avvenga (tipologia di organizzazione impostata sui vecchi criteri della produzione fordista, ragioni “politiche” di potere dei vari “feudatari” all’interno dell’azienda, il classico “si è sempre fatto così…”), ma di fatto una maggiore condivisione di visione, metodologie e conoscenze tra i diversi settori non potrebbe che aiutare l’azienda a crescere e prosperare.
“Sviluppo” e “operazioni”
Una delle suddivisioni tipiche nelle aziende che debbano in qualche modo sviluppare infrastrutture e applicazioni software è quella tra le unità di sviluppo e quelle operative. Intendiamo per sviluppo, generalmente indicato con il termine inglese di development (abbreviato spesso in dev), quel settore, quel dipartimento, quei gruppi di lavoro che hanno come compito fondamentale la progettazione e lo sviluppo del software e delle tecnologie ad esso correlate. Con il termine inglese di operations, che ormai è entrato stabilmente nel linguaggio aziendale anche nell’abbreviazione ops, si intendono invece tutte quelle funzioni tramite le quali l’impresa riesce a mettere a disposizione del cliente il prodotto/servizio: nel caso di un prodotto “fisico” può essere la linea di produzione, l’impiantistca, ma anche la logistica, il supporto al cliente, e così via.
Tradizionalmente, development e operations sono state considerate attività separate e la loro complementarietà è stata più esaltata a parole che messa in pratica nei fatti. E invece, è proprio nelle differenze e nella separazione tra dev e ops che occorre andare a cercare le ragioni per certi problemi e certi fallimenti, ribaltando l’approccio tradizionale e comprendendo come il vantaggio competitivo di una grande azienda possa risiedere proprio nella maggiore e più stretta interazione tra le due divisioni. In un approccio olistico, un’azienda funziona bene se ha una comune visione dei propri obiettivi e dei modi in cui raggiungerli; spingere affinche’ un settore funzioni bene indipendentemente dagli altri non è assolutamente una chiave di successo: magari la divisione sviluppo potrà funzionare perfettamente, e magari andrà ottimamente quella operations… ma se le due aree non si integrano, si va poco lontano… Con la classica, banale, abusata metafora calcistica: posso avere un insieme di ottimi giocatori nel mio club, ma se non si allenano e giocano in maniera coordinata, come squadra, forniranno prestazioni decisamente inferiori a quelle ottimali.
DevOps
È anche da queste considerazioni che nasce la metodologia di sviluppo software DevOps che si incentra proprio sul favorire comunicazione e collaborazione tra sviluppatori e addetti alle operations, per favorire l’integrazione, e per migliorare il rilascio frequente di software. Al di là delle considerazioni di tipo organizzativo e “umano” che non vanno mai sottovalutate, l’approccio DevOps punta a ottenere i suoi risultati attraverso la standardizzazione degli ambienti di sviluppo, il collaudo del software, l’adozione di metodologie agili. Torneremo comunque ad approfondire questo argomento in futuro.
Con ogni probabilità, il primo e più evidente aspetto che differenzia i settori sviluppo e quello operations sta nel modo in cui essi considerano la tecnologia e il valore che essa apporta a quello che è percepito come loro obiettivo. Ed è qui che l’area development deve comprendere come certe scelte architetturali, tecnologiche e di programmazione non possano essere fatte senza tenere in considerazione l’impatto che esse avranno nell’ambito delle operations.
Dal Test Driven Development al Continuous Development
Un approccio di tale genere allo sviluppo rapido di software si basa su tre “discipline” complementari: il TDD (Test Driven Development), la CI (Continuous Integration) e il CD (Continuous Development).
Test Driven Development
Di Test Driven Development si parla ormai da parecchi anni [1]. Come i lettori ben sapranno, si tratta una pratica, concettualmente semplice ma di applicazione non banale, basata sullo sviluppo del software a partire dai test automatici. Nel TDD si sviluppa anzitutto il codice di test, e successivamente il codice applicativo vero e proprio della funzionalità da implementare. Ciò è svolto attraverso appositi tool e con una precisa progressione di test falliti e superati [2]. Avere delle robuste suite di test per la propria applicazione è una vera e propria assicurazione per gli sviluppatori.
Va inoltre detto che, grazie a tecnologie come i framework di mock, è possibile fare test che verifichino condizioni di malfunzionamento particolare, per esempio problemi di rete, oppure eccezioni derivanti dal filesystem. Queste sono situazioni possibili, anzi… probabili, che vanno prese in considerazione già in fase di sviluppo per non ritrovarsi in produzione con un’applicazione che crolla alla prima difficoltà. È poi possibile conoscere quanti e quali test sono stati effettuati, per avere un’idea globale, da parte di tutti i team di sviluppo, del livello di “copertura” dei test sulla applicazione.
Non ci dilungheremo oltre, e rimandiamo ai riferimenti in fondo all’articolo il lettore che necessitasse di approfondire tale argomento [3].
Continuous Integration
Per Continuous Integration si intende il processo automatizzato di build e test in grado di consentire la modifica, la compilazione e la verifica di un progetto in maniera continua, anche più volte in un giorno. Grazie alla CI si agevola lo sviluppo incrementale e si può valutare l’integrazione tra i diversi moduli software mano a mano che li si costruisce. Di Continuous Integration e del suo valore strategico parliamo nella seconda parte di questo articolo.
Continuous Development
Un ulteriore passo avanti è rappresentato dal Continuous Development. Il termine Continuous (Software) Development viene usato in maniera piuttosto libera per descrivere i diversi aspetti del processo iterativo di sviluppo del software che include, appunto, l’integrazione continua, il rilascio continuo, il test continuo, e il deploy continuo. Il concetto alla base di tutto questo è semplice: invece di attendere e inglobare molti cambiamenti tutti insieme in un grande rilascio effettuato a scadenze temporali dilatate, si preferisce rilasciare una versione aggiornata, con cambiamenti minimi, ogni volta che ciascuna modifica sia stata positivamente implementata e sia pronta. Di questo parleremo nel prossimo articolo.
Continuous Integration: le motivazioni
Allora, immaginiamo di avere una struttura tecnica in cui le suite di test che devono governare il processo di build siano ben costruite e funzionanti e vengano correttamente utilizzate per guidare il processo di sviluppo: i vari team di sviluppo stanno facendo TDD.
A questo punto occorre passare a un’automazione dell’integrazione dell’intero ecosistema della nostra applicazione, con un approccio olistico. Il problema da affrontare è vecchio quanto lo sviluppo del software e si concretizza nel rilascio di una versione affidabile del prodotto. È qualcosa che tutti abbiamo osservato: possiamo grosso modo renderci conto dei tempi con cui i vari team di sviluppo rilasceranno le parti di applicazione che sono di loro competenza, ma conoscere con esattezza i tempi e le difficoltà con cui queste varie parti dovranno essere integrate… è tutta un’altra storia.
Dal waterfall al RUP
Senza andare troppo indietro al tempo dei progetti triennali con metodologie waterfall, anche negli anni Novanta, con l’affermazione dei modelli iterativi e incrementali tipo RUP [4], i rischi di una integrazione tardiva avevano il loro pesante impatto sui progetti. Parliamo di progetti in cui i vari team di sviluppo lavoravano in relativo isolamento per lunghi periodi, anche di più mesi. Alla fine, magari dopo tre mesi, ci si trovava a dover “montare i pezzi” e ci si rendeva conto che questa integrazione era tutt’altro che agevole: potevano allora volerci anche svariate settimane per trovare una soluzione attraverso test e altre pratiche di risoluzione dei problemi, non sempre elegantissime… È a prezzo di tali intoppi che ci si rese conto a poco a poco di come il modello ideale della fabbrica di automobili in cui prima realizzo i pezzi e poi li monto insieme semplicemente si adattasse poco bene allo sviluppo del software. Al di là dei problemi e delle difficoltà che tutto questo creava ai team di sviluppo, c’erano anche delle ripercussioni non indifferenti sul lato business poiche’ poteva diventare difficile gestire i ritardi generati da questa mancata facile integrazione
Martin Fowler e l’integrazione continua
Tutto questo non era certo sfuggito a Martin Fowler che, proprio in concomitanza con il nuovo millennio, introdusse il concetto di Continuous Integration [4] come soluzione ai problemi appena elencati. In breve si trattava di spostare il processo di test e integrazione delle varie parti dell’applicazione ad ogni nuova build che si realizzava, e non più alla fine di un lungo periodo di sviluppo isolato. In tal modo si identificavano subito eventuali problemi che, peraltro, risultavano anche più facili da risolvere visto che, tra un test di integrazione e un altro, erano intervenute poche modifiche e quindi risultava più agevole andare a capire dove si era verificato il problema.
Certo, questo approccio prevedeva di preparare e installare un ambiente di integrazione, e di mettere a punto un preciso processo per effettuare questi test, oltre a necessitare di tempo “sottratto” alla scrittura del codice e dedicato ai test. Ma i costi, in termini temporali, implicati in questo modo sviluppare erano davvero minimi se paragonati alla enorme quantità di tempo perso a causa di integrazioni tardive. Con questo approccio, l’integrazione non era più un problema, ma diventava parte dela scrittura di codice e della compilazione.
Continuous Integration: la tecnologia
La Continuous Integration prevede l’adozione di un server per l’integrazione continua, ad esempio Cruise Control [6], Jenkins [7] o Bamboo [8]. Esistono prodotti commerciali così come alternative open, ma la cosa fondamentale è che il server di CI fornisca alcune funzionalità fondamentali:
- controllare il repository del codice sorgente e verificare ogni check-in;
- effettuare il check-out del codice sorgente in conseguenza di ogni check-in;
- effettuare la build del codice sorgente;
- eseguire i casi di test;
- lanciare eventuali build secondarie.
Tipologie di build
La Continuous Integration definisce svariate tipologie di build e di vincoli che le riguardano. La build principale è detta commit build, ed è eseguita ogniqualvolta uno sviluppatore effettui il commit del codice verso il repository del codice sorgente. Si richiede in genere che sia completata in tempi brevi (sotto i dieci minuti) poiche’, durante l’esecuzione, gli altri sviluppatori non dovranno effettuare commit del codice. Se la commit build fallisce, si capirà subito che c’è un problema con il codice che era appena stato inserito e che è necessario “aggiustarlo” prima che altri sviluppatori possano effettuare il commit ulteriore codice, rendendo più difficile la diagnosi del problema.
Dal momento che ci si attende che questa build principale finisca la sua esecuzione in meno di dieci minuti, sarà necessario effettuare alcune ottimizzazioni: la commit build comunicherà probabilmente con oggetti mock invece che con un vero database o un vero strato di messaggistica. In definitiva, la build principale ci assicura che non vengano introdotti problemi fondamentali quando viene introdotto nuovo codice. Una volta che la commit build sia andata a buon fine, gli altri sviluppatori potranno effettuare il commit del loro codice e ripetere il medesimo processo.
Ma non c’è solo questa possibilità. Dopo il completamento delle build di commit, è consentito anche di avviare un certo numero di build secondarie che effettueranno test più lunghi dell’applicazione. Per esempio, si potrebbe usare una di queste build secondarie per effettuare il deploy dell’applicazione in un ambiente di integrazione in cui ci sia un vero database, dei servizi dipendenti, un message bus, e così via, per eseguire test di integrazione più probanti, tipo una ricerca vera e propria sul database. Laddove la commit build effettua un test utile ma in condizioni predefinite, con la build secondaria si possono testare altri aspetti, quali ad esempio la connessione al database o le validazioni dei dati.
Se, sulle build secondarie, uno dei test fallisce, questo non impedisce agli altri sviluppatori di lavorare sul ramo principale del codice e di continuare a controllare i propri cambiamenti, ma sarà comunque necessario mettere a posto il problema incontrato, reimmettendo nuovo codice. Se il problema scoperto con queste build secondarie potrà essere scoperto anche in una build principale, la metodologia della Continuous Integration prevede di aggiungere un test alla commit build che sia in grado di individuare tempestivamente tale problema; tutto questo, però, mantenendo sempre il limite dei 10 minuti per l’esecuzione della build principale.
Continuous Integration: il valore per l’azienda
Dopo aver visto le motivazioni per cui è stata sviluppata, e aver brevemente descritto alcuni aspetti tecnologici, apparirà ormai chiaro come il valore principale della Continuous Integration risieda nel fatto che essa riduce al minimo lo spreco di tempo necessario per effettuare integrazioni tardive.
Ma non ci si deve fermare a questo pur importante risultato: avere un server per la Continuous Integration e un gruppo di sviluppo che ne padroneggia il processo apre le porte a interessanti sviluppi. Per esempio, i test automatizzati di unità e di integrazione completa potranno essere estesi con test di prestazione e di scalabilità. Ma si potrà anche andare oltre, verso il cosiddetto continuous delivery in un’ottica di continuous development, argomenti di cui parleremo nel prossimo articolo.
I benefici in breve
In definitiva, i valori di tipo “business” che la Continuous Integration può apportare in ogni azienda IT sono i seguenti:
- i tempi dei cicli di rilascio possono essere previsti in maniera più attendibile;
- le funzionalità implementate dal codice sono maggiormente testate;
- l’integrazione completa delle applicazioni è testata in ambienti simili a quelli di produzione;
- il codice è testato meglio anche per quanto riguarda le prestazioni;
- le applicazioni e gli ambienti sono testati meglio per quanto attiene alla scalabilità;
- rilasci rapidi.
In una sorta di circolo virtuoso, questi benefici si riflettono a più livelli: un codice ben testato porterà a un’applicazione più stabile che creerà meno malfunzionamenti e necessiterà di minore supporto tecnico; applicazioni scalabili e dalle buone prestazioni faranno crescere la soddisfazione del cliente; i rilasci rapidi implicano che si potranno sviluppare velocemente certe funzionalità importanti che potranno essere rese disponibili in produzione non appena pronte. Tutto questo ha anche un valore come possibile vantaggio competitivo sui propri concorrenti.
Conclusioni
Uno degli obiettivi sia per il development che per le operations è quello di rilasciare nuove versioni delle applicazioni in tempi brevi: i tempi degli aggiornamenti colossali una o due volte l’anno sono ormai definitivamente tramontati, e l’approccio al deploy di nuove funzionalità e versioni è ormai su base settimanale, se non quotidiana. Del resto, se una nuova funzione è ben identificata e ben implementata può essere rapidamente messa a disposizione degli utenti, con un evidente apporto di valore. Resta però il problema di come sia possibile testare in maniera adeguata una grande applicazione nell’ottica dello sviluppo rapido. La Continuous Integration ci viene in aiuto proprio per risolvere questa difficoltà.
In questo articolo abbiamo introdotto alcuni importanti argomenti legati allo sviluppo rapido del software, parlando di Test Driven Development, Continuous Integration, Continuous Development. Abbiamo fatto cenno anche all’approccio DevOps, ripromettendoci di approfondirlo in futuri articoli. Nel prossimo articolo affronteremo il tema del Continuous Development.