Bästa praxis för förfrågningar
Reading time: 9 min
The Graph tillhandahåller ett decentraliserat sätt att hämta data från blockkedjor.
The Graph-nätverkets data exponeras genom ett GraphQL API, vilket gör det enklare att fråga data med GraphQL-språket.
Den här sidan kommer att guida dig genom de grundläggande reglerna för GraphQL-språket och bästa praxis för GraphQL-frågor.
Till skillnad från REST API bygger ett GraphQL API på ett schema som definierar vilka frågor som kan utföras.
Till exempel kommer en fråga för att hämta en token med hjälp av frågan token
att se ut som följer:
query GetToken($id: ID!) {token(id: $id) {idowner}}
som kommer att returnera följande förutsägbara JSON-svar (när du skickar rätt $id
variabelvärde):
{"token": {"id": "...","owner": "..."}}
GraphQL-frågor använder GraphQL-språket, som definieras i .
Ovanstående GetToken
-fråga består av flera språkdelar (ersätts nedan med [...]
platshållare):
query [operationName]([variableName]: [variableType]) {[queryName]([argumentName]: [variableName]) {# "{ ... }" uttrycker en Selection-Set, vi frågar efter fält från `queryName`.[field][field]}}
Listan över syntaktiska do's and don'ts är lång, men här är de viktigaste reglerna att tänka på när det gäller att skriva GraphQL-förfrågningar:
- Varje
queryName
får endast användas en gång per operation. - Varje
field
får bara användas en gång i ett urval (vi kan inte frågaid
två gånger undertoken
) - 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 . - Varje variabel som tilldelas ett argument måste matcha dess typ.
- I en given lista med variabler måste var och en av dem vara unik.
- Alla definierade variabler måste användas.
Om du inte följer ovanstående regler kommer du att få ett felmeddelande från Graph API.
For a complete list of rules with code examples, please look at our .
GraphQL är ett språk och en uppsättning konventioner som transporteras över HTTP.
Det innebär att du kan ställa en fråga till ett GraphQL API med hjälp av standard fetch
(nativt eller via @whatwg-node/fetch
eller isomorphic-fetch
).
Men, som det anges i , rekommenderar vi att du använder vår graph-client
som stöder unika funktioner som:
- Hantering av subgrafer över olika blockkedjor: Frågehantering från flera subgrafer i en enda fråga
- Fullt typad resultat
Så här ställer du en fråga till The Graph med 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` är fullständigt typad!console.log(result)}main()
Fler GraphQL-klientalternativ behandlas i .
Nu när vi har gått igenom de grundläggande reglerna för syntax för GraphQL-förfrågningar ska vi titta på bästa praxis för att skriva GraphQL-förfrågningar.
En vanlig (dålig) praxis är att dynamiskt bygga upp frågesträngar enligt följande:
const id = params.idconst fields = ['id', 'owner']const query = `query GetToken {token(id: ${id}) {${fields.join('\n')}}}`// Execute query...
Medan det tidigare avsnittet genererar en giltig GraphQL-fråga har den många nackdelar:
- det gör det svårare att förstå frågan som helhet
- utvecklare är ansvariga för att säkert sanera stränginterpolationen
- att inte skicka värdena av variablerna som en del av förfrågningsparametrarna förhindrar möjlig cache på servern
- det hindrar verktyg från statisk analys av frågan (exempel: Linter eller typgenereringsverktyg)
Av dessa skäl rekommenderas det alltid att skriva frågor som statiska strängar:
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,},})
Detta medför många fördelar:
- Lättlästa och underhållna frågor
- GraphQL server hanterar sanitet av variabler
- Variabler kan cachas på serversidan
- Frågor kan statiskt analyseras av verktyg (mer om detta i följande avsnitt)
Observera: Hur man inkluderar fält villkorligt i statiska frågor
Ibland vill vi inkludera fältet owner
endast under vissa villkor.
För detta kan vi utnyttja direktivet @include(if:...)
på följande sätt:
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,},})
Observera: Det motsatta direktivet är @skip(if: ...)
.
GraphQL blev känd för sitt motto "Be om det du vill ha".
Av den anledningen finns det ingen möjlighet i GraphQL att få alla tillgängliga fält utan att behöva lista dem individuellt.
När du frågar GraphQL API:er, tänk alltid på att endast fråga efter de fält som faktiskt kommer att användas.
En vanlig orsak till överhämtning är samlingar av enheter. Som standard kommer frågor att hämta 100 enheter i en samling, vilket vanligtvis är mycket mer än vad som faktiskt kommer att användas, t.ex., för att visas för användaren. Därför bör frågor nästan alltid ange first explicit och se till att de bara hämtar så många enheter som de faktiskt behöver. Detta gäller inte bara för toppnivåsamlingar i en fråga, utan ännu mer för inbäddade samlingar av enheter.
Till exempel, i följande fråga:
query listTokens {tokens {# kommer att ge upp till 100 tokensidtransactions {# kommer att ge upp till 100 transaktionerid}}}
Svaret kan innehålla 100 transaktioner för varje av de 100 tokens.
Om applikationen bara behöver 10 transaktioner bör frågan explicit ange first: 10
på transaktionsfältet.
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}}
Din applikation kan kräva att du ställer flera typer av datafrågor enligt följande:
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))
Medan denna implementation är helt giltig kräver den två rundturer med GraphQL API:n.
Lyckligtvis är det också giltigt att skicka flera frågor i samma GraphQL-begäran enligt följande:
import { execute } from "your-favorite-graphql-client"const query = `query GetTokensandCounters {tokens(first: 50) {idowner}counters {idvalue}}`const { result: { tokens, counters } } = execute(query)
Detta tillvägagångssätt kommer att förbättra den övergripande prestandan genom att minska tiden som spenderas på nätverket (sparar en omväg till API:n) och kommer att ge en mer koncis implementation.
En användbar funktion för att skriva GraphQL-frågor är GraphQL-fragment.
Om vi tittar på följande fråga kommer du att märka att vissa fält upprepas över flera urvalssatser ({ ... }
):
query {bondEvents {idnewDelegate {idactivestatus}oldDelegate {idactivestatus}}}
Sådana upprepade fält (id
, active
, status
) medför många problem:
- svårare att läsa för mer omfattande frågor
- när du använder verktyg som genererar TypeScript-typer baserat på frågor (mer om det i den sista avsnittet), kommer
newDelegate
ocholdDelegate
att resultera i två olika inline-gränssnitt.
En omstrukturerad version av frågan skulle vara följande:
query {bondEvents {idnewDelegate {...DelegateItem}oldDelegate {...DelegateItem}}}# vi definierar ett fragment (subtyp) på Transcoder# att faktorisera upprepade fält i fråganfragment DelegateItem on Transcoder {idactivestatus}
Att använda GraphQL fragment
kommer att förbättra läsbarheten (särskilt i större skala) och leda till bättre generering av TypeScript-typer.
När du använder verktyget för typsgenerering kommer den ovanstående frågan att generera en korrekt typ av DelegateItemFragment
(se sista avsnittet).
Fragmentbas måste vara en typ
Ett fragment kan inte baseras på en oanvändbar typ, kort sagt, på en typ som inte har fält:
fragment MyFragment on BigInt {# ...}
BigInt
är en skalär (inbyggd "vanlig" typ) som inte kan användas som grund för ett fragment.
Hur man sprider ett fragment
Fragment är definierade på specifika typer och bör användas i enlighet med det i frågor.
Exempel:
query {bondEvents {idnewDelegate {...VoteItem # Fel! `VoteItem` kan inte spridas på `Transcoder` typ}oldDelegate {...VoteItem}}}fragment VoteItem on Vote {idvoter}
newDelegate
och oldDelegate
är av typen Transcoder
.
Det är inte möjligt att sprida ett fragment av typ Vote
här.
Definiera fragment som en atomisk affärsenhet för data
GraphQL Fragment måste definieras baserat på deras användning.
För de flesta användningsfall är det tillräckligt att definiera ett fragment per typ (i fallet med upprepade fält eller typgenerering).
Här är en tumregel för användning av fragment:
- när fält av samma typ upprepas i en fråga, gruppera dem i ett fragment
- när liknande men inte samma fält upprepas, skapa flera fragment, t.ex.
# base fragment (mostly used in listing)fragment Voter on Vote {idvoter}# utökat fragment (vid förfrågan om en detaljerad vy av en omröstning)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 .
För att hålla dig uppdaterad med de tidigare nämnda bästa praxis och syntaktiska regler rekommenderas det starkt att använda följande arbetsflöde och IDE-verktyg.
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
: används ett fält på en korrekt typ?@graphql-eslint/no-unused variables
: bör en given variabel förbli oanvänd?- och mer!
Detta kommer att tillåta dig att upptäcka fel utan ens att testa frågor på lekplatsen eller köra dem i produktion!
VSCode och GraphQL
är ett utmärkt komplement till din utvecklingsarbetsflöde för att få:
- syntaxmarkering
- autokompletteringsförslag
- validering mot schema
- snuttar
- gå till definition för fragment och inmatningstyper
Om du använder graphql-eslint
är ett måste för att visualisera fel och varningar korrekt infogade i din kod.
WebStorm/Intellij och GraphQL
kommer att förbättra din upplevelse av att arbeta med GraphQL genom att tillhandahålla:
- syntaxmarkering
- autokompletteringsförslag
- validering mot schema
- snuttar
Mer information om denna som visar upp alla tilläggets huvudfunktioner.