Introduction à GraphQL

1. Vue d'ensemble

GraphQL est un langage de requête, créé par Facebook dans le but de créer des applications client basées sur une syntaxe intuitive et flexible, pour décrire leurs besoins en données et leurs interactions.

L'un des principaux défis des appels REST traditionnels est l'incapacité du client à demander un ensemble de données personnalisé (limité ou étendu). Dans la plupart des cas, une fois que le client demande des informations au serveur, il obtient tous ou aucun des champs.

Une autre difficulté consiste à travailler et à gérer plusieurs points de terminaison. À mesure qu'une plate-forme se développe, le nombre augmentera par conséquent. Par conséquent, les clients doivent souvent demander des données à différents points de terminaison.

Lors de la construction d'un serveur GraphQL, il est seulement nécessaire d'avoir une URL pour toutes les données de récupération et de mutation. Ainsi, un client peut demander un ensemble de données en envoyant une chaîne de requête, décrivant ce qu'il veut, à un serveur.

2. Nomenclature de base GraphQL

Jetons un coup d'œil à la terminologie de base de GraphQL.

  • Requête: est une opération en lecture seule demandée à un serveur GraphQL
  • Mutation: est une opération de lecture-écriture demandée à un serveur GraphQL
  • Résolveur: dans GraphQL, le résolveur est responsable du mappage de l'opération et du code en cours d'exécution sur le backend qui est chargé de traiter la demande. Il est analogue au backend MVC dans une application RESTFul
  • Type: un type définit la forme des données de réponse qui peuvent être renvoyées par le serveur GraphQL, y compris les champs qui sont des arêtes vers d'autres types
  • Entrée: comme un Type, mais définit la forme des données d'entrée qui sont envoyées à un serveur GraphQL
  • Scalaire: est un type primitif , tel qu'une chaîne , un int , un booléen , un flottant , etc.
  • Interface: une interface stockera les noms des champs et leurs arguments, afin que les objets GraphQL puissent en hériter, assurant l'utilisation de champs spécifiques
  • Schéma: dans GraphQL, le schéma gère les requêtes et les mutations, définissant ce qui peut être exécuté dans le serveur GraphQL

2.1. Chargement du schéma

Il existe deux manières de charger un schéma dans le serveur GraphQL:

  1. en utilisant le langage de définition d'interface (IDL) de GraphQL
  2. en utilisant l'un des langages de programmation pris en charge

Montrons un exemple utilisant IDL:

type User { firstName: String }

Maintenant, un exemple de définition de schéma utilisant du code Java:

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

3. Langage de définition d'interface

Le langage de définition d'interface (IDL) ou le langage de définition de schéma (SDL) est le moyen le plus concis de spécifier un schéma GraphQL. La syntaxe est bien définie et sera adoptée dans la spécification officielle de GraphQL.

Par exemple, créons un schéma GraphQL pour un utilisateur / e-mails pourrait être spécifié comme ceci:

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 est une implémentation basée sur la spécification et l'implémentation de référence JavaScript. Notez qu'il nécessite au moins Java 8 pour fonctionner correctement.

4.1. Annotations GraphQL-java

GraphQL permet également d'utiliser les annotations Java pour générer sa définition de schéma sans tout le code standard créé par l'utilisation de l'approche IDL traditionnelle.

4.2. Dépendances

Pour créer notre exemple, commençons par importer la dépendance requise qui repose sur le module Graphql-java-annotations:

 com.graphql-java graphql-java-annotations 3.0.3 

Nous implémentons également une bibliothèque HTTP pour faciliter la configuration dans notre application. Nous allons utiliser Ratpack (bien qu'il puisse également être implémenté avec Vert.x, Spark, Dropwizard, Spring Boot, etc.).

Importons également la dépendance Ratpack:

 io.ratpack ratpack-core 1.4.6 

4.3. la mise en oeuvre

Créons notre exemple: une API simple qui fournit un «CRUDL» (Créer, Récupérer, Mettre à jour, Supprimer et Liste) pour les utilisateurs. Tout d'abord, créons notre POJO utilisateur :

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

Dans ce POJO, nous pouvons voir l' annotation @GraphQLName («utilisateur») , comme une indication que cette classe est mappée par GraphQL avec chaque champ annoté avec @GraphQLField.

Ensuite, nous allons créer la classe UserHandler . Cette classe hérite de la bibliothèque de connecteurs HTTP choisi (dans notre cas, Ratpack) une méthode de gestionnaire, qui va gérer et Invoke du GraphQL résolveur la fonction. Ainsi, rediriger la requête (charges utiles JSON) vers la requête ou l'opération de mutation appropriée:

@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)); }); }

Maintenant, la classe qui prendra en charge les opérations de requête, c'est-à-dire UserQuery. Comme mentionné, toutes les méthodes qui récupèrent les données du serveur vers le client sont gérées par cette 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 fournit des fonctionnalités que le client peut personnaliser la réponse. Ainsi, dans la dernière opération RETRIEVE utilisée comme exemple, au lieu de renvoyer le nom et l'e-mail, nous pouvons, par exemple, renvoyer uniquement l'e-mail:

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

Ainsi, les informations renvoyées par le serveur GraphQL ne renverront que les données demandées:

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

5. Conclusion

GraphQL est un moyen simple et assez attrayant de minimiser la complexité entre client / serveur en tant qu'approche alternative aux API REST.

Comme toujours, l'exemple est disponible dans notre référentiel GitHub.