Qu'est-ce que GraphQL ? Son histoire et exemples C# / Python.

GraphQL est un langage de requête développé par Facebook en 2012 pour résoudre les problèmes d'overfetching et de underfetching liés aux API REST traditionnelles.

Qu'est-ce que GraphQL ? Son histoire et exemples C# / Python.
GraphQL est un langage de requête développé par Facebook en 2012 pour résoudre les problèmes d'overfetching et de underfetching liés aux API REST traditionnelles. Les développeurs pouvaient utiliser GraphQL pour décrire les données dont ils avaient besoin dans une requête unique, plutôt que de devoir faire plusieurs requêtes REST pour obtenir les mêmes informations.

Histoire

GraphQL est un langage de requête développé par Facebook en 2012 pour résoudre les problèmes d'overfetching et de underfetching liés aux API REST traditionnelles. Les développeurs pouvaient utiliser GraphQL pour décrire les données dont ils avaient besoin dans une requête unique, plutôt que de devoir faire plusieurs requêtes REST pour obtenir les mêmes informations.

Les problèmes d'overfetching et de underfetching se produisent lorsque les développeurs utilisent des API REST qui ne répondent pas exactement aux données dont ils ont besoin. L'overfetching se produit lorsque les développeurs reçoivent plus de données qu'ils n'ont réellement besoin, tandis que l'underfetching se produit lorsque les développeurs ne reçoivent pas assez de données pour répondre à leurs besoins. Ces problèmes peuvent entraîner des problèmes de performance et de maintenabilité pour les applications qui utilisent ces API.

GraphQL a été inventé par Lee Byron et Dan Schafer de Facebook. Ils ont commencé à travailler sur GraphQL en 2012 pour résoudre les problèmes liés aux API REST qui étaient utilisées par l'application mobile de Facebook. Ils ont rapidement réalisé que GraphQL pouvait être utilisé pour résoudre les problèmes d'overfetching et d'underfetching dans d'autres applications également.

Les premiers scénarios pour lesquels GraphQL a été utilisé comprenaient la gestion des données pour les applications mobiles de Facebook, comme l'affichage des informations d'un utilisateur ou l'affichage des publications d'un utilisateur. GraphQL a également été utilisé pour la gestion des données pour les applications web de Facebook, comme l'affichage des données d'analyse pour les annonceurs.

GraphQL est devenu open-source en 2015 et a rapidement gagné en popularité en raison de sa flexibilité et de sa capacité à répondre aux besoins des développeurs. Il est utilisé dans de nombreuses applications, notamment celles de Facebook, GitHub et Shopify.

Les requêtes GraphQL

Une requête GraphQL ressemble à une requête HTTP ou à une requête REST, mais elle contient un corps de requête qui décrit les données que vous souhaitez récupérer.

Voici un exemple d'une requête GraphQL qui récupère les informations sur un utilisateur avec un ID spécifique :

query {
  user(id: 1) {
    name
    age
    email
  }
}

Dans cette requête, nous avons défini une opération de type query qui permet de récupérer des données avec cette requête. Ensuite nous avons défini le nom de la requête user avec un argument id qui est un nombre entier, cette requête va retourner les données liées à l'utilisateur avec l'ID 1. Enfin, nous avons défini les champs que nous souhaitons récupérer pour cet utilisateur : name, age et email.

Il est important de noter que la requête GraphQL peut être utilisée pour récupérer des données de plusieurs sources de données en une seule requête, ce qui permet d'éviter les requêtes multiples nécessaires pour récupérer les mêmes informations en utilisant des API REST traditionnelles.

Il est également possible de faire des mutations avec GraphQL qui permette de faire des opérations CRUD (create, read, update, delete) sur les données. exemple :

mutation {
  createUser(input: {name: "John", age: 30, email: "[email protected]"}) {
    name
    age
    email
  }
}

Cette requête de mutation crée un utilisateur avec les informations spécifiées dans l'argument input, et retourne les champs name, age, et email pour l'utilisateur créé.

Enfin il est aussi possible de souscrire à un événement avec GraphQL pour recevoir des données en temps réel exemple :

subscription {
  newUsers {
    name
    age
    email
  }
}

Cette requête de souscription permet de recevoir les nouveaux utilisateurs qui se connectent en temps réel dans notre application.

Il est important de noter que les requêtes GraphQL doivent être structurées de manière à respecter la structure définie dans le schéma GraphQL. Si vous tentez de récupérer des champs qui ne sont pas définis dans le schéma, ou si vous passez des arguments qui ne sont pas valides pour une requête ou une mutation donnée, vous obtiendrez une erreur.

