Salve popolo di ignoranti (me compreso, ovviamente)
Per chi non mi conoscesse o è atterrato qui per sbaglio e volesse saperne di più, lo invito a leggere la mia presentazione in homepage.
Questa è la seconda parte di questo articolo. Nella prima parte ne abbiamo parlato in maniera molto generica, dando una definizione di resilienza. In questa seconda parte, vorrei entrare un po’ più nel dettaglio.
Il commento che ha acceso la miccia
Oltre ogni aspettativa, l’articolo precedente ha scatenato una reazione, ovvero un commento di un collega che stimo particolarmente. Avendo avuto la sua autorizzazione, lo riporto di seguito:
Non sono del tutto d’accordo con la tesi presentata.
Sebbene l’articolo introduca il tema, ritengo che la resilienza non sia quello che descrive. Nell’articolo si leggono più che altro regole di buona programmazione, ma collegare i SOLID principles a questo argomento non è del tutto corretto.
La resilienza, infatti, riguarda la resistenza al cambiamento, ma non del dato in sé o di come viene gestito. Piuttosto, riguarda il suo caso d’uso. Per intenderci, un approccio più resiliente non consiste nell’usare dei try-catch per gestire l’errore puntuale, ma nell’impiegare un error handler globale o, ancora meglio, nel prevedere gli edge case a tal punto da non averne completamente bisogno.
Ci sono passaggi e argomenti come il Rapid Recovery e, più noti, la Redundancy, che determinano molto di più il carattere di un’applicazione rispetto al clean coding (che l’articolo descrive comunque abbastanza bene).
Costruire un software resiliente è un approccio proattivo che coinvolge la progettazione, lo sviluppo, i test e le operazioni.
L’obiettivo è anticipare i problemi e preparare l’intero sistema a gestirli in modo efficace. Così facendo si garantisce la continuità del servizio e, di conseguenza, la fiducia degli utenti.G.Gagliano
Come dargli torto? Ha descritto con poche parole la definizione di resilienza software. Per andare un po’ più a fondo. Partiamo dal suo intervento.
La resilienza non è solo buona programmazione
Ed è verissimo. Nella prima parte dell’articolo, io mi sono focalizzato su quello perchè è quello che vedo tutti i giorni. Effettivamente un software può essere scritto benissimo, ma se a crollare è una dipendenza e ferma tutto, allora non si può dire che sia resiliente, è ben fatto ma fragile.
Il codice pulito è una base necessaria, ma non sufficiente: serve a limitare i danni, non ad evitarli.
Non si tratta di gestire l’errore, ma di evitarlo e contenerlo
E’ proprio qui che nasce la differenza tra un professionista ed ammiocuggino (quello bravo eh? )
Try – catch is for boys, retry policy is for alpha
La resilienza vera, è proattiva e nasce in fase di design. Applicando , come suggerisce il collega, un handler centralizzato per la gestione degli errori
app.UseExceptionHandler("/Home/Error");
In questo modo si evita di avere try-catch sparsi per tutto il codice.
Un’altra pratica interessante è quella di utilizzare delle policy di retry, magari con Polly (santo subito)
var policy = Policy
.Handle()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
In questo modo stiamo dicendo : Ehy amico, hai fallito? Prendi un bel respiro e riprova fra un po’.
A questa policy si può legare anche una strategia di fallback, che al terzo fallimento, mostri dei dati di default , senza far collassare l’applicazione.
Tutto ciò, senza avere un sistema di logging, è assolutamente inutile.
Se non so dove sta il problema…come lo risolvo?
Rapid Recovery e Redundancy
Qui arriviamo al cuore del discorso di Gianluca.
La resilienza, non è evitare il problema, ma piuttosto tornare operativi nel minor tempo possibile.
Per farlo, si possono applicare varie strategie
- Rapid recovery : Come dice il termine stesso, ritornare operativi rapidamente, magari utilizzando pipeline di deployment ripetibili e automatizzate, oppure avendo a disposizione snapshot e backup pronti per essere ripristinati al volo
- Redundancy : La ridondanza, non è uno spreco, ma un’assicurazione. Per ridondanza si intende, Database replicati, cache distribuita ed ovviamente un load balancing con failover automatico
- Fault Isolation : Quando un componente cede, il resto deve continuare a funzionare. Il sistema non deve crollare, ma deve degradare, per esempio: se il servizio notifiche è offline, l’app salva comunque i dati e invierà le notifiche più tardi.
Conludendo
Il titolo di questo articolo, oltre alla citazione di una nota serie TV, l’ho scelto anche per far riflettere sul fatto che comunque, siamo noi, con la nostra capacità di “vederci lungo” a rendere resiliente un software. Abbiamo una marea di strumenti a disposizione per poterlo fare, ma dobbiamo essere noi a prevedere quello che potrebbe succedere. Anche se non lo prevediamo, dobbiamo fare comunque in modo che la nostra applicazione stia sempre in piedi. Non è bello ricevere la chiamata di un cliente che ti dice :
il tuo software crasha in continuazione.
E’ molto meglio sentirsi dire :
Guarda, oggi le notifiche non funzionano, però il resto tutto ok.
Ringrazio Luca Gagliano per il commento assolutamente costruttivo, che mi ha aiutato a scrivere questa seconda parte.
Se il contenuto vi è piaciuto o se avete altri suggerimenti, lasciate un commento.
Se volete seguitemi anche sui canali social per vedere delle piccole pillole.
Buon sviluppo a tutti