Introduzione a GraphQL

1. Panoramica

GraphQL è un linguaggio di query, creato da Facebook con lo scopo di creare applicazioni client basate su una sintassi intuitiva e flessibile, per descrivere i requisiti e le interazioni dei dati.

Una delle sfide principali con le chiamate REST tradizionali è l'incapacità del client di richiedere un set di dati personalizzato (limitato o ampliato). Nella maggior parte dei casi, una volta che il client richiede le informazioni dal server, ottiene tutti o nessuno dei campi.

Un'altra difficoltà è lavorare e mantenere più endpoint. Man mano che una piattaforma cresce, di conseguenza il numero aumenterà. Pertanto, i client spesso devono richiedere dati da endpoint diversi.

Quando si costruisce un server GraphQL, è necessario avere solo un URL per tutti i dati che vengono caricati e modificati. Pertanto, un client può richiedere un set di dati inviando una stringa di query, descrivendo ciò che desidera, a un server.

2. Nomenclatura GraphQL di base

Diamo uno sguardo alla terminologia di base di GraphQL.

  • Query: è un'operazione di sola lettura richiesta a un server GraphQL
  • Mutazione: è un'operazione di lettura-scrittura richiesta a un server GraphQL
  • Resolver: in GraphQL, il Resolver è responsabile della mappatura dell'operazione e del codice in esecuzione sul backend che è responsabile della gestione della richiesta. È analogo al backend MVC in un'applicazione RESTFul
  • Tipo: un tipo definisce la forma dei dati di risposta che possono essere restituiti dal server GraphQL, inclusi i campi che sono bordi di altri tipi
  • Input: come un Type, ma definisce la forma dei dati di input inviati a un server GraphQL
  • Scalare: è un tipo primitivo , come String , Int , Boolean , Float , ecc
  • Interfaccia: Un'interfaccia memorizzerà i nomi dei campi e dei loro argomenti, in modo che gli oggetti GraphQL possano ereditarli, garantendo l'uso di campi specifici
  • Schema: in GraphQL, lo schema gestisce le query e le mutazioni, definendo ciò che è consentito eseguire nel server GraphQL

2.1. Caricamento dello schema

Esistono due modi per caricare uno schema nel server GraphQL:

  1. utilizzando Interface Definition Language (IDL) di GraphQL
  2. utilizzando uno dei linguaggi di programmazione supportati

Dimostriamo un esempio usando IDL:

type User { firstName: String }

Ora, un esempio di definizione dello schema utilizzando il codice Java:

GraphQLObjectType userType = newObject() .name("User") .field(newFieldDefinition() .name("firstName") .type(GraphQLString)) .build();

3. Linguaggio di definizione dell'interfaccia

Interface Definition Language (IDL) o Schema Definition Language (SDL) è il modo più conciso per specificare uno schema GraphQL. La sintassi è ben definita e sarà adottata nella specifica ufficiale di GraphQL.

Ad esempio, creiamo uno schema GraphQL per un utente / le email potrebbero essere specificate in questo modo:

schema { query: QueryType } enum Gender { MALE FEMALE } type User { id: String! firstName: String! lastName: String! createdAt: DateTime! age: Int! @default(value: 0) gender: [Gender]! emails: [Email!]! @relation(name: "Emails") } type Email { id: String! email: String! default: Int! @default(value: 0) user: User @relation(name: "Emails") }

4. GraphQL-java

GraphQL-java è un'implementazione basata sulla specifica e sull'implementazione di riferimento JavaScript. Nota che richiede almeno Java 8 per funzionare correttamente.

4.1. Annotazioni GraphQL-java

GraphQL rende anche possibile utilizzare le annotazioni Java per generare la sua definizione dello schema senza tutto il codice boilerplate creato dall'uso del tradizionale approccio IDL.

4.2. Dipendenze

Per creare il nostro esempio, iniziamo innanzitutto a importare la dipendenza richiesta che si basa sul modulo Graphql-java-annotations:

 com.graphql-java graphql-java-annotations 3.0.3 

Stiamo anche implementando una libreria HTTP per semplificare l'installazione nella nostra applicazione. Utilizzeremo Ratpack (sebbene possa essere implementato anche con Vert.x, Spark, Dropwizard, Spring Boot, ecc.).

