10 minutes
Bonnes pratiques d'interrogation
The Graph offre un moyen décentralisé d’interroger les données des blockchains. Ses données sont exposées par le biais d’une API GraphQL, ce qui facilite l’interrogation avec le langage GraphQL.
Apprenez les règles essentielles du langage GraphQL et les meilleures pratiques pour optimiser votre subgraph.
Interroger une API GraphQL
Anatomie d’une requête GraphQL
Contrairement à l’API REST, une API GraphQL repose sur un schéma qui définit les requêtes qui peuvent être effectuées.
Par exemple, une requête pour obtenir un jeton en utilisant la requête token
ressemblera à ce qui suit :
1query GetToken($id: ID!) {2 token(id: $id) {3 id4 owner5 }6}
qui retournera la réponse JSON prévisible suivante (_en passant la bonne valeur de la variable $id
):
1{2 "token": {3 "id": "...",4 "owner": "..."5 }6}
Les requêtes GraphQL utilisent le langage GraphQL, qui est défini dans une spécification.
La requête GetToken
ci-dessus est composée de plusieurs parties de langage (remplacées ci-dessous par des espaces réservés [...]
) :
1query [operationName]([variableName]: [variableType]) {2 [queryName]([argumentName]: [variableName]) {3 # "{ ... }" express a Selection-Set, we are querying fields from `queryName`.4 [field]5 [field]6 }7}
Règles d’écriture des requêtes GraphQL
- Chaque
queryName
ne doit être utilisé qu’une seule fois par opération. - Chaque
champ
ne doit être utilisé qu’une seule fois dans une sélection (nous ne pouvons pas interrogerid
deux fois soustoken
) - Certains
champs
ou certaines requêtes (commetokens
) renvoient des types complexes qui nécessitent une sélection de sous-champs. Ne pas fournir de sélection quand cela est attendu (ou en fournir une quand cela n’est pas attendu - par exemple, surid
) lèvera une erreur. Pour connaître un type de champ, veuillez vous référer à [Graph Explorer] (/subgraphs/explorer/). - Toute variable affectée à un argument doit correspondre à son type.
- Dans une liste de variables donnée, chacune d’elles doit être unique.
- Toutes les variables définies doivent être utilisées.
Remarque : le non-respect de ces règles entraînera une erreur de la part de The Graph API.
Pour une liste complète des règles avec des exemples de code, consultez le Guide des validations GraphQL.
Envoi d’une requête à une API GraphQL
GraphQL est un langage et un ensemble de conventions qui se transportent sur HTTP.
Cela signifie que vous pouvez interroger une API GraphQL en utilisant le standard fetch
(nativement ou via @whatwg-node/fetch
ou isomorphic-fetch
).
Cependant, comme mentionné dans “Interrogation à partir d’une application”, il est recommandé d’utiliser graph-client
, qui supporte les caractéristiques uniques suivantes :
- Traitement des subgraphs multi-chaînes : Interrogation de plusieurs subgraphs en une seule requête
- Suivi automatique des blocs
- [Pagination automatique] (https://github.com/graphprotocol/graph-client/blob/main/packages/auto-pagination/README.md)
- Résultat entièrement typé
Voici comment interroger The Graph avec graph-client
:
1import { execute } from '../.graphclient'23const query = `4query GetToken($id: ID!) {5 token(id: $id) {6 id7 owner8 }9}10`11const variables = { id: '1' }1213async function main() {14 const result = await execute(query, variables)15 // `result` is fully typed!16 console.log(result)17}1819main()
D’autres alternatives au client GraphQL sont abordées dans “Requête à partir d’une application”.
Les meilleures pratiques
Écrivez toujours des requêtes statiques
Une (mauvaise) pratique courante consiste à construire dynamiquement des chaînes de requête comme suit :
1const id = params.id2const fields = ['id', 'owner']3const query = `4query GetToken {5 token(id: ${id}) {6 ${fields.join('\n')}7 }8}9`1011// Execute query...
Bien que l’extrait ci-dessus produise une requête GraphQL valide, il présente de nombreux inconvénients :
- cela rend plus difficile la compréhension de la requête dans son ensemble
- les développeurs sont responsables de l’assainissement de l’interpolation de la chaîne de caractères
- ne pas envoyer les valeurs des variables dans le cadre des paramètres de la requête empêcher la mise en cache éventuelle côté serveur
- il empêche les outils d’analyser statiquement la requête (ex : Linter, ou les outils de génération de types)
C’est pourquoi il est recommandé de toujours écrire les requêtes sous forme de chaînes de caractères statiques :
1import { execute } from 'your-favorite-graphql-client'23const id = params.id4const query = `5query GetToken($id: ID!) {6 token(id: $id) {7 id8 owner9 }10}11`1213const result = await execute(query, {14 variables: {15 id,16 },17})
Cela présente de nombreux avantages :
- Facile à lire et à entretenir les requêtes
- Le serveur GraphQL s’occupe de la validation des variables
- Les variables peuvent être mises en cache au niveau du serveur
- Les requêtes peuvent être analysées statiquement par des outils (plus d’informations à ce sujet dans les sections suivantes)
Comment inclure des champs de manière conditionnelle dans des requêtes statiques
Il se peut que vous souhaitiez inclure le champ owner
uniquement pour une condition particulière.
Pour cela, vous pouvez utiliser la directive @include(if :...)
comme suit :
1import { execute } from 'your-favorite-graphql-client'23const id = params.id4const query = `5query GetToken($id: ID!, $includeOwner: Boolean) {6 token(id: $id) {7 id8 owner @include(if: $includeOwner)9 }10}11`1213const result = await execute(query, {14 variables: {15 id,16 includeOwner: true,17 },18})
Note : La directive opposée est @skip(if : ...)
.
Demandez ce que vous voulez
GraphQL est devenu célèbre grâce à son slogan “Ask for what you want” (demandez ce que vous voulez).
Pour cette raison, il n’existe aucun moyen, dans GraphQL, d’obtenir tous les champs disponibles sans avoir à les lister individuellement.
- Lorsque vous interrogez les API GraphQL, pensez toujours à interroger uniquement les champs qui seront réellement utilisés.
- Assurez-vous que les requêtes ne récupèrent que le nombre d’entités dont vous avez réellement besoin. Par défaut, les requêtes récupèrent 100 entités dans une collection, ce qui est généralement beaucoup plus que ce qui sera réellement utilisé, par exemple pour l’affichage à l’utilisateur. Cela s’applique non seulement aux collections de premier niveau d’une requête, mais plus encore aux collections imbriquées d’entités.
Par exemple, dans la requête suivante :
1query listTokens {2 tokens {3 # will fetch up to 100 tokens4 id5 transactions {6 # will fetch up to 100 transactions7 id8 }9 }10}
La réponse pourrait contenir 100 transactions pour chacun des 100 jetons.
Si l’application n’a besoin que de 10 transactions, la requête doit explicitement définir first: 10
dans le champ transactions.
Utiliser une seule requête pour demander plusieurs enregistrements
Par défaut, les subgraphs ont une entité singulière pour un enregistrement. Pour plusieurs enregistrements, utilisez les entités plurielles et le filtre : where: {id_in:[X,Y,Z]}
ou where: {volume_gt:100000}
Exemple de requête inefficace :
1query SingleRecord {2 entity(id: X) {3 id4 name5 }6}7query SingleRecord {8 entity(id: Y) {9 id10 name11 }12}
Exemple de requête optimisée :
1query ManyRecords {2 entities(where: { id_in: [X, Y] }) {3 id4 name5 }6}
Combiner plusieurs requêtes en une seule
Votre application peut nécessiter l’interrogation de plusieurs types de données, comme suit :
1import { execute } from "your-favorite-graphql-client"23const tokensQuery = `4query GetTokens {5 tokens(first: 50) {6 id7 owner8 }9}10`11const countersQuery = `12query GetCounters {13 counters {14 id15 value16 }17}18`1920const [tokens, counters] = Promise.all(21 [22 tokensQuery,23 countersQuery,24 ].map(execute)25)
Bien que cette mise en œuvre soit tout à fait valable, elle nécessitera deux allers-retours avec l’API GraphQL.
Heureusement, il est également possible d’envoyer plusieurs requêtes dans la même requête GraphQL, comme suit :
1import { execute } from "your-favorite-graphql-client"23const query = `4query GetTokensandCounters {5 tokens(first: 50) {6 id7 owner8 }9 counters {10 id11 value12 }13}14`1516const { result: { tokens, counters } } = execute(query)
Cette approche améliore les performances globales en réduisant le temps passé sur le réseau (vous évite un aller-retour vers l’API) et fournit une mise en œuvre plus concise.
Tirer parti des fragments GraphQL
Une fonctionnalité utile pour écrire des requêtes GraphQL est GraphQL Fragment.
En regardant la requête suivante, vous remarquerez que certains champs sont répétés dans plusieurs Ensembles de sélection ({ ... }
) :
1query {2 bondEvents {3 id4 newDelegate {5 id6 active7 status8 }9 oldDelegate {10 id11 active12 status13 }14 }15}
Ces champs répétés (id
, active
, status
) posent de nombreux problèmes :
- Les requêtes plus longues deviennent plus difficiles à lire.
- Lorsque l’on utilise des outils qui génèrent des types TypeScript basés sur des requêtes (plus d’informations à ce sujet dans la dernière section),
newDelegate
etoldDelegate
donneront lieu à deux interfaces inline distinctes.
Une version remaniée de la requête serait la suivante :
1query {2 bondEvents {3 id4 newDelegate {5 ...DelegateItem6 }7 oldDelegate {8 ...DelegateItem9 }10 }11}1213# nous définissons un fragment (sous-type) sur Transcoder14# pour factoriser les champs répétés dans la requête15fragment DelegateItem on Transcoder {16 id17 active18 status19}
L’utilisation de GraphQL fragment
améliorera la lisibilité (en particulier à grande échelle) et permettra une meilleure génération de types TypeScript.
Lorsque l’on utilise l’outil de génération de types, la requête ci-dessus génère un type DelegateItemFragment
approprié (_voir la dernière section “Outils”).
Bonnes pratiques et erreurs à éviter avec les fragments GraphQL
La base du fragment doit être un type
Un fragment ne peut pas être basé sur un type non applicable, en bref, sur un type n’ayant pas de champs :
1fragment MyFragment on BigInt {2 # ...3}
BigInt
est un scalaire (type natif “plain” ) qui ne peut pas être utilisé comme base d’un fragment.
Comment diffuser un fragment
Les fragments sont définis pour des types spécifiques et doivent être utilisés en conséquence dans les requêtes.
L’exemple:
1query {2 bondEvents {3 id4 newDelegate {5 ...VoteItem # Erreur ! `VoteItem` ne peut pas être étendu sur le type `Transcoder`6 }7 oldDelegate {8 ...VoteItem9 }10 }11}1213fragment VoteItem on Vote {14 id15 voter16}
newDelegate
et oldDelegate
sont de type Transcoder
.
Il n’est pas possible de diffuser un fragment de type Vote
ici.
Définir Fragment comme une unité commerciale atomique de données
Les Fragment
GraphQL doivent être définis en fonction de leur utilisation.
Pour la plupart des cas d’utilisation, la définition d’un fragment par type (dans le cas de l’utilisation répétée de champs ou de la génération de types) est suffisante.
Voici une règle empirique pour l’utilisation des fragments :
- Lorsque des champs de même type sont répétés dans une requête, ils sont regroupés dans un
Fragment
. - Lorsque des champs similaires mais différents se répètent, créer plusieurs fragments, par exemple :
1# fragment de base (utilisé principalement pour les listes)2fragment Voter on Vote {3 id4 voter5}67# fragment étendu (lors de l'interrogation d'une vue détaillée d'un vote)8fragment VoteWithPoll on Vote {9 id10 voter11 choiceID12 poll {13 id14 proposal15 }16}
Les outils essentiels
Explorateurs Web GraphQL
Itérer sur des requêtes en les exécutant dans votre application peut s’avérer fastidieux. Pour cette raison, n’hésitez pas à utiliser Graph Explorer pour tester vos requêtes avant de les ajouter à votre application. Graph Explorer vous fournira un terrain de jeu GraphQL préconfiguré pour tester vos requêtes.
Si vous recherchez un moyen plus souple de déboguer/tester vos requêtes, d’autres outils web similaires sont disponibles, tels que Altair et GraphiQL.
Linting GraphQL
Afin de respecter les meilleures pratiques et les règles syntaxiques mentionnées ci-dessus, il est fortement recommandé d’utiliser les outils de workflow et d’IDE suivants.
GraphQL ESLint
GraphQL ESLint vous aidera à rester au fait des meilleures pratiques GraphQL sans effort.
La configuration “opérations-recommandées” permet d’appliquer des règles essentielles telles que:
@graphql-eslint/fields-on-correct-type
: un champ est-il utilisé sur un type correct ?@graphql-eslint/no-unused variables
: une variable donnée doit-elle rester inutilisée ?- et plus !
Cela vous permettra de récupérer les erreurs sans même tester les requêtes sur le terrain de jeu ou les exécuter en production !
Plugins IDE
VSCode et GraphQL
L’[extension GraphQL VSCode] (https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) est un excellent complément à votre workflow de développement :
- Mise en évidence des syntaxes
- Suggestions d’auto-complétion
- Validation par rapport au schéma
- Snippets
- Aller à la définition des fragments et des types d’entrée
Si vous utilisez graphql-eslint
, l’extension ESLint VSCode extension est indispensable pour visualiser correctement les erreurs et les avertissements dans votre code.
WebStorm/Intellij et GraphQL
Le [JS GraphQL plugin] (https://plugins.jetbrains.com/plugin/8097-graphql/) améliorera considérablement votre expérience lorsque vous travaillez avec GraphQL en fournissant :
- Mise en évidence des syntaxes
- Suggestions d’auto-complétion
- Validation par rapport au schéma
- Snippets
Pour plus d’informations sur ce sujet, consultez l’article WebStorm qui présente toutes les principales fonctionnalités du plugin.