Perche' Groovy


Come non si può apprezzare la JVM...

Uno degli aspetti a cui tengo di più è la libertà. La libertà di poter scegliere la tecnologia che più mi affascina o che più si adatti al problema da risolvere, senza però rinuciare al know-how acquisito. Ed ecco che con la JVM tutto questo è possibile: Avere la possibilità di scegliere rimanendo nella propria comfort zone è una fantastica opportunità.

Che cos'è che cerchi?

  • Vuoi un approccio funzionale? C'è Scala o Java
  • Vuoi un approccio funzionale più rigoroso? Eccoti Clojure
  • Cerchi un linguaggio che unisca l'approccio funzionale, la tipizzazione dinamica (o statica, puoi scegliere) e che ti semplifica lo sviluppo di applicazioni J2EE? C'è Groovy

Ora, non voglio tediarvi con la solita spiegazione tecnica perchè non ho intenzione di insegnarvi Groovy. Quello che voglio è farvi conoscere alcune features di questo linguaggio in modo da poter valutare un suo utilizzo nei vostri progetti futuri.

Let's go!!!


Map Constructors

Tra tutte le features di Groovy, quella che trovo la più utile di tutti è la possibilità di inizializzare un oggetto passando una mappa al costruttore. Per chiarire meglio, definiamo una classe di esempio che con un guizzo di inventiva chiameremo: Prova. Essa conterà semplicemente tre parametri: param1 non tipizzato, param2 tipizzato a String e child tipizzato con la medesima classe.

class Prova {

  def param1
  String param2
  Prova child

  @Override
  String toString() {
      "${param1} ${param2} -> Child:${child?.param1} ${child?.param2}"
    }
}

N.B. Come potete notare siamo stati obbligati a definire il tipo String nell'override del toString(). Questo perchè il metodo originale in Java ritorna uno String e non un Object (nel caso non avessimo specificato il tipo).

Ora possiamo inizializzare la nostra classe Prova con una mappa in Groovy:

def mappa = [param1: "Hello", param2: "World", child:[param1: "Ciao", param2: "Mondo"]]
def prova = new Prova(mappa)

println prova.toString()
Hello World -> Child:Ciao Mondo

Anche se non abbiamo definito alcun costruttore, Groovy ha assegnato i values della mappa nei field della nostra classe. L'abbinamento è stato fatto eguagliando il nome dei field con le key della mappa. La stessa procedura è stata fatta anche per la mappa annidata che a sua volta è andata a popolare il campo child.


Direct field access operator (.), Safe navigation operator (?) e GString

Analizzando il metodo toString() della nostra classe, ci accorgiamo che siamo riusciti ad utilizzare 3 importantissime features di Groovy, rispettivamente l'operatore ., ? e l'oggetto groovy.lang.GString. Cominciamo con il field access operator cioè il (.). Se in Java siamo stati obbligati a definire getters e setters per accedere e scrivere le nostre proprietà, in Groovy le cose sono molto più semplici:

Esempio:

def mappa = [param1: "Hello", param2: "World", child:[param1: "Ciao", param2: "Mondo"]]

def prova = new Prova(mappa)
def result1 = prova.param1 
def result2 = prova.param2
assert result1 == 'Hello' && result2 == 'World'

N.B. Quando si definiscono le proprietà di una classe Groovy, spesso si tralasciano i modificatori di visibilità. In Groovy tutto è public, ma chiariamo una cosa: Tralasciare non vuol dire ignorare. Se stiamo costruendo un .jar dobbiamo tener presente che esso può essere utilizzato anche in Java, quindi se sono stati definiti dei modificatori in quest'ultimo caso saranno valutati.

L'operatore (?), detto anche safe navigation operator, può precedere il (.). In questo modo ci garantisce che la nostra applicazione non si blocchi a fronte di un NPE. Facendo riferimento alla classe Prova se eseguiamo un toString() con la proprietà child == null, non avremo un NullPointerException ma semplicemente un poco elegante Child null null, senza che la nostra applicazioni termini. E' buona norma quindi, utilizzare l'operatore (?) tutte le volte che si accedere ad una proprietà tramite il (.). In caso di null dovremo trattarlo come un risultato qualsiasi e ricordarci che nella logica di Groovy null equivale a false.

Ultima ma non ultima, la features GString. Ovvero la possibilità di interpolare testo e risultati, il tutto all'iterno di una String, anzi di una GString. Vediamo subito un esempio.

Secondo voi qual'è il codice più leggibile, elegante e facile da mantenere? Questo?

String scheme = "http";
String server = "127.0.0.1"
int port = 8080;
String context= "api";
String actionMethod = "clienti";
Long timeMillis = new Date().getTime();
String request = scheme+"://"+server+":"+port+"/"+context+"/"+actionMethod+"?t="+timeMillis; 

