Aller au contenu
PROXIAD - LOGO DEF VALIDÉ - 201123 blanc-01 (1)
BLOG TECH
PRÊT À DÉCOLLER ?
Search
Close

FR

|

EN

ARTICLE

GraphQL pour des applications Offline et réactives

Caching is hard!


Si les bonnes pratiques du cache HTTP sont maintenant bien établies, GraphQL, en permettant à chaque client d’éxecuter des requêtes très dynamiques, présente plusieurs challenges.
Dans cette présentation, nous ferons une rapide présentation de GraphQL et des principales différences avec REST. Nous verrons comment le système de type permet de construire un cache normalisé, type safe et persistant.
Enfin, nous montrerons comment l’utiliser en pratique dans une App d’exemple Android.

Cet article est un résumé de la conférence donnée par Benoit Lubek et Martin Bonnin lors du devfest 2022

GraphQL, qu’est-ce que c’est ?

GraphQL, pour Graph Query Language, est un langage qui permet de décrire et d’exécuter des API grâce à :

  • un schéma, qui gère aussi la nullabilité ainsi que la déprécation des champs
    • des types (Int, Float, String, Boolean)
    • des objets
    • des interfaces
    • des listes.

GraphQL met en place une introspection des API, ce qui signifie que l’API est auto-documentée. On peut alors interroger un endpoint directement pour récupérer son schéma (champs possibles, types, etc …). Ainsi, l’avantage de GraphQL réside dans le fait qu’il n’y a besoin plus que d’un seul endpoint pour adresser tous les cas d’utilisation d’une API.


Prenons l’exemple d’une API REST à laquelle plusieurs types de support (ordinateur, smartphone, etc.) vont faire appel, avec des besoins de champs différents.


Une telle API devra alors soit exposer un endpoint unique avec tous les champs utilisables, soit exposer un endpoint pour chaque support.
Grâce à GraphQL, un seul endpoint a besoin d’être exposé, et chaque support pourra requêter uniquement les champs dont il a besoin.

GraphQL, comment ça marche ?

Une requête GraphQL s’exprime dans une syntaxe proche du JSON. Il suffit de lister les champs que l’on veut récupérer ainsi que les champs des sous-objets pour les types composites.

Requête

query UserQuery {
    user {
        id
        login
        avatar {
            small
            medium
        }
    }
}

Réponse

{
    "data": {
        "user" {
            "id": 42,
            "login": "demo",
            "avatar" {
                "small": "...",
                "medium": "..."
            }
        }
    }
}

Si l’on enlève un champ de la requête, le champ disparaîtra également de la réponse, ce qui a un impact sur le caching.
Traditionnellement, pour mettre en cache des informations sur mobile, une base de données relationnelle (SQLite) est utilisée et chaque champ des API REST est mappé à une colonne d’une table.

Cependant, ce procédé ne fonctionne pas avec GraphQL car on ne récupère pas d’objet entier mais uniquement les champs demandés. On ne peut pas simplement créer des colonnes avec des valeurs à null pour les données non demandées car GraphQL peut retourner la valeur null ayant possiblement une signification métier. On ne peut pas non plus créer une table par requête ou stocker chaque payload JSON, comme cela entrainerait une redondance des données qui ferait augmenter inutilement la taille du cache.

Caching de GraphQL, comment faire ?

Une solution trouvée chez Apollo est de ne pas stocker les réponses dans des table structurées, mais de stocker les retours dans des listes de Record. Plus simplement, un Record peut être représenté par une Map.

Afin d’enregistrer les données en cache, les réponses GraphQL sont mises à plat.

Origine

{
    "data": {
        "user": {
            "id": 42,
            "email": "test@proxiad.com",
            "login": "test",
            "name": "Test Heure"
        }
    }
}

Aplati

{
    "data": {
        "user": CacheReference("42")
    },
    "42": {
        "id": 42,
        "email": "test@proxiad.com",
        "login": "test",
        "name": "Test Heure"
    }
}

data devient un premier Record qui contient un objet user, une CacheReference pointant vers le Record du user ayant l’id 42.
Si l’on reçoit une nouvelle réponse avec un champ supplémentaire pour le user 42, il suffira alors de merger les Record pour ajouter le nouveau champ au cache.

{
    "42": {
        "id": 42,
        "email": "test@proxiad.com",
        "login": "test",
        "name": "Test Heure",
        // new Record field 
        "avatarUrl": "http://..."
    }
}

