Introduction à GraphQL

1. Vue d’ensemble

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

L’un des principaux problèmes 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 les champs, voire aucun.

Une autre difficulté est de travailler et de maintenir plusieurs points finaux. Au fur et à mesure que la plate-forme grandit, le nombre augmente. Par conséquent, les clients doivent souvent demander des données provenant de différents terminaux.

Lors de la construction d’un serveur GraphQL, il suffit d’avoir une seule URL pour toutes les opérations d’extraction et de mutation des données. 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 GraphQL de base

Regardons la terminologie de base de GraphQL.

  • Query: 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 Resolver est chargé de mapper le

opération et le code en cours d’exécution sur le backend qui est responsable de gérer la demande. Il est analogue à MVC backend dans un RESTFul application Type: ** Un Type définit la forme des données de réponse pouvant être

renvoyé du serveur GraphQL, y compris les champs qui sont des bords à autres types Entrée: ** comme un Type, __ mais définit la forme des données d’entrée qui est

envoyé à un serveur GraphQL Scalar: ** est un type primitif, tel qu’un Chaîne , Int , Boolean ,

Float , etc Interface: ** Une interface stockera les noms des champs et leurs noms.

arguments, donc les objets GraphQL peuvent en hériter, assurant l’utilisation de champs spécifiques Schema: ** Dans GraphQL, le schéma gère les requêtes et les mutations,

définir ce qui est autorisé à être exécuté sur 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 IDL (Interface Definition Language) de GraphQL

  2. en utilisant l’un des langages de programmation pris en charge

Montrons un exemple en 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 GraphQL.

Par exemple, créons un schéma GraphQL pour un utilisateur/des courriels peuvent être spécifiés 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 sur la mise en œuvre de référence JavaScript. Notez que Java 8 est au minimum nécessaire pour fonctionner correctement.

4.1. Annotations GraphQL-java

GraphQL permet également d’utiliser Java annotations 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 https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.graphql-java%22%20AND%20a Module% 3A% 22graphql-java-annotations% 22[Graphql-java-annotations]:

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-annotations</artifactId>
    <version>3.0.3</version>
</dependency>

Nous mettons également en place une bibliothèque HTTP pour faciliter la configuration de notre application. Nous allons utiliser Ratpack (bien que il pourrait également être implémenté avec Vert.x, Spark, Dropwizard, Spring Boot, etc.).

Importons également la dépendance Ratpack:

<dependency>
    <groupId>io.ratpack</groupId>
    <artifactId>ratpack-core</artifactId>
    <version>1.4.6</version>
</dependency>

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. Commençons par créer notre User POJO:

@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 («user») , indiquant 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 choisie (dans notre cas, Ratpack) une méthode de gestionnaire, qui gérera et invoquera la fonctionnalité Resolver de GraphQL. Ainsi, la redirection de 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<String, Object> parameters = (Map<String, Object>)
            payload.get("parameters");
          ExecutionResult executionResult = graphql
            .execute(payload.get(SchemaUtils.QUERY)
              .toString(), null, this, parameters);
          Map<String, Object> 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 supportera les opérations de requête, c.-à-d. UserQuery. Comme mentionné, toutes les méthodes qui extraient des 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<User> listUsers(DataFetchingEnvironment env) {
       //return list of users
    }
}

De la même manière que UserQuery, nous créons maintenant UserMutation, qui gérera toutes les opérations visant à modifier certaines données stockées sur le serveur:

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

Notez les annotations dans les classes UserQuery et UserMutation : @ GraphQLName («query») et @ GraphQLName («mutation») . Ces annotations sont utilisées pour définir les opérations de requête et de mutation, respectivement.

Le serveur GraphQL-java pouvant exécuter les opérations de requête et de mutation, nous pouvons utiliser les charges utiles JSON suivantes pour tester la demande du client par rapport au serveur:

  • Pour l’opération CREATE:

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

Comme réponse du serveur pour cette opération:

{
    "data": {
        "createUser": {
            "id": 1,
            "name": "John",
            "email": "[email protected]"
        }
    }
}
  • Pour l’opération RETRIEVE:

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

Comme réponse du serveur pour cette opération:

{
    "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 à titre d’exemple, au lieu de renvoyer le nom et l’adresse électronique, nous pouvons, par exemple, renvoyer uniquement l’adresse électronique:

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

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

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

5. Conclusion

GraphQL est un moyen simple et attrayant de réduire la complexité client/serveur en tant qu’approche alternative aux API REST.

Comme toujours, l’exemple est disponible sur notre référentiel GitHub .