In questa lezione, mettiamo in pratica i concetti sin qui acquisiti realizzando un servizio di esempio con GraphQL. Non avremo alcun database persistente a disposizione, ma salveremo le informazioni all'interno di un oggetto Javascript che usa un array come archivio: la nostra massima attenzione sarà infatti
per la definizione delle API. GraphQL può essere implementato in molti modi ed in vari linguaggi: qui faremo uso
del linguaggio Javascript basandoci sul substrato che offre Node.js.
L'esempio: un database per proverbi
In questo esempio creiamo una classe Javascript, denominata Archivio, che contiene al suo interno
un array di proverbi sui quali operano i metodi:
- proverbioRandom, che estrae un proverbio a caso e lo restituisce;
- nuovo, che immagazzina un nuovo proverbio passato come argomento;
- selezioneProverbi, che riceve come argomento un numero intero che indica quanti proverbi devono essere restituiti al chiamante. Saranno forniti come risultato i primi dell'array.
La classe restituisce anche un oggetto di classe StatoServizio che contiene il numero di accessi
eseguiti al metodo proverbioRandom ed il risultato ottenuto nella sua ultima invocazione. Questo è
il codice con cui lo realizziamo:
class StatoServizio {
constructor(){
this.accessi=0
}
proverbioEmesso(proverbio){
this.ultimo=proverbio
this.accessi++
}
}
class Archivio{
constructor(proverbi){
this.proverbi=proverbi
this.stato=new StatoServizio()
}
proverbioRandom() {
var item = this.proverbi[Math.floor(Math.random()*this.proverbi.length)];
this.stato.proverbioEmesso(item)
return item
}
nuovo(proverbio)
{
this.proverbi.push(proverbio)
return this.proverbi.length
}
selezioneProverbi(quanti){
if (quanti>0){
return this.proverbi.slice(0, quanti)
}
}
}
Nello script con cui eseguiamo il server immettiamo il codice necessario per creare un oggetto di classe Archivio inizializzandolo con un lotto di proverbi:
archivio = new Archivio(['Gallina vecchia fa buon brodo',
'Il mattino ha l\'oro in bocca',
'Tra moglie e marito non mettere il dito',
'Una mela al giorno toglie il medico di torno',
'Ai pazzi si dà sempre ragione',
'Anno nuovo vita nuova',
'Bacco, tabacco e Venere riducon l\'uomo in cenere',
'Battere il ferro finché è caldo',
'Buon sangue non mente'])
Realizzazione dell'esempio
Per realizzare un servizio che condivida queste funzionalità con GraphQL seguiremo questi passaggi:
- importazione delle librerie necessarie: per lo più il framework Express ed il linguaggio GraphQL;
- strutturazione dello schema che definisce tipi di dato, query e mutation;
- implementazione delle funzionalità in un oggetto che risponderà alla variabile root;
- avvio del server sulla porta TCP di nostra scelta.
I paragrafi successivi descrivono lo sviluppo nel dettaglio
Le librerie necessarie
Prepariamo l'ambiente necessario da riga di comando con npm:
> npm init
> npm install express express-graphql graphql --save
Importiamo nel progetto le librerie necessarie con le seguenti direttive:
var express = require('express');
var http = require('express-graphql');
var { buildSchema } = require('graphql');
Tramite buildSchema potremo passare a definire i tipi di dato del servizio.
Lo schema
Nello schema definiamo: il tipo Query, che raccoglie tutte le funzionalità di interrogazione al servizio ossia quelle che restituiscono informazioni senza apportare alcune modifiche a quanto archiviato nel server; il tipo Mutation, che raccoglie funzionalità di inserimento cancellazione e modifica
di dati; il tipo StatoServizio, che sarà necessario in quanto, come vedremo, restituito da uno dei metodi della Query:
var def = buildSchema(`
type StatoServizio{
ultimo: String
accessi: Int
}
type Query {
proverbioRandom: String
stato: StatoServizio
proverbi(quanti: Int): [String]
}
type Mutation{
nuovo(nuovo: String): Int
}
`);
A scopo didattico, abbiamo fatto in modo di raccogliere in questi casi una serie di casistiche differenti:
- proverbioRandom viene interrogato senza argomenti, restituisce un oggetto di natura
String
; - proverbi è anch'esso una funzionalità di sola lettura ma pone due aspetti interessanti: richiede un
argomento e restituisce un array di oggetti (notare le parentesi quadre in[String]
); - stato è ancora un metodo in lettura ma non restituisce un oggetto di tipo built-in bensì un'istanza di StatoServizio, da noi definita. Questa classe contiene due elementi e vedremo come se ne potrà specificare quali restituire grazie alla flessibilità del linguaggio GraphQL;
- in Mutation creiamo l'unico metodo che apporta modifiche.
Si noti che la distinzione tra Query e Mutation separeranno nettamente le funzionalità in sola
lettura (corrispondenti alle GET
di REST) e quelle in modifica (i vari metodi POST
, PUT
, DELETE
del paradigma REST).
Implementazione delle funzionalità
Quanto previsto nello schema verrà implementato nell'oggetto root:
var root = {
// Query
proverbioRandom: () =>
{
return archivio.proverbioRandom()
},
stato: () => {
return archivio.stato
},
proverbi: ({quanti}) => {
return archivio.selezioneProverbi(quanti)
},
// Mutation
nuovo: ({nuovo}) => {
archivio.nuovo(nuovo);
return archivio.proverbi.length
}
};
Vi inseriamo sia le funzioni relative alla Query sia quella che offre una Mutation. Non facciamo nulla di diverso da qualsiasi altro contesto Javascript: riceviamo eventuali argomenti, otteniamo le informazioni accedendo all'oggetto archivio e le restituiamo.
Avvio del server
Nella fase finale dello script, avviamo il server Express, che mette a disposizione il fondamento necessario
per rendere il tutto raggiungibile via Rete:
var app = express();
app.use('/proverbi', http({
schema: def,
rootValue: root,
graphiql: true,
}));
app.listen(8888);
console.log('Servizio in esecuzione su localhost:8888/proverbi');
Si notino i seguenti punti:
- abbiamo definito l'endpoint ove risponde il servizio, "/proverbi";
- passiamo i riferimenti a schema e oggetto root che abbiamo utilizzato, rispettivamente, per contenere definizione dei tipi e implementazione delle funzionalità;
- attiviamo graphiql, che vedremo a breve al lavoro. Si tratta di un'applicativo fruibile in browser
per testare subito le funzionalità implementate in GraphQL. Si presenta con un'interfaccia
divisa in due pannelli: in quello a sinistra si scrivono le interrogazioni GraphQL, in quella a destra
ne leggiamo l'esito. Specifichiamo come porta TCP di attivazione la numero 8888, ma questa potrà essere personalizzata a piacimento.
Fatto ciò si potrà avviare il tutto grazie a Node.js. Se il nostro file prende il nome di server.js, il comando per l'attivazione sarà:
> node server.js
Richieste e risposte: proviamo il servizio
Anche GraphQL lavora in una modalità richiesta/risposta. La richiesta ha un formato simile a JSON, mentre la risposta è effettivamente fornita come oggetto JSON. Le potremo
provare in GraphiQL, il servizio che sarà disponibile nel browser puntando l'endpoint che abbiamo
configurato: nel nostro esempio, http://localhost:8888/proverbi. Così apparirà l'interfaccia di
GraphiQL:
Come già accennato, non dovremo far altro che scrivere una richiesta nel pannello sinistro e, premuto il tasto con il triangolo nero in alto, vedere la risposta in quello a destra. Una richiesta verrà introdotta da una parola chiave query o mutation a seconda del tipo di funzionalità cui stiamo accedendo.
Eseguendo la query:
query{
proverbioRandom
}
otterremo risposte di questo tipo:
{
"data": {
"proverbioRandom": "Tra moglie e marito non mettere il dito"
}
}
Come si vede, il campo data contiene proprio il risultato che abbiamo richiesto. Dopo alcune interrogazioni simili a questa si potrà richiedere lo stato attuale del servizio:
query{
stato {
ultimo
accessi
}
}
ottenendo:
{
"data": {
"stato": {
"ultimo": "Il mattino ha l'oro in bocca",
"accessi": 15
}
}
}
Questa query dimostra la flessibilità offerta da GraphQL: senza cambiare endpoint sarà sufficiente chiedere
meno dati per ottenere risultati più precisi. Infatti, rimuovendo il campo accessi dalla richiesta:
query{
stato {
ultimo
}
}
verrà, in maniera corrispondente, ristretta la risposta:
{
"data": {
"stato": {
"ultimo": "Il mattino ha l'oro in bocca"
}
}
}
Infine, possiamo vedere una mutation con la quale aggiungeremo un ulteriore proverbio:
mutation{
nuovo(nuovo:"Vivi e lascia vivere")
}
e la risposta ci comunicherà il nuovo numero di proverbi presenti nel database:
{
"data": {
"nuovo": 10
}
}
Il codice dell'esempio descritto in questa lezione è disponibile a questo link.