Cependant, ici on s’est basé sur l’id de l’utilisateur. Hors, ce champ peut ne pas être présent dans les schémas. Dans ce cas, une solution peut être d’utiliser le chemin vers la data comment identifiant en cache :

{
    "data": {
        "user": CacheReference("data.user")
    },
    "data.user": {
        "email": "test@proxiad.com",
        "login": "test",
        "name": "Test Heure"
    }
}

Une nouvelle fois, cette solution n’est pas optimale, car un même user peut être représenté avec un chemin différent dans le graphe, entraînant une duplication de données.
Il est donc très important de bien définir ses identifiants lorsque l’on veut faire du caching avec GraphQL.

Comment lire les données dans le cache

Le stockage des données sous forme de Record dans le cache entraîne la perte de leur typage. Cependant grâce au schéma de GraphQL, il est possible de le retrouver. Pour, par la suite, relier les types aux données venant du cache, plusieurs outils existent comme par exemple la librairie open source Apollo Kotlin.

Graphql et Appolo Kotlin

Apollo est un client GraphQL qui génère du code à partir des requêtes et du schéma de l’API, afin de créer des Models et des Parsers. Ces Parsers vont populer les Models grâce aux informations venant en réponse de la requête.

Apollo Kotlin embarque également dans sa libraire le système de cache normalisé que nous avons vu précédemment.

Exemple

Nous allons maintenant voir comment utiliser cette librairie avec un exemple en Kotlin adapté pour des applications Android.

Initilisation du cache et du client

val memoryCache = MemoryCacheFactory(maxSizeBytes = 5_000_000) // cache en mémoire

val apolloClient: ApolloClient = ApolloClient.builder()
    .serverUrl(SERVER_URL)
    .normalizedCache(memoryCache)
    .build()

On définit simplement un cache (ici en mémoire). Ce cache est donné au client lors de son initialisation. À partir de ce moment, toutes les requêtes passeront par le cache.

Il est possible de créer un cache persistant en base SQLite :

val sqlCache = SqlNormalizedCacheFactory(context, "app.db")

Il est également possible de chaîner les caches et donc de stocker les données en mémoire et en base. De cette manière, nous avons les avantages de la rapidité du cache mémoire et d’un fallback vers la base SQLite.

val memoryThenSqlCache = memoryCache.chain(sqlCache)

Watchers

Apollo permet également la mise en place de watchers, qui vont écouter les mises à jour du cache pour lancer des actions (mettre à jour une UI par exemple) :

mutation {
    updateUser({id: "42", status: "Entrain de lire un article"}) {
        id
        status
    }
}

Conclusion

Dans cet article, nous avons vu comment Apollo permet de gérer facilement un cache normalisé et typé pour une application utilisant des API GraphQL. Le cache permet de maintenir une certaine réactivité même en cas d’absence de réseau ou limite la consommation de bande passante dans le cas contraire.

Sources

  • Rediffusion de la conférence donnée lors du devfest 2022 : https://www.youtube.com/watch?v=5CgNlSuqPOU
  • Github du projet Apollo Kotlin : https://github.com/apollographql/apollo-kotlin

Direction Technique
Stéphane YVON
Proxiad NORD

PROXIAD - LOGO DEF VALIDÉ - 201123 blanc-01 (1)
  • Applicatif
  • Infrastructures
  • Cybersécurité
  • DevOps
  • Contact

PARIS

47 Rue de Ponthieu
75008 Paris
France
contact.idf@proxiad.com

LILLE

19 Avenue Le Corbusier
59800 Lille
France
contact.nord@proxiad.com

ROUEN

4 Passage de la Luciline,
76000 Rouen
France
contact.normandie@proxiad.com

NANTES

275 Boulevard Marcel
Paul, 44800 Saint-
Herblain, France
contact.ouest@proxiad.com

STRASBOURG

3 Avenue de l'Europe,
67300 Schiltigheim
France
contact.est@proxiad.com

AIX-MARSEILLE

Europarc de Pichaury - 1330
av Guillibert de la Lauziére,
13290 Aix-en-Provence
contact.aixmarseille@proxiad.com

SOPHIA ANTIPOLIS

930 route des Dolines
06560 Valbonne
France
sophiantipolis@proxiad.com

LYON

