Bonnes pratiques d'interrogation
Reading time: 10 min
Le Graph fournit un moyen décentralisé d’interroger les données des blockchains.
Les données du réseau Graph sont exposées via une API GraphQL, ce qui facilite l'interrogation des données avec le langage GraphQL.
Cette page vous guidera à travers les règles essentielles du langage GraphQL et les meilleures pratiques en matière de requêtes 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 à l'aide de la requête token
ressemblera à ceci :
query GetToken($id: ID!) {token(id: $id) {idowner}}
qui renverra la réponse JSON prévisible suivante (lors du passage de la valeur de variable $id
appropriée) :
{"token": {"id": "...","owner": "..."}}
Les requêtes GraphQL utilisent le langage GraphQL, qui est défini sur .
La requête GetToken
ci-dessus est composée de plusieurs parties de langage (remplacées ci-dessous par des espaces réservés [...]
) :
query [operationName]([variableName]: [variableType]) {[queryName]([argumentName]: [variableName]) {# "{ ... }" express a Selection-Set, we are querying fields from `queryName`.[field][field]}}
Bien que la liste des choses à faire et à ne pas faire syntaxiquement soit longue, voici les règles essentielles à garder à l'esprit lorsqu'il s'agit d'écrire 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 (on ne peut pas interrogerid
deux fois soustoken
) - Some
field
s or queries (liketokens
) return complex types that require a selection of sub-field. Not providing a selection when expected (or providing one when not expected - for example, onid
) will raise an error. To know a field type, please refer to . - 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.
Toutes les variables définies doivent être utilisées.
For a complete list of rules with code examples, please look at our .
GraphQL est un langage et un ensemble de conventions qui transportent via 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 indiqué dans , nous vous recommandons d'utiliser notre graph-client
qui prend en charge des fonctionnalités uniques telles que :
- Gestion des subgraphs inter-chaînes : interrogation à partir de plusieurs subgraphs en une seule requête
- Résultat entièrement typé
Voici comment interroger The Graph avec graph-client
:
import { execute } from '../.graphclient'const query = `query GetToken($id: ID!) {token(id: $id) {idowner}}`const variables = { id: '1' }async function main() {const result = await execute(query, variables)// `result` is fully typed!console.log(result)}main()
D'autres alternatives client GraphQL sont couvertes dans .
Maintenant que nous avons couvert les règles de base de la syntaxe des requêtes GraphQL, examinons maintenant les meilleures pratiques d'écriture de requêtes GraphQL.
Une (mauvaise) pratique courante consiste à créer dynamiquement des chaînes de requête comme suit :
const id = params.idconst fields = ['id', 'owner']const query = `query GetToken {token(id: ${id}) {${fields.join('\n')}}}`// 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 de comprendre la requête dans son ensemble
- les développeurs sont responsables de nettoyer en toute sécurité l'interpolation de chaîne
- ne pas envoyer les valeurs des variables dans le cadre des paramètres de la requête empêcher une éventuelle mise en cache côté serveur
- cela empêche les outils d'analyser statiquement la requête (ex : Linter, ou outils de génération de types)
Pour cette raison, il est recommandé de toujours écrire les requêtes sous forme de chaînes statiques :
import { execute } from 'your-favorite-graphql-client'const id = params.idconst query = `query GetToken($id: ID!) {token(id: $id) {idowner}}`const result = await execute(query, {variables: {id,},})
Cela apporte de de nombreux avantages :
- Requêtes faciles à lire et à maintenir
- Le serveur GraphQL gère la désinfection 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)
Remarque : Comment inclure des champs de manière conditionnelle dans les requêtes statiques
Nous pourrions vouloir inclure le champ owner
uniquement dans une condition particulière.
Pour cela, nous pouvons exploiter la directive @include(if:...)
comme suit :
import { execute } from 'your-favorite-graphql-client'const id = params.idconst query = `query GetToken($id: ID!, $includeOwner: Boolean) {token(id: $id) {idowner @include(if: $includeOwner)}}`const result = await execute(query, {variables: {id,includeOwner: true,},})
Remarque : La directive opposée est @skip(if: ...)
.
GraphQL est devenu célèbre pour son slogan « 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.
Les collections d’entités sont une cause fréquente de surextraction. Par défaut, les requêtes récupèrent 100 entités dans une collection, ce qui est généralement bien plus que ce qui sera réellement utilisé, par exemple pour l'affichage à l'utilisateur. Les requêtes doivent donc presque toujours être définies explicitement en premier et s'assurer qu'elles ne récupèrent que le nombre d'entités dont elles ont réellement besoin. Cela s'applique non seulement aux collections de niveau supérieur dans une requête, mais encore plus aux collections d'entités imbriquées.
Par exemple, dans la requête suivante :
query listTokens {tokens {# will fetch up to 100 tokensidtransactions {# will fetch up to 100 transactionsid}}}
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.
By default, subgraphs have a singular entity for one record. For multiple records, use the plural entities and filter: where: {id_in:[X,Y,Z]}
or where: {volume_gt:100000}
Example of inefficient querying:
query SingleRecord {entity(id: X) {idname}}query SingleRecord {entity(id: Y) {idname}}
Example of optimized querying:
query ManyRecords {entities(where: { id_in: [X, Y] }) {idname}}
Votre application peut nécessiter l'interrogation de plusieurs types de données comme suit :
import { execute } from "your-favorite-graphql-client"const tokensQuery = `query GetTokens {tokens(first: 50) {idowner}}`const countersQuery = `query GetCounters {counters {idvalue}}`const [tokens, counters] = Promise.all([tokensQuery,countersQuery,].map(execute))
Bien que cette implémentation soit totalement valide, 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 :
import { execute } from "your-favorite-graphql-client"const query = `query GetTokensandCounters {tokens(first: 50) {idowner}counters {idvalue}}`const { result: { tokens, counters } } = execute(query)
Cette approche améliorera les performances globales en réduisant le temps passé sur le réseau (vous évite un aller-retour vers l'API) et fournira un implémentation plus concise.
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 sur plusieurs ensembles de sélection ({ ... }
) :
query {bondEvents {idnewDelegate {idactivestatus}oldDelegate {idactivestatus}}}
De tels champs répétés (id
, active
, status
) posent de nombreux problèmes :
- plus difficile à lire pour des requêtes plus approfondies
- lors de l'utilisation d'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
entraînera deux interfaces en ligne distinctes.
Une version refactorisée de la requête serait la suivante :
query {bondEvents {idnewDelegate {...DelegateItem}oldDelegate {...DelegateItem}}}# nous définissons un fragment (sous-type) sur Transcoder# pour factoriser les champs répétés dans la requêtefragment DelegateItem on Transcoder {idactivestatus}
L'utilisation de GraphQL fragment
améliorera la lisibilité (en particulier à grande échelle) mais entraînera également une meilleure génération de types TypeScript.
Lors de l'utilisation de l'outil de génération de types, la requête ci-dessus générera un type DelegateItemFragment
approprié (voir la dernière section "Outils").
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 :
fragment MyFragment on BigInt {# ...}
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 sur des types spécifiques et doivent être utilisés en conséquence dans les requêtes.
L'exemple:
query {bondEvents {idnewDelegate {...VoteItem # Erreur ! `VoteItem` ne peut pas être étendu sur le type `Transcoder`}oldDelegate {...VoteItem}}}fragment VoteItem on Vote {idvoter}
newDelegate
et oldDelegate
sont de type Transcoder
.
Il n'est pas possible de diffuser ici un fragment de type Vote
.
Définir Fragment comme une unité commerciale atomique de données
Le fragment GraphQL doit être défini en fonction de son utilisation.
Pour la plupart des cas d'utilisation, définir un fragment par type (en cas d'utilisation répétée de champs ou de génération de type) est suffisant.
Voici une règle générale pour utiliser Fragment :
- lorsque des champs du même type sont répétés dans une requête, regroupez-les dans un Fragment
- lorsque des champs similaires mais différents sont répétés, créez plusieurs fragments, ex :
# fragment de base (utilisé principalement pour les listes)fragment Voter on Vote {idvoter}# fragment étendu (lors de l'interrogation d'une vue détaillée d'un vote)fragment VoteWithPoll on Vote {idvoterchoiceIDpoll {idproposal}}
Iterating over queries by running them in your application can be cumbersome. For this reason, don't hesitate to use to test your queries before adding them to your application. Graph Explorer will provide you a preconfigured GraphQL playground to test your queries.
If you are looking for a more flexible way to debug/test your queries, other similar web-based tools are available such as and .
Afin de suivre les meilleures pratiques et les règles syntaxiques mentionnées ci-dessus, il est fortement recommandé d'utiliser le flux de travail et les outils IDE suivants.
GraphQL ESLint
will help you stay on top of GraphQL best practices with zero effort.
config will enforce essential rules such as:
@graphql-eslint/fields-on-correct-type
: un champ est-il utilisé sur un type approprié ?@graphql-eslint/no-unused variables
: une variable donnée doit-elle rester inutilisée ?- et plus !
Cela vous permettra de détecter les erreurs sans même tester les requêtes sur le playground ni les exécuter en production!
VSCode et GraphQL
L' est un excellent ajout à votre flux de travail de développement pour obtenir :
- coloration syntaxique
- suggestions d'autocomplé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' est un incontournable pour visualiser correctement les erreurs et les avertissements intégrés dans votre code.
WebStorm/Intellij et GraphQL
Le améliorera considérablement votre expérience lorsque vous travaillez avec GraphQL en fournissant :
- coloration syntaxique
- suggestions d'autocomplétion
- validation par rapport au schéma
- snippets
Plus d'informations sur cet qui présente toutes les principales fonctionnalités du plugin.