Chi realizza software si trova spesso a dover scegliere tra strumenti tecnologici diversi, preferendo certi framework per la rapidità d’uso, magari rinunciando però alle potenzialità messe a disposizione da toolkit che prevedono approcci meno immediati ma garantiscono versatilità e ottimizzazione delle prestazioni adatte anche a grandi applicazioni. È il caso di AngularJS vs. GWT, ad esempio. E se ci fosse un punto di incontro tra i due mondi? E quello che si propone di rappresentare Singular, di cui vi daremo un’anticipazione in questo articolo e nella seconda parte che pubblicheremo il mese prossimo.
AngularJS vs. GWT: alla ricerca di una sintesi
Abbiamo avuto occasione in varie riprese di parlare con Daniel Kurka, uno degli sviluppatori “core” delle tecnologie web usate da Google, quelle, per capirsi che stanno dietro a prodotti come Inbox by Gmail, nonche’ membro dello Steering Committee di GWT.
Nel parlare di AngularJS, tecnologia considerata la “regina” degli opinionated frameworks, Daniel Kurka ha sempre espresso i suoi dubbi non sul valore dello strumento in se’, ma riguardo alla performance di grandi applicazioni basate su questo framework.
D’altronde, AngularJS e GWT sono due tecnologie che palesemente divergono: focalizzato alla produttività e alla rapidità di sviluppo il primo, pensato per l’ottimizzazione e la performance il secondo. Un opinionated framework che ti guida nel “suo” modo di scrivere applicazioni, AngularJS; un toolkit che fa della versatilità e dell’essere totalmente aperto (unopinionated) a qualsiasi scelta strutturale, GWT. Intrinsecamente basato sulla tipatura dinamica del JavaScript il framework, e basti pensare a come è definito $scope; ostinatamente radicato nel concetto di static typing il toolkit.
Insomma, sembrano due mondi senza punti di contatto.
Una nuova prospettiva: Singular
Potete quindi immaginare l’interesse che ha suscitato all’ultima GWT.create vedere Daniel presentare un talk dal titolo “Singular: Reimagining AngularJS in Java” in cui le osservazioni circa i “difetti” del framework AngularJS hanno preso forma e trovato una possibile soluzione proprio nelle “rigidità” del toolkit.
Figura 1 – La presentazione di Daniel Kurka effettuata all’ultima conferenza GWT.create.
Va premesso che, al momento in cui scriviamo, di Singular c’è ben poco: la presentazione di cui abbiamo parlato, e alcune discussioni che dopo il talk abbiamo avuto modo di fare con lui e con gli altri sviluppatori coinvolti nel progetto.
Le cose, però, sono in rapida maturazione e i cardini di questo nuovo framework, potenzialmente rivoluzionario, sembrano piuttosto chiari, ed è per questo che abbiamo voluto presentare subito le nostre riflessioni e i nostri esperimenti.
Essendo sviluppatori GWT da diverso tempo, possiamo provare a vedere come le promesse di Daniel a riguardo del nuovo Singular potrebbero essere soddisfatte dal compilatore Java to JavaScript. Si tratta di un esercizio di stile in quanto non possiamo per il momento verificare se l’implementazione sia effettivamente corrispondente alle considerazioni che seguiranno, ma sarà comunque possibile mettere in luce l’impiego di alcune caratteristiche di GWT che possono risultare utili anche in altri contesti.
Dobbiamo però anzitutto vedere alcune caratteristiche di AngularJS.
Le caratteristiche di AngularJS
L’analisi circa cosa offra AngularJS è banale per chiunque ne abbia letto anche solo un esempio: focalizzato sulla produttività degli sviluppatori, zero boilerplate, costruito in modo da essere testabile (“testing as a first class citizen”), sintetico, dotato di una sua implementazione per rendere possibile la Dependency Injection, capace del cosiddetto “two way data binding” che mantiene sincronizzato il modello e le viste senza quasi che ci sia bisogno di scrivere una linea di codice.
Insomma AngularJS pare da subito il framework con cui qualunque sviluppatore web vorrebbe avere a che fare, ma certe “comodità” si pagano…
“Parse templates at compile time to resolve directives”
“Effettuare il parsing dei template a tempo di compilazione per risolvere le direttive”. Osservando come sia strutturata l’interazione tra direttive e template in AngularJS, è semplice convincersi che il framework è costretto a elaborare i templates (a runtime) per trovare le direttive. AngularJS lo fa in maniera estremamente efficiente ma questo compito deve essere comunque assolto durante lo startup dell’applicazione ed è interamente a carico del browser, ritardando l’operazione o quantomeno incidendo nel tempo di startup dell’applicazione.
Lo scan delle direttive è a tutti gli effetti una penalty che riguarda la struttura dell’applicazione e non è ragionevole pagare questo costo computazionale ad ogni startup. A ben pensarci GWT, ha già una sorta di sistema di template che si chiama UiBinder; il compilatore processa i file XML che definiscono le classi create con questa tecnologia e letteralmente genera le corrispondenti classi che poi verranno a loro volta compilate in JavaScript: la penalty di UiBinder viene pagata solo a compile time. Il binding tra direttive e template in AngularJS è a ragion veduta più complesso del processo di generazione degli UiBinder ma il parallelo regge ed è lecito pensare che una strada simile possa essere percorsa per calcolare in anticipo le associazioni.
Si tratta di un parallelo probabilmente stringente per quanto riguarda l’approccio, ma quasi certamente non lo sarà per quanto riguarda l’effettiva tecnologia utilizzata. UiBinder è realizzato interamente attraverso i Generators di GWT (Deferred Binding [1]), mentre Singular utilizzerà la Java Annotation Processing [2].
Su questa scelta, che al momento sembra quasi indifferente, ritorneremo più avanti; ma vale la pena di osservare che utilizzare APT invece che Generators apre la possibilità di utilizzare la tecnologia di generazione e wiring nei template non solo con GWT ma in generale in applicazioni basate su Java.
“Undestand the ‘scope’ sufficiently enough to not require dirty checking”
“Comprendere lo scope in maniera sufficiente a non richierere il dirty checking”. Una seconda, e più complessa, causa di degradazione delle performance è invece quello che viene chiamato dirty checking loop.
Per rendere possibile il data binding bidirezionale, AngularJS, ad ogni cambiamento nel model, è costretto a visitare l’intero model per capire cosa sia da aggiornare al fine di sincronizzare i cambiamenti sulla view.
Figura 2 – Dirty checking loop.
La spiegazione in una frase della necessità del dirty checking loop è che, mancando una informazione di tipo (e di relazione tra tipi) “statica” non c’è modo di capire, senza passare in rassegna tutto, quali parti del model e delle viste possano essere coinvolte da un aggiornamento.
Il concetto è forse sottile ma si spiega bene con un esempio trovato proprio in un post su AngularJS e intitolato “Using $scope.$digest() As A Performance Optimization In AngularJS” [3]. Nell’articolo, Ben Nadel spiega che esiste la possibiltà di rendere locali i dirty check utilizzando la funzione digest invece che apply. Dice l’autore: “può offrire un’ottimizzazione del ciclo di vita dei dirty data in situazione in cui si sia a conoscenza, per qualche ragione assodata, che i cambiamenti locali non avranno implicazioni globali”.
Insomma, se, per come è strutturata l’applicazione, lo sviluppatore è consapevole che alcuni update potranno interessare solo una parte dello scope, può essere una buona idea, dal punto di vista della performance, restringere forzatamente la visita.
La buona ragione potrebbe essere proprio l’informazione di tipo che quindi rende addirittura inutile il loop permettendo al compilatore di inferire le dipendenze nel model e pertanto generare codice che non deve fare nessuna visita globale ma neanche locale.
Quanto si possa realmente guadagnare da “ottimizzazioni” come quelle descritte è difficile da dire, ma Daniel ha presentato una demo e le slide della GWT.create [4] danno qualche altro dettaglio, con risultati quasi stupefacenti:
- prestazione 20 volte più veloce nel rendering iniziale;
- prestazione 5 volte migliore a runtime;
- codice JavaScript di dimensioni 4 volte più piccole rispetto alla versione scritta a mano… il che non guasta davvero!
Le promesse di Singular
Pur non essendo sentiti come “problemi” di AngularJS, ci sono almeno altre due caratterisctiche che meritano di essere citate circa le promesse di Singular ai programmatori Java: una rivistazione (tipata) della Dependency Injection e la possibilità, naturalmente, di usare i tool di sviluppo del mondo Java.
La seconda, la si condivida o meno, non ha bisogno di commenti, mentre la prima mira a risolvere le difficoltà che anche gli sviluppatori di AngularJS hanno trovato nel loro modello di DI.
Una rivisitazione della Dependency Injection
Che il modello di Dependency Injection presente in Angular 1.x avesse comunque qualche problema è testimoniato dal fatto che in Angular 2 c’è stato un ripensamento a tal proposito [5].
Singular userà uno degli approcci alla Dependency Injection esistenti nel mondo Java; vista la traction non ci meraviglieremmo si trattasse di Dagger2 [6] che condivide con Singular, e GWT, l’approccio “generativo” che mira a minimizzare i costi da pagare a runtime, scaricandoli sul compilatore.
L’eterna promessa: “write once”
C’è tanta carne al fuoco, no? Ma è solo l’inizio e forse il bello arriva adesso: Singular potrebbe essere lo strumento giusto per generare (e/o compilare) dal medesimo source tree applicazioni multipiattaforma. Native su Android, GWT su browser e, aggiungendo j2objc [7] alla toolchain, anche native su iOS.
In buona sostanza, si potrà fare qualcosa di simile a quanto fatto da Google per realizzare Inbox, in cui le versioni web, iOS e Android condividono oltre il 70% del codice. E anche se tutti quelli coinvolti dicono che non si tratta della medesima tecnologia effettivamente utilizzata per Inbox, di sicuro in quel contesto sono maturate le idee e le tecnologie impiegate.
Conclusioni
Nella prossima parte di questa riflessione, cercheremo di addentrarci nei dettagli della generazione di applicazioni multipiattaforma, per vedere come la giusta architettura del software e le giuste tecnologie potrebbero portarci a scrivere applicazioni “Write Once Compile Anywhere”.