O questo?

def scheme = "http";
def server = "127.0.0.1"
def port = 8080;
def context= "/api";
def actionMethod = "/clienti";
def request = "${scheme}://${server}:${port}/${context}/${actionMethod}?t=${new Date().time}

Traits

Riportiamo la nostra attenzione sulla classe Prova. Abbiamo appena visto che è possibile utilizzare una mappa di valori per inizializzare la nostra classe. Ma cosa succederebbe se la mappa contenesse proprietà non presenti nella classe Prova?

Facciamo un test:

> def prova = new Prova([param1: "Ciao",param2: "Mondo",param3:"Terzo parametro"])

groovy.lang.MissingPropertyException: No such property: param3 for class: Prova
Possible solutions: param2, param1
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:53)

E' vero che Groovy ci viene in aiuto suggerendoci le possibili soluzioni, ma rimane il fatto che dobbiamo poter ignorare i parametri in più e continuare con la nostra inizializzazione. Come fare? Semplice, ignoriamoli a run-time!

Nella nostra classe Prova, basterà aggiungere il seguente metodo:

def propertyMissing(String name, String value) {
   println("ERRORE:\t Proprietà ${name} inesistente!")
   return null
}

PropertyMissing è un metodo che viene richiamato a run-time ogniqualvolta viene effettuato un set di una proprietà non presente nella classe. Nel nostro caso la utilizzeremo solo per ignorare l'errore e andare avanti nell'esecuzione del programma e per farlo basterà copiare-incollare il metodo nelle classi interesssate.

Ma, per essere sicuri che ogni classe abbia il metodo propertyMissing possiamo definire una interface IgnorePropertyMissing;

interface IgnorePropertyMissing {
    def propertyMissing(String name, value)
}

Questo è Groovy, quindi possiamo fare di meglio:

trait IgnorePropertyMissing {
    def propertyMissing(String name, value) {
    }
}

Abbiamo appena usato un Trait. Per ora ci basti sapere, un trait è una interfaccia che può contenere anche l'implementazione di un metodo. Ma questa è solo la punta dell'iceberg. Per approfondire e scoprire tutte le potenzialità dei Traits, vi consiglio la documentazione ufficiale.

Un possibile caso d'uso

Ora, abbiamo scoperto tante cose interessanti su Groovy. Ma tutti siamo consci che la teoria senza pratica, serve a ben poco. Quindi analizziamo ora un caso d'uso molto ricorrente: Leggere un JSON proveniente da un webservice.

La libreria Groovy (versione full-optional)

compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.15' 

contiene un tool molto utile: JsonSlurper. Il nome ce lo dice chiaramente, la libreria si occupa di parsare una stringa JSON e restituire il tutto sottoforma di una mappa:

       def http = new URL(url).openConnection()
       def jsonslurper = new JsonSlurperClassic()

       def response = jsonslurper.parse(http.inputStream.newReader())

Ma allora, se abbiamo una mappa che rappresenta la response JSON del servizio perchè non usarla per popolare un oggetto? Pertanto se questo è il nostro JSON di risposta:

{"status":"ok","count":4,"posts":[
  {"id":1,"type":"post","param1":"Hello","param2":"World"},
  {"id":2,"type":"post","param1":"Hello","param2":"World"},
  {"id":3,"type":"post","param1":"Hello","param2":"World"},
  {"id":4,"type":"post","param1":"Hello","param2":"World"}
  ]
}

Il corrispettivo POGO (Plain Old Groovy Object) sarà:

def PostPOGO {
  def id
  def type
  String param1
  String param2
}

Quindi il nostro mapping JSON-->POGO sarà:

def response = jsonslurper.parse(http.inputStream.newReader())
        response.posts.each {
            myList.add(new PostPOGO(it))
}

Et voilà!


Conclusioni

Spero vivamente di avervi incuriosito e stimolato nell'utilizzo di Groovy. Se il siete sviluppatori Java, molto probabilmente apprezzerete la pulizia della sua sintassi e sopratutto, la facilità con cui è possibile lavorare in modo funzionale. Il tutto favorito da una curva d'apprendimento relativamente facile.

Di seguito un paio di caratteristiche sul linguaggio (dalla documentazione ufficiale)

  • Dynamic Typing su JVM
  • Static typing opzionale
  • Curva di apprendimento ridotta per sviluppatori Java
  • DSL
  • Interoperabilità al 100% Java

A presto e Happy Coding!


Link e risorse utili
  1. Documentazione ufficiale. La migliore risorsa per cominciare. LINK
  2. MrHaki Blog LINK
  3. (Libro) Manning - Groovy In Action 2ed (MUST) LINK
  4. (Libro) Pragmatic Bookshelf - Programming Groovy 2 (GOOD TO HAVE) LINK