Daniele Irsuti - Frontend developer

Graphql: getting started

Bomba: GraphQL sostituirà REST nei prossimi anni.

Cos’è GraphQL? Come cita wikipedia:

“open-source data query and manipulation language for APIs”.

BOOM! fammi vedere

Abbiamo un client ed un endpoint dal quale prendere dei dati:

Il client manda in POST un payload del genere:

query {
  events(limit: 5) {
    quakes {
      description
      magnitude {
        value,
        uncertainty
      }
    }
  }
}

Questo strano payload che ricorda un json, si tratta di una tipica query in GraphQL ed in realtà è piuttosto auto-descrittiva.

In breve: sta chiedendo gli ultimi 5 eventi sismici accaduti con descrizione del luogo e valore del magnitudo con un relativo margine d’incertezza (almeno credo, magari INGV ne saprà di più). La risposta alla query ritornerà un JSON come segue:

{
  "data": {
    "events": {
      "quakes": [
        {
          "description": "7 km W Monte Cavallo (MC)",
          "magnitude": {
            "value": 0.8,
            "uncertainty": 0.5
          }
        },
        {
          "description": "7 km W Monte Cavallo (MC)",
          "magnitude": {
            "value": 0.9,
            "uncertainty": 0.2
          }
        },
        {
          "description": "3 km SE Mattie (TO)",
          "magnitude": {
            "value": 2.1,
            "uncertainty": 0.3
          }
        },
        {
          "description": "3 km SW San Piero Patti (ME)",
          "magnitude": {
            "value": 2,
            "uncertainty": 0.5
          }
        },
        {
          "description": "3 km SE Pietralunga (PG)",
          "magnitude": {
            "value": 1,
            "uncertainty": 0.1
          }
        }
      ]
    }
  }
}

Punti di forza

Qual’è il punto di forza in ciò che abbiamo appena visto?

Che ricevo esattamente ciò che chiedo; nessun dato superfluo

In REST molto spesso accade che per comporre il layout di una webapp ci vediamo costretti ad effettuare più richieste e spesso quest’ultime ci forniscono più dati di quanto abbiamo bisogno (overfetching).

A volte i dati che riceviamo, nonostante la mole di informazioni, non sono sufficenti al nostro scopo e siamo costretti ad effettuare ulteriori chiamate (underfetching).

A questo scopo GraphQL vince a mani basse il confronto con REST ed è per questo che anche Netflix ha deciso di includerlo nel suo progetto (https://medium.com/netflix-techblog/our-learnings-from-adopting-graphql-f099de39ae5f).

GraphQL ti permette di veicolare qualsiasi tipo di dato che proviene da un altro server o db a passare da un unico endpoint. Nell’articolo precedentemente linkato Netflix spiega come il passaggio da REST a GraphQL sia stata un’esperienza molto positiva che non ha comportato stravolgimento nel codice.

I miei primi passi

In tutto ciò ho deciso di realizzare qualcosa che non fosse già messa in giro da nessun altro (almeno credo!), così da rendere quest’esperienza la più unica possibile ed evitare il copy/paste nei momenti di difficoltà.

INGV Logo

Step 1

Prima di tutto ci serve un web server. Express è stato il primo a venirmi in mente ed è stata una scelta azzeccata perché esiste un middleware di graphQL per express.

Ed ecco qui tutto il codice:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const app = express();

const schema = require('./schema') // tranquilli ci arriviamo.

app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    graphiql: true
  })
);

app.listen(process.env.PORT || 8080);
console.log('Listening....');

Step 2

Visto quell’import schema? Quello sarà il nostro barattolo da cui attingere.

Per cominciare esporteremo un GraphQLSchema:

module.exports = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    description:
      'Ritorna tutti gli eventi sismici riportati in un certo periodo',
      fields: () => {
        return {
            events: { 
                type: QuakesType,
                args: {
                    starttime: { type: GraphQLString }
                },
                resolve: (root, args) => {
                    const endpoint = `http://webservices.ingv.it/fdsnws/event/1/query`;
                    const params = new URLSearchParams();

                    Object.keys(args).map(arg =>
                    args[arg] ? params.append(arg, args[arg]) : null
                    );

                    return fetch(endpoint + '?' + params.toString())
                    .then(response => response.text())
                    .then(parseXML);
                }
            }
        }
      }
  })
})

Cosa abbiamo qui? Vi evidenzio un po’ di punti chiave:

  • GraphQLSchema: Questo è lo schema;
  • GraphQLObjectType: Questo crea il tipo di oggetto che verrà utilizzato per eseguire le query;
  • QuakesType: sarà il tipo contenuto nel oggetto events ossia la risposta della query;
  • args: sono i parametri della query di ricerca
  • starttime: { type: GraphQLString }: è il tipo del parametro di ricerca.

Step 3

Abbiamo già quello che sarà lo schema ma iniziamo a comporre i vari modelli che comporranno lo schema.

const MagType = new GraphQLObjectType({
  name: 'Magnitude',
  description: 'Magnitudo e margine di incertezza',

  fields: () => ({
    value: { type: GraphQLFloat },
    uncertainty: { type: GraphQLFloat }
  })
});

const QuakeType = new GraphQLObjectType({
  name: 'Quake',
  description: 'Evento sismico',

  fields: () => ({
    description: {
      type: GraphQLString
    },
    magnitude: {
      type: MagType
    }
  })
});

const QuakesType = new GraphQLObjectType({
  name: 'Quakes',
  description: 'Una lista di eventi sismici',

  fields: () => ({
    quakes: {
      type: new GraphQLList(QuakeType),
      resolve: xml => {
        return xml['q:quakeml']['eventParameters'][0].event.map(event => {
          return {
            description: event.description[0].text[0],
            magnitude: {
              value: event.magnitude[0].mag[0].value[0],
              uncertainty: event.magnitude[0].mag[0].uncertainty[0],
            }
            // Ometto per brevità, trovate il sorgente completo su GitHub
          };
        });
      }
    }
  })

Questo è un punto cruciale. Nello snippet precedente ho mostrato una fetch, che viene, tramite currying, passata per la funzione parseXml da xml a json. Il json prodotto beh… è abbastanza sporco ma è intrinseco nella natura dell’xml ed in particolare di questo xml.

In sostanza la promise dell’xml viene passata come parametro al primo modello QuakesType che, tramite il metodo resolve, ha il compito di mappare l’oggetto risolto con la lista di tipo QuakeType. Infatti nel seguente punto:

resolve: xml => {
    return xml['q:quakeml']['eventParameters'][0].event.map(event => {
        return {
        description: event.description[0].text[0],
        magnitude: {
            value: event.magnitude[0].mag[0].value[0],
            uncertainty: event.magnitude[0].mag[0].uncertainty[0],
        }
        // Ometto per brevità, trovate il sorgente completo su GitHub
        };
    });
}

Noterete a questo punto che il mapping descritto è simile al metodo fields del modello QuakeType:

  fields: () => ({
    description: {
      type: GraphQLString
    },
    magnitude: {
      type: MagType // { value: ..., uncertainty: ...}
    }
  })

Step 4

Funziona? Provate voi. Per brevità ho omesso molte cose e potrei anche aver commesso dei typo per cui vi consiglio di dare un’occhiata repository dell’esercizio svolto.

Conclusione

Qui giunge al termine questa introduzione su GraphQl. Forse non molto accurata, ma abbastanza pratica. Seguiranno altri articoli, finora ho trattato soltanto la lettura di dati, ma come funzionerà per modificare dati?

Link