Querying > Querying Best Practices

Querying Best Practices

Reading time: 10 min

The Graph fornisce un modo decentralizzato per effettuare query dei dati delle blockchain.

I dati di The Graph network sono esposti attraverso un API GraphQL, che facilita query dei dati con il linguaggio GraphQL.

Questa pagina vi guiderà attraverso le regole essenziali del linguaggio GraphQL e le best practice delle query in GraphQL.


Effettuare query con API GraphQL

Collegamento a questa sezione

L'anatomia di una query GraphQL

Collegamento a questa sezione

A differenza del REST API, un GraphQL API si basa su uno schema che definisce le query che possono essere eseguite.

Ad esempio, una query per ottenere un token utilizzando la query token avrà il seguente aspetto:

query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}

che restituirà la seguente risposta JSON prevedibile (quando si passa il valore corretto della variabile $id):

{
"token": {
"id": "...",
"owner": "..."
}
}

Le query GraphQL utilizzano il linguaggio GraphQL, definito da una specificazione.

La query GetToken di cui sopra è composta da più parti linguistiche (sostituite di seguito con i segnaposto [...]):

query [operationName]([variableName]: [variableType]) {
[queryName]([argumentName]: [variableName]) {
# "{ ... }" express a Selection-Set, we are querying fields from `queryName`.
[field]
[field]
}
}

Sebbene l'elenco delle cose da fare e da non fare a livello sintattico sia lungo, ecco le regole essenziali da tenere a mente quando si tratta di scrivere query GraphQL:

  • Ogni queryName deve essere usato una sola volta per ogni operazione.
  • Ogni field deve essere utilizzato una sola volta in una selezione (non si può effettuare query di id due volte sotto token)
  • Some fields or queries (like tokens) return complex types that require a selection of sub-field. Not providing a selection when expected (or providing one when not expected - for example, on id) will raise an error. To know a field type, please refer to Graph Explorer.
  • Qualsiasi variabile assegnata a un argomento deve corrispondere al suo tipo.
  • In un determinato elenco di variabili, ciascuna di esse deve essere unica.
  • Tutte le variabili definite devono essere utilizzate.

Se non si rispettano le regole di cui sopra, si ottiene un errore dall'API Graph.

For a complete list of rules with code examples, please look at our GraphQL Validations guide.

Invio di una query a un API GraphQL

Collegamento a questa sezione

GraphQL è un linguaggio e una serie di convenzioni che si trasportano su HTTP.

Significa che è possibile effettuare query di un API GraphQL utilizzando lo standard fetch (in modo nativo o tramite @whatwg-node/fetch or isomorphic-fetch).

Tuttavia, come indicato in "Eseguire una query da un'applicazione", si consiglia di utilizzare il nostro graph-client che supporta caratteristiche uniche come:

Ecco come effettuare query di The Graph con graph-client:

import { execute } from '../.graphclient'
const query = `
query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}
`
const variables = { id: '1' }
async function main() {
const result = await execute(query, variables)
// `result` is fully typed!
console.log(result)
}
main()

Altre alternative di client GraphQL sono trattate in "Eseguire una query da un'applicazione".

Ora che abbiamo trattato le regole di base della sintassi delle query GraphQL, esaminiamo le best practices di scrittura delle query GraphQL.


Scrivere sempre query statiche

Collegamento a questa sezione

Una pratica comune (sbagliata) è quella di costruire dinamicamente le stringhe di query come segue:

const id = params.id
const fields = ['id', 'owner']
const query = `
query GetToken {
token(id: ${id}) {
${fields.join('\n')}
}
}
`
// Execute query...

Sebbene lo snippet di cui sopra produca una GraphQL query valida, ha molti svantaggi:

  • rende più difficile la comprensione della query nel suo insieme
  • gli sviluppatori sono responsabili della sanificazione sicura dell'interpolazione delle stringhe
  • non inviare i valori delle variabili come parte dei parametri della richiesta previene eventuali cache sul lato server
  • impedisce agli strumenti di analizzare staticamente la query (ad esempio, Linter o strumenti di generazione dei tipi)

Per questo motivo, si consiglia di scrivere sempre le query come stringhe statiche:

import { execute } from 'your-favorite-graphql-client'
const id = params.id
const query = `
query GetToken($id: ID!) {
token(id: $id) {
id
owner
}
}
`
const result = await execute(query, {
variables: {
id,
},
})