Regus - Lyon Brotteaux,
132 rue Bossuet
69006 Lyon
contact.lyon@proxiad.com

BORDEAUX

Mama Works,
51 Quai Lawton, Bâtiment G4
33300 Bordeaux
contact.bordeaux@proxiad.com

SOFIA

59 Boulevard G.M. Dimitrov,
1700 Sofia,
Bulgaria
contact.bulgaria@proxiad.com

PLOVDIV

6 Belgrad St,
4000 Plovdiv
Bulgaria
contact.bulgaria@proxiad.com

GREEN SI &
GREEN SI CONSULTING

47 rue de Ponthieu,
75008 Paris, France
contact.greensi@proxiad.com

SKOPJE

4/1-11 rue Dimitrie Chupovski,
100 Skopje
Macédoine du Nord
contact.macedonia@proxiad.com

© 2021 PROXIAD

  • MENTIONS LéGALES
  • POLITIQUE DE CONFIDENTIALITé
  • siège social, 47 rue de ponthieu, 75008 paris, france
  • +33 1 44 83 83 70
Facebook Linkedin Twitter Instagram Youtube
En poursuivant votre navigation sur ce site, vous acceptez l’utilisation de cookies à des fins de statistiques de visites. En savoir plusRéglages cookiesREJETERACCEPTER
Manage consent

Présentation de la confidentialité

Ce site Web utilise des cookies pour améliorer votre expérience lorsque vous naviguez sur le site Web. Parmi ces cookies, les cookies classés comme nécessaires sont stockés sur votre navigateur car ils sont essentiels pour le fonctionnement des fonctionnalités de base du site Web.
Nous utilisons également des cookies tiers qui nous aident à analyser et à comprendre comment vous utilisez ce site Web. Ces cookies ne seront stockés dans votre navigateur qu'avec votre consentement.
Vous avez également la possibilité de désactiver ces cookies. Mais la désactivation de certains de ces cookies peut avoir un effet sur votre expérience de navigation.
Necessary
Toujours activé
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Non-necessary
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.
Enregistrer & appliquer
Gérer le consentement aux cookies
Pour offrir les meilleures expériences, nous utilisons des technologies telles que les cookies pour stocker et/ou accéder aux informations des appareils. Le fait de consentir à ces technologies nous permettra de traiter des données telles que le comportement de navigation ou les ID uniques sur ce site. Le fait de ne pas consentir ou de retirer son consentement peut avoir un effet négatif sur certaines caractéristiques et fonctions.
Fonctionnel Toujours activé
Le stockage ou l’accès technique est strictement nécessaire dans la finalité d’intérêt légitime de permettre l’utilisation d’un service spécifique explicitement demandé par l’abonné ou l’utilisateur, ou dans le seul but d’effectuer la transmission d’une communication sur un réseau de communications électroniques.
Préférences
Le stockage ou l’accès technique est nécessaire dans la finalité d’intérêt légitime de stocker des préférences qui ne sont pas demandées par l’abonné ou l’utilisateur.
Statistiques
Le stockage ou l’accès technique qui est utilisé exclusivement à des fins statistiques. Le stockage ou l’accès technique qui est utilisé exclusivement dans des finalités statistiques anonymes. En l’absence d’une assignation à comparaître, d’une conformité volontaire de la part de votre fournisseur d’accès à internet ou d’enregistrements supplémentaires provenant d’une tierce partie, les informations stockées ou extraites à cette seule fin ne peuvent généralement pas être utilisées pour vous identifier.
Marketing
Le stockage ou l’accès technique est nécessaire pour créer des profils d’utilisateurs afin d’envoyer des publicités, ou pour suivre l’utilisateur sur un site web ou sur plusieurs sites web ayant des finalités marketing similaires.
Gérer les options Gérer les services Gérer les fournisseurs En savoir plus sur ces finalités
Voir les préférences
{title} {title} {title}
Logo Proxiad
  • Accueil
  • Expertises
  • Prêt à décoller
  • JOURNAL DE BORD
  • BLOG TECH
Menu
  • Accueil
  • Expertises
  • Prêt à décoller
  • JOURNAL DE BORD
  • BLOG TECH

NOUS REJOINDRE

Voir nos offres d'emploi   →

CONTACTEZ-NOUS

Envoyez-nous un message   →

SUIVEZ-NOUS

Facebook Linkedin Twitter Instagram Youtube