Introduzione
Il terzo articolo di questa introduzione a Kotlin dimostra come sviluppare cryptoeval, un semplice ed utile servizio web per calcolare il valore del proprio portfolio di criptovalute. Nello sviluppare questa applicazione introdurremo alcune funzionalità avanzate del linguaggio che non sono ancora state illustrate ed useremo una libreria dedicata allo sviluppo di client/server HTTP chiamata http4k [1].
Il codice completo dell’applicazione è disponibile su Github [8].
Cryptoeval: come funziona
Cryptoeval è un servizio web che può essere invocato dal browser o da qualsiasi client HTTP (ad esempio curl [2]). Per invocarlo è sufficiente chiamare l’URL del servizio passando nella query string le criptovalute possedute (come chiavi) e le quantità (come valori). Supponendo per esempio che il servizio sia in esecuzione su
http://localhost:9000
e di voler calcolare il valore del nostro portfolio composto da 1.5 Bitcoin, 2 Ethereum e 12.3 Litecoin, chiameremo l’URL:
http://localhost:9000?btc=1.5ð=2<c=12.3
Il risultato atteso è il valore complessivo del portfolio in dollari statunitensi (USD).
Come avviene la quotazione
Le quotazioni delle principali criptovalute sono determinate, ad ogni chiamata, attraverso l’invocazione della REST API di CoinMarketCap [3], che le restituisce in un formato JSON simile al seguente:
[ { "id": "bitcoin", "name": "Bitcoin", "symbol": "BTC", "rank": "1", "price_usd": "573.137", "price_btc": "1.0", "24h_volume_usd": "72855700.0", "market_cap_usd": "9080883500.0", "available_supply": "15844176.0", "total_supply": "15844176.0", "percent_change_1h": "0.04", "percent_change_24h": "-0.3", "percent_change_7d": "-0.57", "last_updated": "1472762067" }, { "id": "ethereum", "name": "Ethereum", "symbol": "ETH", "rank": "2", "price_usd": "12.1844", "price_btc": "0.021262", "24h_volume_usd": "24085900.0", "market_cap_usd": "1018098455.0", "available_supply": "83557537.0", "total_supply": "83557537.0", "percent_change_1h": "-0.58", "percent_change_24h": "6.34", "percent_change_7d": "8.59", "last_updated": "1472762062" }, ... ]
Creeremo l’applicazione iterativamente, sviluppando e testando le singole funzionalità separatamente; inoltre, per brevità, non considereremo i casi di errore né le ottimizzazioni per migliorare le prestazioni dell’applicazione.
Client per CoinMarketCap API
In questa prima iterazione, vogliamo sviluppare un client che contatti l’API di CoinMarketCap e scarichi le quotazioni di tutte le principali criptovalute; vogliamo inoltre effettuare il parsing del risultato e stamparlo sulla console. È possibile consultare il codice completo usato in questa sezione nel branch client del progetto su Github [8].
Client con http4k
Come detto in precedenza, utilizziamo una libreria per creare client/server web chiamata http4k; come si può facilmente immaginare, questa è solo una delle numerose opzioni: altre scelte popolari sono Spring/Spring Boot [4] e Ktor [5]. http4k permette di creare una semplice applicazione con pochissime righe di codice e nessuna configurazione.
Iniziamo dunque con il client, creando un file CryptoEvalServer.kt che conterrà tutta l’applicazione. Creiamo una data class per definire la quotazione di una criptovaluta:
data class Quote(val symbol: String, val priceUsd: BigDecimal)
La funzione per scaricare le quotazioni è altrettanto semplice:
fun retrieveQuotes() : List<Quote> { val client = ApacheClient() val request = Request(Method.GET, "https://api.coinmarketcap.com/v1/ticker/?limit=20") val jsonResponse = parse(client(request).bodyString()) fun toQuote(jsonQuote: JsonNode) = Quote( symbol = jsonQuote["symbol"].asText(), priceUsd = jsonQuote["price_usd"].asText().toBigDecimal() ) return jsonResponse.map(::toQuote) }
Breve analisi del codice
Qualche approfondimento sul codice di questa funzione:
- ApacheClient, Request, Method sono classi fornite da http4k che rappresentano, rispettivamente, il client web, la richiesta HTTP e il metodo (verbo) HTTP da utilizzare.
- parse è un metodo dell’oggetto Jackson definito da http4k per effettuare il parsing da stringa a JSON.
Una nota a parte merita la funzione toQuote che è definita all’interno della funzione retrieveQuotes. Come è naturale aspettarsi, Kotlin fornisce questa possibilità per definire funzioni che siano visibili in uno scope molto limitato; ovviamente sarebbe possibile anche riscrivere la funzione come una lambda expression direttamente nella linea successiva (jsonResponse.map); ma, in molti casi, rendere il codice troppo conciso ne penalizza la leggibilità.
Aggiungiamo infine la funzione main per testare velocemente il nostro client.
fun main(args: Array<String>) { println(retrieveQuotes()) }
Una volta eseguita, la funzione main stampa sulla console una lista di quotazioni delle principali criptovalute.
Trovare la quotazione di una singola cryptocurrency
La seconda funzionalità che vogliamo sviluppare consiste nel trovare la quotazione di una singola criptovaluta (il branch find del progetto su Github contiene il codice completo [8]).
Se dovessimo sviluppare un’applicazione commerciale, la soluzione sarebbe probabilmente disporre di un database — persistente su disco o in memoria — in cui aggiornare periodicamente le quotazioni, ed effettuare una lookup su tale database per trovare la quotazione della criptovaluta.
Per il nostro esempio, vogliamo evitare la complicazione di gestire ed aggiornare il database, per cui chiameremo CoinMarketCap ogni volta che il nostro servizio viene richiesto, e cercheremo la quotazione che ci interessa tra quelle restituite dalla API.
Extension function
Per quanto inefficiente, questa soluzione ci permette di introdurre una funzionalità avanzata di Kotlin chiamata extension function. Una extension function è la possibilità fornita da un linguaggio di programmazione di estendere una classe senza dover ereditare da essa o utilizzare alcun design pattern. La sintassi è del tutto analoga a quella per definire una funzione, con l’unica differenza che deve ovviamente essere specificato il nome della classe a cui la funzione viene aggiunta.
Vediamo nel nostro esempio la funzione findQuote
fun List<Quote>.findQuote(symbol: String) = this.find { it.symbol.toUpperCase() == symbol.toUpperCase() }?.priceUsd ?: BigDecimal.ZERO
La funzione findQuote è aggiunta al tipo List<Quote>, che è il tipo di ritorno della funzione retrieveQuotes definita nella sezione precedente. Il corpo della funzione non fa altro che iterare sulla lista usando il metodo find fino a trovare una quotazione per il simbolo richiesto; si noti inoltre come venga utilizzata una safe call per determinare il prezzo della quotazione, in modo da evitare una NullPointerException nel caso il simbolo non abbia una quotazione. Infine l’Elvis operator ?: ci assicura che, nel caso la safe call restituisca null, la quotazione restuita dalla funzione sia zero.
Possiamo a questo punto modificare la funzione main perché ci restituisca solo la quotazione in dollari di Bitcoin
fun main(args: Array<String>) { println(retrieveQuotes().findQuote("BTC")) }
Come si può vedere, la funzione findQuote è ora a tutti effetti un metodo della classe List<Quote>.
Il servizio REST
Uno dei principi di http4k è Application as a Function, ossia l’idea che una applicazione possa essere definita da una, o più, semplici funzioni. Nel nostro caso la nostra funzione deve solamente:
- chiamare il client di CoinMarketCap per scaricare le ultime quotazioni;
- analizzare l’URL con cui il servizio è stato invocato (ricordiamo, del tipo http://localhost:9000/?eth=1&btc=2<c=12) per determinare le criptovalute contenute nel portfolio e le loro quantità;
- moltiplicare ciascuna quantità per il valore della criptovaluta corrispondente;
- sommare tutti i valori per determinare il valore totale del portfolio.
La funzione endpoint risultante — che riceve una richiesta HTTP come parametro e restituisce una risposta http — è la seguente:
fun endpoint(request: Request) : Response { val quotes = retrieveQuotes() val portfolio = request.uri.queries() .map { ( symbol, quantity) -> BigDecimal(quantity) * quotes.findQuote(symbol) } .fold(BigDecimal.ZERO, { first, second → first + second }) return Response(OK).body("$portfolio") }
Destructuring
Per capire il codice della funzione, e in particolare la seconda istruzione che contiene tutta la logica per determinare il valore del portfolio, dobbiamo prima parlare di una seconda funzionalità avanzata di Kotlin chiamata destructuring.
L’invocazione a request.uri.queries() restituisce una lista di Parameter, dove Parameter è una classe fornita da http4k che non è altro che un alias della classe standard Pair [6]. Semplificando, possiamo pensare a Pair come una coppia di valori definita da una data class del tipo:
data class Pair<A, B>(val first: A, val second: B)
Una data class in Kotlin può essere sempre destrutturata, ossia, dato un oggetto myPair di tipo Pair, possiamo risalire ai suoi parametri scrivendo:
val (first, second) = myPair
A questo punto abbiamo tutte le conoscenze per comprendere la seconda istruzione della funzione endpoint che calcola il valore totale del portfolio:
- inizialmente tramite uri.queries() troviamo la lista delle cryptocurrencies contenute nel portfolio; l’invocazione restituisce una lista di Pair(<symbol>, <quantity>);
- grazie alla destrutturazione, nel metodo map assegniamo alla variabile symbol il primo elemento del Pair, e a quantity il secondo; la lambda expression moltiplica quantity alla quotazione della criptovaluta symbol che è determinata invocando l‘extension function findQuote definita nella sezione precedente;
- sommiamo tutti i valori in dollari per ciascuna criptovaluta con il metodo fold, che funziona come l’analogo metodo nella Collection API di Java 8
La funzione restituisce infine una HTTP Response contenente solo il valore complessivo in dollari del portfolio.
Come eseguire il servizio REST
Eseguire la nostra funzione endpoint all’interno di un server Web a questo punto è semplicissimo: si tratta solo di eseguire il metodo asServer della funzione endpoint.
fun main(args: Array<String>) { ::endpoint.asServer(Jetty(9000)).start() }
Come si può vedere, ancora una volta il codice è estremamente semplice e conciso. Tuttavia quando abbiamo definito la funzione endpoint nella sezione precedente non abbiamo definito nessun metodo asServer; inoltre, come è possibile che una funzione esponga un metodo?
Andiamo a vedere come gli sviluppatori di http4k hanno implementato questo “trucco” utilizzando i typealiases [6] e le extension functions [7].
Per prima cosa notiamo che endpoint è una funzione che accetta una Request come unico parametro e ritorna una Response. Come sospettiamo, http4k ha definito il seguente alias
typealias HttpHandler = (Request) -> Response
Questo costrutto semplicemente definisce un nome alternativo, HttpHandler, per una funzione che accetti una Request e restituisca una Response: quindi la nostra funzione endpoint è un HttpHandler.
Tuttavia HttpHandler non è solo un “nome alternativo”, ma anche a tutti gli effetti una classe; quindi è possibile estendere tale classe con delle funzioni (le extension functions che abbiamo visto in precedenza). E infatti, scorrendo il codice di http4k, incontriamo la seguente:
fun HttpHandler.asServer(config: ServerConfig): Http4kServer = ...
Ricapitolando: la funzione endpoint che abbiamo scritto è implicitamente definita come tipo HttpHandler a cui sono stati associati alcuni metodi che possiamo utilizzare, come asServer. Questo pattern davvero insolito per chi sia abituato a linguaggi di programmazione solamente object oriented permette di rendere il codice estremamente compatto, senza sacrificarne la leggibilità: basta infatti navigare con il proprio IDE attraverso le funzioni per capirne immediatamente il funzionamento.
Conclusione
In questo articolo abbiamo sviluppato un servizio REST per calcolare il valore di un portfolio di criptovalute in Kotlin usando la libreria http4k per creare i client/server Web. Nel fare ciò abbiamo introdotto tre concetti di programmazione avanzata di Kotlin (destructuring, extension functions e typealiases) e visto come questi possano essere usati in congiunzione per rendere il codice estremamente conciso e potente.
Concisione e produttività, come premesso nel primo articolo di questa serie, sono gli obiettivi della comunità di sviluppo di Kotlin e ci sembra di poter dire che questa applicazione lo dimostri ampiamente: il servizio web è completamente definito in meno di 40 righe di codice estremamente semplici da comprendere attrezzati con i pochi concetti di base esposti in questi tre articoli.
Filippo Diotalevi si occupa di consulenza IT da circa 20 anni. Dopo la laurea in Ingegneria Informatica, ha lavorato in Italia e Germania, e si è stabilito da alcuni anni a Londra dove continua ad essere attivo nella progettazione e sviluppo di applicazioni per la Java Virtual Machine.
È autore di due libri su Java e di numerosi articoli tecnici apparsi su riviste e siti italiani e internazionali.