Questo comporta molti vantaggi:

  • Query facili da leggere e da mantenere
  • Il server GraphQL gestisce la sanificazione delle variabili
  • Le variabili possono essere messe in cache a livello di server
  • Le query possono essere analizzate staticamente dagli strumenti (maggiori informazioni nelle sezioni successive)

Nota: come includere i campi in modo condizionato nelle query statiche

Si potrebbe voler includere il campo owner solo in una condizione particolare.

Per questo possiamo sfruttare la direttiva @include(if:...) come segue:

import { execute } from 'your-favorite-graphql-client'
const id = params.id
const query = `
query GetToken($id: ID!, $includeOwner: Boolean) {
token(id: $id) {
id
owner @include(if: $includeOwner)
}
}
`
const result = await execute(query, {
variables: {
id,
includeOwner: true,
},
})

Nota: La direttiva opposta è @skip(if: ...).

Ask for what you want

Collegamento a questa sezione

GraphQL è diventato famoso per il suo slogan "Chiedi quello che vuoi".

Per questo motivo, non c'è modo, in GraphQL, di ottenere tutti i campi disponibili senza doverli elencare singolarmente.

Quando si interrogano le GraphQL API, si deve sempre pensare di effettuare query di solo i campi che verranno effettivamente utilizzati.

Una causa comune di over-fetching sono le collezioni di entità. Per impostazione predefinita, le query recuperano 100 entità in un collezione, che di solito sono molte di più di quelle effettivamente utilizzate, ad esempio per la visualizzazione all'utente. Le query dovrebbero quindi essere impostate quasi sempre in modo esplicito e assicurarsi di recuperare solo il numero di entità di cui hanno effettivamente bisogno. Questo vale non solo per le collezioni di primo livello in una query, ma ancora di più per le collezioni di entità annidate.

Ad esempio, nella seguente query:

query listTokens {
tokens {
# will fetch up to 100 tokens
id
transactions {
# will fetch up to 100 transactions
id
}
}
}

La risposta potrebbe contenere 100 transazioni per ciascuno dei 100 token.

Se l'applicazione ha bisogno solo di 10 transazioni, la query deve impostare esplicitamente first: 10 sul campo transazioni.

Use a single query to request multiple records

Collegamento a questa sezione

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) {
id
name
}
}
query SingleRecord {
entity(id: Y) {
id
name
}
}

Example of optimized querying:

query ManyRecords {
entities(where: { id_in: [X, Y] }) {
id
name
}
}

Combine multiple queries in a single request

Collegamento a questa sezione

L'applicazione potrebbe richiedere di effettuare query di più tipi di dati, come di seguito indicato:

import { execute } from "your-favorite-graphql-client"
const tokensQuery = `
query GetTokens {
tokens(first: 50) {
id
owner
}
}
`
const countersQuery = `
query GetCounters {
counters {
id
value
}
}
`
const [tokens, counters] = Promise.all(
[
tokensQuery,
countersQuery,
].map(execute)
)

Sebbene questa implementazione sia assolutamente valida, richiederà due viaggi di andata e ritorno con API GraphQL.

Fortunatamente, è anche possibile inviare più query nella stessa richiesta GraphQL, come segue:

import { execute } from "your-favorite-graphql-client"
const query = `
query GetTokensandCounters {
tokens(first: 50) {
id
owner
}
counters {
id
value
}
}
`
const { result: { tokens, counters } } = execute(query)