Il est également possible de passer des variables dans une requête GraphQL plutôt que de spécifier des valeurs directement dans la requête. Cela permet de rendre les requêtes plus flexibles et faciles à réutiliser. Voici un exemple de requête GraphQL qui utilise une variable pour spécifier l'ID de l'utilisateur à récupérer :

query($userId: Int!) {
  user(id: $userId) {
    name
    age
    email
  }
}

Les variables sont généralement passées dans le contexte de la requête, par exemple, dans un client GraphQL, les variables peuvent être spécifiées dans l'interface utilisateur avant d'envoyer la requête.

Il est important de noter que GraphQL est un langage de requête, il ne gère pas les données et le stockage en lui-même. Il est utilisé pour interroger les données d'une source de données, il est souvent utilisé en conjonction avec un serveur GraphQL qui s'occupe de la logique de base de données et de la sécurité pour gérer les requêtes et les mutations.

Exemple C# et ReactJS

Imaginons le cas suivant :
Nous avons un site e-commerce en ReactJS et nous souhaitons afficher dans son compte client son nom et prénom et afficher en ligne la liste des commandes qu'il a passé en affichant uniquement la date, la référence et le montant payé.

(Attention le code présenté n'est pas forcément optimal. Il n'est ici qu'à titre indicatif).

Dans notre modèle de donnée C# nous avons par exemple ces 2 classes :

public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Username { get; set; }
    public bool active { get; set; }
    public DateTime BirthDate { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public string Reference { get; set; }
    public DateTime Date { get; set; }
    public decimal AmountPaid { get; set; }
    public Status status { get; set; }
    public List<Product> Products { get; set; }
}
//...

Pour récupérer uniquement le nom, prénom du client et les informations de commandes (date, références et montant payé) pour un site web e-commerce utilisant ReactJS et un serveur GraphQL écrit en .NET Core, vous devriez commencer par définir les types GraphQL correspondants pour les données de client et les informations de commande dans votre schéma GraphQL.

Voici un exemple de ce à quoi pourrait ressembler les types de données GraphQL pour les clients et les commandes :

using HotChocolate;
using HotChocolate.Types;
using HotChocolate.Resolvers;

class CustomerType : ObjectType<Customer>
{
    protected override void Configure(IObjectTypeDescriptor<Customer> descriptor)
    {
        descriptor.Field(t => t.FirstName).Type<NonNullType<StringType>>();
        descriptor.Field(t => t.LastName).Type<NonNullType<StringType>>();
        descriptor.Field(t => t.Orders).Type<ListType<NonNullType<OrderType>>>();
    }
}

class OrderType : ObjectType<Order>
{
    protected override void Configure(IObjectTypeDescriptor<Order> descriptor)
    {
    descriptor.Field(t => t.Reference).Type<StringType>();
    descriptor.Field(t => t.Date).Type<DateTimeType>();
    descriptor.Field(t => t.AmountPaid).Type<DecimalType>();
    }
}

public class MySchema : Schema
{
    protected override void Configure(ISchemaBuilder builder)
    {
        builder.QueryType<QueryType>();
    }
}

Ensuite, vous devrez définir un resolver pour la requête qui récupère les données de client et les informations de commande. Cela pourrait ressembler à ceci :

class QueryType : ObjectType
{
    protected override void Configure(IObjectTypeDescriptor descriptor)
    {
        descriptor.Field("customer")
            .Type<CustomerType>()
            .Argument("id", a => a.Type<IntType>())
            .Resolver(context => GetCustomerById(context.Argument<int>("id")));
    }
}

Dans cet exemple, nous avons défini une requête "customer" qui prend un argument "id" de type entier, et qui appelle la fonction "GetCustomerById" pour récupérer les données de client correspondant à l'ID fourni en argument.

En utilisant ce schéma GraphQL, un développeur pourra alors effectuer une requête pour récupérer les informations sur un client spécifique ainsi que les informations de facturation associées en utilisant une seule requête au lieu d'avoir à effectuer une requête séparée pour chaque ensemble de données.

Il est important de noter que pour pouvoir utiliser GraphQL avec .NET Core il faut utiliser un framework GraphQL pour .NET Core tel que Hot Chocolate ou graphql-dotnet. Il est également important de définir le schéma GraphQL correctement ainsi que les resolvers pour chaque champ pour que les données soient correctement récupérées à partir de la source de données.

Ensuite, dans votre application ReactJS, vous pourriez utiliser un composant pour afficher les informations de client et de commande, en utilisant les données récupérées via GraphQL. Vous pourriez utiliser la bibliothèque react-apollo  ou apollo-client pour effectuer la requête GraphQL et passer les données à un composant pour l'afficher.

import React from "react";
import { ApolloProvider, useQuery } from "react-apollo";
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import gql from "graphql-tag";

const client = new ApolloClient({
  link: createHttpLink({ uri: 'http://localhost:5000/graphql' }),
  cache: new InMemoryCache()
});

const GET_CUSTOMER = gql`
  query customer($id: Int!) {
    customer(id: $id) {
      firstName
      lastName
      orders {
        date
        reference
        amountPaid
      }
    }
  }
`;

function CustomerInfo({ id }) {
  const { loading, error, data } = useQuery(GET_CUSTOMER, {
    variables: { id },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;

  const { firstName, lastName, orders } = data.customer;

  return (
    <div>
      <h1>{firstName} {lastName}</h1>
      <h2>Orders</h2>
      {orders.map((order) => (
        <div key={order.reference}>
          <p>Date: {order.date}</p>
          <p>Reference: {order.reference}</p>
          <p>Amount Paid: {order.amountPaid}</p>
        </div>
      ))}
    </div>
  );
}
 
function App() {
    return (
    <ApolloProvider client={client}>
    <CustomerInfo id={1} />
    </ApolloProvider>
    );
}

export default App;

Il est important de noter que cet exemple de code est simplifié pour montrer comment utiliser GraphQL pour récupérer des données spécifiques dans une application ReactJS. Il y a d'autres éléments à prendre en compte pour une mise en place complète d'une application GraphQL. Il est également important de bien comprendre les concepts fondamentaux de GraphQL et comment ils s'appliquent aux différentes parties de votre application.

Il est également possible de rajouter des validation ou des autorisations pour les requêtes, les mutations ou les souscriptions pour s'assurer que seul les utilisateurs autorisés peuvent accéder aux données. Il est aussi important de gérer les erreurs pour que les utilisateurs puissent comprendre les erreurs qui se sont produites.

Il est également important de noter que cet exemple utilise un schéma GraphQL statique, mais il est également possible de générer dynamiquement un schéma GraphQL en fonction des données disponibles. Il existe des outils comme GraphQL Codegen qui peuvent aider à générer automatiquement des types et des resolvers GraphQL à partir de données existantes.

En résumé, pour récupérer uniquement le nom, prénom du client et les informations de commandes (date, références et montant payé) pour un site web e-commerce utilisant ReactJS et un serveur GraphQL écrit en .NET Core, il est nécessaire de définir les types GraphQL correspondants pour les données de client et les informations de commande dans le schéma GraphQL, de définir les resolvers pour les requêtes et ensuite utiliser les données récupérées via GraphQL pour afficher les informations dans les composants ReactJS.

Le même mais pour python ?

Comme promis, voici le même code coté serveur mais avec python, la bibliothèque graphene et Flask.

import graphene
from flask import Flask
from flask_graphql import GraphQLView
from models import Customer, Order

class OrderType(graphene.ObjectType):
    reference = graphene.String()
    date = graphene.DateTime()
    amount_paid = graphene.Decimal()

class CustomerType(graphene.ObjectType):
    first_name = graphene.String(required=True)
    last_name = graphene.String(required=True)
    orders = graphene.List(lambda: OrderType, required=True)

class Query(graphene.ObjectType):
    customer = graphene.Field(lambda: CustomerType, id=graphene.Int())

    def resolve_customer(self, info, id):
        return get_customer_by_id(id)

def get_customer_by_id(id):
    return Customer.query.filter(Customer.id == id).first()

app = Flask(__name__)
app.add_url_rule(
    '/graphql',
    view_func=GraphQLView.as_view(
        'graphql',
        schema=graphene.Schema(query=Query),
        graphiql=True 
        )
)

if name == 'main':
	app.run()

Ici nous utilisons Flask pour créer une application web et GraphQLView pour ajouter une vue GraphQL à notre application. Nous définissons ensuite les types GraphQL CustomerType et OrderType, ainsi que la Query pour récupérer les données de client. Nous avons ajouté une méthode get_customer_by_id(id) qui va récupérer les données dans une base de données. Enfin, nous définissons une route pour notre vue GraphQL en utilisant app.add_url_rule et démarrons l'application en utilisant app.run().