Buongiorno a tutti e ben tornati alle cronache di Jader e dei suoi animali mitologici! ;)
Questa mattina vi racconterò una nuova puntata della saga, oramai divenuta celeberrima :), sugli animali mitologici e i loro casi d'uso un po' fuori dall'ordinario!
In realtà, ad essere del tutto onesti, qui il caso d'uso non m'è stato imposto da loro direttamente, però diciamo che la colpa rimane loro perché.. Sono loro che hanno scelto lo strumento del male per il quale ho dovuto scomodare dei nuovi amici!
Ma entriamo subito nel vivo, così che anche voi possiate beneficiare di questa funzionalità di Liferay!
Questa volta però, prima del caso d'uso, devo darvi un po' di contesto!
RTIR: il sistema di ticket da interfacciare!
In queste settimane mi è toccata la parte più noiosa del -chiamiamolo così..- lavoro: ho dovuto fare un refactor (nemmeno fossi l'Ingegner Napolitano quando apre il mio codice! :D) di un client REST per interfacciare RTIR: un sistema di ticket molto gradito al Cliente.
RTIR, per chi non lo sapesse è una nuovissima e fiammante web interface scritta nientemeno che in perl (si, nel 2019 ancora il perl..) di cui hanno fatto ben 2 release di API REST. [NdJ: "nuova e fiammante", onestamente, è una battuta...]
Oddio.. In tutta onestà chiamarle "API REST" è forse un po' troppo: come potrete vedere dall'abbondante e copiosa documentazione (versione 1.0 e versione 2.0), tutto sono tranne che API disegnate come si deve!
Diciamo che quando hanno fatto quella roba lì (insieme inclusivo anche della pagina singola di documentazione per ogni release dell'API..), forse non avevano bene in mente che qualcuno avrebbe dovuto un giorno invocarle davvero..
Tuttavia, siccome m'è toccato, come si dice spesso ;), mi sono messo di buona lena e ho interfacciato queste API facendo quello che andava fatto (non lato codice, lato pratico: mi sono tappato il naso e mi sono messo a fare senza rompere -troppo- le scatole! :D).
Cookie, autenticazioni, bearer token, trust manager.. Nient'altro?? :D
Senza perdermi oltre nel criticare un sistema che sicuramente sarà fichissimo (tanto da essere stato scelto dal Cliente dei miei animali mitologici come sistema di ticket interno), mi sono lanciato nel disegnare tutti gli aspetti della nostra interfaccia, considerando chiaramente anche gli aspetti di configurazione.
Nel farlo, ho scoperto che mi sarebbero serviti diversi flag (con relativi parametri):
- autenticazione basata su Basic Auth: vuoi non proteggerlo dal web?
- autenticazione basata su bearer token: vuoi non metterlo, in una API REST?
- autenticazione basata su user / pass: eggià, nel 2019 questo è uno dei metodi consigliati...
- utilizzo di un trust manager custom: vuoi non installare un certificato self signed, quando uno vero ormai te lo danno gratis?
Chiaramente, nel mio modello, più di uno di questi meccanismi doveva essere implementato pena il non funzionamento dell'intero cinema!
E mentre facevo tutto questo, sul quale tralascio ovviamente la parte di codice perché non funzionale a questo post, mi rendevo conto che avrei avuto un leggerissimo problema di performance: per come stavo strutturando il client (legato per indirettezza a come sono disegnate le API...), avrei avuto metodi stateless che, a singola invocazione, avrebbero dovuto invocare due URL per singola chiamata.
Questo meccanismo "a doppia chiamata" si rendeva necessario proprio per i flag di cui sopra: la prima chiamata sarebbe servita per ottenere l'autenticazione (cookie, token, quel che l'è), la seconda per invocare effettivamente l'API.
Peccato che il grafo da navigare di questo coso sia oscenamente grosso: inutile dire che, a fronte del caricamento del dettaglio di un ticket, mi trovo a dover invocare 6 / 7 URL che, moltiplicate per la fase di autenticazione, diventano 12 / 14 URL..
Faccio presente che questo perverso meccanismo a multi chiamata per avere il dettaglio di un ticket è normato dal design della API di cui sopra, quindi non sono io che non ho idea di come fare un client REST, sono loro che non hanno idea di come disegnare una API.. :/
Avendo costruito però tutto il client REST (ed essendo anche soddisfatto del mio lavoro, nonostante le critiche che l'Ingegner Napolitano mi muoverebbe se vedesse il mio codice.. :D), mi scocciava ridisegnarlo da capo per evitare questo problema..
E allora mi sono detto: "visto il codice sul quale ho fatto refactor, nessun s'accorgerà della melma che ho disegnato e forse la passerò liscia.." ;D
Ma poi, più lo utilizzavo per farci i test e più mi rendevo conto che no, dai, era davvero osceno: sia in termini di performance sia in termini di design.. Per quanto i miei animali mitologici mi torturino, non mi sembrava il caso di lasciare loro del codice fatto così ammmelma!
Però la pigrizia la faceva da padrona..
Siccome lo statement qui sopra è sempre vero ;), avendo io disegnato un client che fa esattamente tutto quello che mi serviva e non volendolo ridisegnare completamente ;), mi sono interrogato su come rendere più performante il mio codice!
Chiaramente, evitare il throttling delle URL sarebbe la prima cosa da fare; però come avrei potuto farlo se tutte le URL da invocare fanno riferimento a pojo differenti e devono essere riutilizzabili anche in metodi differenti? Inoltre, come potevo rendere la API indipendente dall'implementazione fisica del client REST senza necessariamente dover creare un accoppiamento con questo nelle firme?
Ed è stato proprio qui, mentre pensavo a tutte queste cose, che m'è venuto in mente un post di Daniele Catellani sul fascino dei thread local, fatto davvero tantissimi anni fa!
Però poi mi sono anche ricordato dello sbatti mostruoso che bisogna fare per usarli (non in termini di codice, ma in termini di gestione) [NdJ: se non avete idea di cosa io sto dicendo ;), questo articolo di LR fa al caso vostro..).
E allora, come al solito ;), mi sono messo a scavare.. :D
A questo punto già avrete intuito cosa mi sono messo a fare! Come spesso accade lavorando con Liferay ;), in più di un milione di righe di codice, sicuramente esiste qualche cosa che fa già quello che mi serve.. :)
Quindi, anziché partire in quarta a inventare il meccanismo definitivo custom per la gestione dei thread local, avendone visti implementati in LR a bizzeffe :D, mi sono messo a cercare e ho trovato i miei nuovi amici: i CentralizedThreadLocal
! :)
CentralizedThreadLocal: chi sono e cosa fanno!
Questa classe è stata progettata per risolvere in maniera molto semplice ed elegante una serie di problemi che esistono nella gestione dei thread local. Ad esempio, permette di ripulire il, chiamiamolo così..., singleton del thread local specifico per ogni request, evitandovi lo sbatti di doverlo fare voi.
Chiaramente, per farlo usa sempre i "soliti" meccanismi: c'è un simpatico filtro che si occupa di ripulire il thread local dopo ogni invocazione, così da non doverci caricare dell'onere di farlo.
A questo punto, però, dobbiamo vedere insieme un po' di codice, per capire nel dettaglio come implementarne uno e come fare in modo che tutti i nostri problemi, magicamente, si dissolvano! :D
Piccola premessa: la classe CentralizedThreadLocal si trova all'interno di due package di prodotto; la prima, il kernel, dove questa è deprecata e la seconda, dentro a petra.lang, che è quella che dobbiamo utilizzare!
package it.dvel.rtir.client.util;
import aQute.bnd.annotation.ProviderType;
import com.liferay.petra.lang.CentralizedThreadLocal;
import javax.ws.rs.core.NewCookie;
import java.util.List;
@ProviderType
public class RtirCookieThreadLocal {
public static List<NewCookie> getCookies() {
return _newCookies.get();
}
public static void setCookies(List<NewCookie> cookies) {
_newCookies.set(cookies);
}
private static final ThreadLocal<List<NewCookie>> _newCookies =
new CentralizedThreadLocal<>(
RtirCookieThreadLocal.class + "._newCookies",
() -> null);
}
Come potete notare, la classe è molto stupida: di fatto è un wrapper sull'oggetto ThreadLocal
che però viene valorizzato con una istanza di CentralizedThreadLocal
.
A questa istanza vengono passati due dettagli importanti: come si chiama il ThreadLocal
, all'interno della mappa che il padre di tutti questi oggetti si crea (quindi questo nome dev'essere univoco perché interpretato come un id..) e una lamda che fornisce un'implementazione della @FunctionalInterface Supplier
, ovvero del wrapper per la costruzione del valore di default del nostro ThreadLocal
.
Di default, usando il costruttore che abbiamo utilizzato noi, viene anche impostato internamente al ThreadLocal
il flag shortLived
a true
, il che significa che il nostro oggetto sarà ripulito "al termine di ogni esecuzione della richiesta".
Ed ecco ottenuto il nostro ThreadLocal
nuovo fiammante e assolutamente in grado di risolvere -elegantemente, aggiungerei ;)- anche il problema della condivisione all'interno del nostro thread delle informazioni necessarie a non dover riautenticare il Client ad ogni richiesta!
Ma le magie del CentralizedThreadLocal
non finiscono qui!
Se andate a vedere nel codice sorgente quali altre opportunità vi offre :), scoprirete una serie di feature davvero interessanti..
.. Però qui io mi fermo!
Adesso è il vostro turno: aprite il codice e divertitevi a scoprire quali e quante cose può fare per voi il nostro nuovo amico e, come al solito ;), se volete potete commentare in questo post le vostre scoperte, così da renderle utili a tutti!!!
Anche per oggi è tutto, buon divertimento con i CentralizedThreadLocal
, Liferay e.. I vostri animali mitologici!! :)
A presto, ciao, J.