Importiamo anche la dipendenza Ratpack:

 io.ratpack ratpack-core 1.4.6 

4.3. Implementazione

Creiamo il nostro esempio: una semplice API che fornisce un "CRUDL" (Crea, Recupera, Aggiorna, Elimina ed Elenco) per gli utenti. Per prima cosa, creiamo il nostro POJO utente :

@GraphQLName("user") public class User { @GraphQLField private Long id; @GraphQLField private String name; @GraphQLField private String email; // getters, setters, constructors, and helper methods omitted }

In questo POJO possiamo vedere l' annotazione @GraphQLName ("utente") , come un'indicazione che questa classe è mappata da GraphQL insieme a ogni campo annotato con @GraphQLField.

Successivamente, creeremo la classe UserHandler . Questa classe eredita dalla libreria del connettore HTTP scelta (nel nostro caso, Ratpack) un metodo gestore, che gestirà e invocerà la funzionalità Resolver di GraphQL . Pertanto, reindirizzando la richiesta (payload JSON) alla query o all'operazione di mutazione corretta:

@Override public void handle(Context context) throws Exception { context.parse(Map.class) .then(payload -> { Map parameters = (Map) payload.get("parameters"); ExecutionResult executionResult = graphql .execute(payload.get(SchemaUtils.QUERY) .toString(), null, this, parameters); Map result = new LinkedHashMap(); if (executionResult.getErrors().isEmpty()) { result.put(SchemaUtils.DATA, executionResult.getData()); } else { result.put(SchemaUtils.ERRORS, executionResult.getErrors()); LOGGER.warning("Errors: " + executionResult.getErrors()); } context.render(json(result)); }); }

Ora, la classe che supporterà le operazioni di query, ovvero UserQuery. Come accennato tutti i metodi che recuperano i dati dal server al client sono gestiti da questa classe:

@GraphQLName("query") public class UserQuery { @GraphQLField public static User retrieveUser( DataFetchingEnvironment env, @NotNull @GraphQLName("id") String id) { // return user } @GraphQLField public static List listUsers(DataFetchingEnvironment env) { // return list of users } }

Similarly to UserQuery, now we create UserMutation, which will manage all the operations that intend to change some given data stored on the server side:

@GraphQLName("mutation") public class UserMutation { @GraphQLField public static User createUser( DataFetchingEnvironment env, @NotNull @GraphQLName("name") String name, @NotNull @GraphQLName("email") String email) { //create user information } }

It is worth notice the annotations in both UserQuery and UserMutation classes: @GraphQLName(“query”) and @GraphQLName(“mutation”). Those annotations are used to define the query and mutation operations respectively.

With the GraphQL-java server able to run the query and mutation operations, we can use the following JSON payloads to test the request of the client against the server:

  • For the CREATE operation:
{ "query": "mutation($name: String! $email: String!){ createUser (name: $name email: $email) { id name email age } }", "parameters": { "name": "John", "email": "[email protected]" } } 

As the response from the server for this operation:

{ "data": { "createUser": { "id": 1, "name": "John", "email": "[email protected]" } } }
  • For the RETRIEVE operation:
{ "query": "query($id: String!){ retrieveUser (id: $id) {name email} }", "parameters": { "id": 1 } }

As the response from the server for this operation:

{ "data": { "retrieveUser": { "name": "John", "email": "[email protected]" } } }

GraphQL fornisce funzionalità che il client può personalizzare la risposta. Quindi, nell'ultima operazione RETRIEVE usata come esempio, invece di restituire il nome e l'email, possiamo, ad esempio, restituire solo l'email:

{ "query": "query($id: String!){ retrieveUser (id: $id) {email} }", "parameters": { "id": 1 } }

Quindi, le informazioni di ritorno dal server GraphQL restituiranno solo i dati richiesti:

{ "data": { "retrieveUser": { "email": "[email protected]" } } }

5. conclusione

GraphQL è un modo semplice e molto interessante per ridurre al minimo la complessità tra client / server come approccio alternativo alle API REST.

Come sempre, l'esempio è disponibile nel nostro repository GitHub.