Questo approccio migliorerà le prestazioni complessive riducendo il tempo trascorso sulla rete (si risparmia un viaggio di andata e ritorno verso l'API) e fornirà un'implementazione più concisa.

Sfruttare i frammenti GraphQL

Collegamento a questa sezione

Una funzione utile per scrivere GraphQL query è GraphQL Fragment.

Osservando la seguente query, si noterà che alcuni campi sono ripetuti su Selection-Sets multipli ({ ... }):

query {
bondEvents {
id
newDelegate {
id
active
status
}
oldDelegate {
id
active
status
}
}
}

Tali campi ripetuti (id, active, status) comportano molti problemi:

  • più difficile da leggere per le query più estese
  • quando si usano strumenti che generano tipi TypeScript basati su query (per saperne di più nell'ultima sezione), newDelegate e oldDelegate risulteranno in due interfacce inline distinte.

Una versione riadattata della query sarebbe la seguente:

query {
bondEvents {
id
newDelegate {
...DelegateItem
}
oldDelegate {
...DelegateItem
}
}
}
# we define a fragment (subtype) on Transcoder
# to factorize repeated fields in the query
fragment DelegateItem on Transcoder {
id
active
status
}

L'uso di GraphQL fragment migliorerà la leggibilità (soprattutto in scala), ma anche la generazione di tipi TypeScript.

Quando si usa lo strumento di generazione dei tipi, la query di cui sopra genererà un tipo DelegateItemFragment corretto (vedi l'ultima sezione "Strumenti").

I frammenti GraphQL da fare e da non fare

Collegamento a questa sezione

La base del frammento deve essere un tipo

Un frammento non può essere basato su un tipo non applicabile, in breve, su un tipo che non ha campi:

fragment MyFragment on BigInt {
# ...
}

BigInt è un scalare (tipo nativo "semplice") che non può essere usato come base di un frammento.

Come diffondere un frammento

I frammenti sono definiti su tipi specifici e devono essere usati di conseguenza nelle query.

Esempio:

query {
bondEvents {
id
newDelegate {
...VoteItem # Error! `VoteItem` cannot be spread on `Transcoder` type
}
oldDelegate {
...VoteItem
}
}
}
fragment VoteItem on Vote {
id
voter
}

newDelegate e oldDelegate sono di tipo Transcoder.

Non è possibile diffondere un frammento di tipo Vote qui.

Definire il frammento come unità aziendale atomica di dati

I Fragment GraphQL devono essere definiti in base al loro utilizzo.

Per la maggior parte dei casi d'uso, è sufficiente definire un fragment per tipo (nel caso di utilizzo di campi ripetuti o di generazione di tipi).

Ecco una regola empirica per l'utilizzo di Fragment:

  • quando i campi dello stesso tipo si ripetono in una query, raggrupparli in un Fragment
  • quando si ripetono campi simili ma non uguali, creare più fragment, ad esempio:
# base fragment (mostly used in listing)
fragment Voter on Vote {
id
voter
}
# extended fragment (when querying a detailed view of a vote)
fragment VoteWithPoll on Vote {
id
voter
choiceID
poll {
id
proposal
}
}

Gli strumenti essenziali

Collegamento a questa sezione

Esploratori web GraphQL

Collegamento a questa sezione

Iterating over queries by running them in your application can be cumbersome. For this reason, don't hesitate to use Graph Explorer 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 Altair and GraphiQL.

Linting di GraphQL

Collegamento a questa sezione

Per tenere il passo con le best practice e le regole sintattiche di cui sopra, si consiglia vivamente di utilizzare i seguenti strumenti di workflow e IDE.

GraphQL ESLint

GraphQL ESLint will help you stay on top of GraphQL best practices with zero effort.

Setup the "operations-recommended" config will enforce essential rules such as:

  • @graphql-eslint/fields-on-correct-type: un campo è utilizzato su un tipo corretto?
  • @graphql-eslint/no-unused variables: una determinata variabile deve rimanere inutilizzata?
  • e altro ancora!

Questo vi permetterà di cogliere gli errori senza nemmeno testare le query sul playground o eseguirle in produzione!

VSCode e GraphQL

L'estensione GraphQL VSCode è un'eccellente aggiunta al vostro flusso di lavoro di sviluppo:

  • evidenziazione della sintassi
  • suggerimenti per il completamento automatico
  • validazione rispetto allo schema
  • frammenti
  • vai alla definizione dei frammenti e dei tipi dell'input

Se si utilizza graphql-eslint, l'estensione ESLint VSCode è indispensabile per visualizzare correttamente gli errori e gli avvertimenti inseriti nel codice.

WebStorm/Intellij e GraphQL

Il plugin JS GraphQL migliorerà significativamente l'esperienza di lavoro con GraphQL fornendo:

  • evidenziazione della sintassi
  • suggerimenti per il completamento automatico
  • validazione rispetto allo schema
  • frammenti

Maggiori informazioni in questo articolo di WebStorm che illustra tutte le caratteristiche principali del plugin.

Modifica pagina

Precedente
Gestione delle chiavi API
Successivo
Eseguire una query da un'applicazione
Modifica pagina