Etiqueta de Query
Reading time: 9 min
O The Graph fornece um método descentralizado de consultar dados de blockchains em query.
Os dados da rede do The Graph são expostos por uma API GraphQL, o que facilita queries de dados com a linguagem GraphQL.
Este é um guia sobre as regras essenciais da linguagem GraphQL, e a etiqueta em queries com GraphQL.
Ao contrário da REST API, uma API GraphQL é construída em cima de um Schema que define quais queries podem ser realizados.
Por exemplo, um query para retornar um token através do query token
ficará assim:
query GetToken($id: ID!) {token(id: $id) {idowner}}
o que retornará a seguinte resposta previsível no JSON (ao passar o valor variável $id
apropriado):
{"token": {"id": "...","owner": "..."}}
Queries GraphQL usam a linguagem GraphQL, definida sobre .
O query GetToken
acima é composto de várias partes da linguagem (substituído abaixo com [...]
temporários):
query [operationName]([variableName]: [variableType]) {[queryName]([argumentName]: [variableName]) {# "{ ... }" expressa um Selection-Set, estamos a consultar campos do `queryName`.[field][field]}}
Enquanto a lista de sintaxes proibidas e permitidas é longa, veja a seguir as regras essenciais quando se trata de escrever queries GraphQL:
- Cada
queryName
deve ser usado apenas uma vez por operação. - Cada
field
deve ser usado apenas uma vez numa seleção (não podemos consultar aid
duas vezes sob otoken
) - Alguns
fields
s ou queries (comotokens
) retornam tipos complexos que requerem uma seleção de subcampo. Não providenciar uma seleção quando esperado (ou providenciar uma quando não é esperado - por exemplo, noid
) resultará num erro. Para conhecer um tipo de campo, consulte o . - Qualquer variável apontada a um argumento deve corresponder ao seu tipo.
- Em uma lista dada de variáveis, cada uma delas deve ser única.
- Todas as variáveis definidas devem ser usadas.
Não seguir as regras acima causará um erro da API do Graph.
Para uma lista completa de regras com exemplos de código, veja o nosso .
GraphQL é uma linguagem e conjunto de convenções que transportam através do HTTP.
Ou seja, dá para fazer um query numa API GraphQL com o fetch
normal (nativamente ou via @whatwg-node/fetch
ou isomorphic-fetch
).
Porém, como dito em , é melhor usar o nosso graph-client
, que apoia funções como:
- Gestão de Subgraph Cross-chain: Queries de múltiplos subgraphs numa única consulta
- Resultado totalmente digitado
Aqui está como fazer queries para o The Graph com o 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()
Falamos sobre mais alternativas de cliente GraphQL em .
Agora que cobrimos as regras básicas da sintaxe de queries GraphQL, vamos agora ver como escrever bons queries no GraphQL.
É (um erro) comum construir strings de consulta dinamicamente, como a seguir:
const id = params.idconst fields = ['id', 'owner']const query = `query GetToken {token(id: ${id}) {${fields.join('\n')}}}`// Execute query...
Enquanto o trecho acima produz um query válido no GraphQL, isto traz muitas desvantagens:
- ela dificulta a consulta na totalidade
- os programadores são responsáveis por higienizar com segurança a interpolação de string
- não mandar os valores das variáveis como parte dos parâmetros de pedido impede um possível caching no lado do servidor
- ela impede as ferramentas de analisar estaticamente a consulta (por ex. Linter ou ferramentas de geração de tipo)
Por isto, é recomendado sempre escrever queries como strings estáticas:
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,},})
Isto traz muitas vantagens:
- Queries fáceis de ler e manter
- O servidor GraphQL cuida da higienização de variáveis
- Variáveis podem ser cacheadas no nível do servidor
- Queries podem ser analisados estaticamente por ferramentas (mais sobre isto nas seções seguintes)
Nota: Como incluir campos condicionalmente em queries estáticos
Talvez você queira incluir o campo owner
com uma condição particular.
Para isto, use a diretiva @include(if:...)
a seguir:
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,},})
Nota: a diretiva oposta é @skip(if: ...)
.
O GraphQL ficou famoso por sua frase de efeito "pergunte pelo que queres".
Por isto, no GraphQL, não há como obter todos os campos disponíveis sem ter que listá-los individualmente.
Ao consultar APIs GraphQL, sempre considere fazer query apenas dos campos que serão usados.
Over-fetching é normalmente causado pela coleção de entidades. Por natureza, os queries retirarão 100 entidades em uma coleção, muito mais do que realmente será usado; por ex., para fins de amostra ao usuário. Portanto, queries devem quase sempre ser configurados primeiro, com a garantia de que só retirarão quantas entidades forem necessárias. Isto serve não só para coleções de alto nível em uma consulta, mas mais ainda para coleções aninhadas de entidades.
Por exemplo, no query seguinte:
query listTokens {tokens {# will fetch up to 100 tokensidtransactions {# will fetch up to 100 transactionsid}}}
A resposta pode conter 100 transações para cada um dos 100 tokens.
Se o aplicativo só precisa de 10 transações, o query deve configurar explicitamente first: 10
no campo de transações.
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}}
O seu aplicativo pode exigir queries de múltiplos tipos de dados, como a seguir:
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))
Enquanto esta implementação é totalmente válida, ela exigirá duas rondas totais com a API GraphQL.
Felizmente, também vale enviar múltiplos queries no mesmo pedido à GraphQL, como a seguir:
import { execute } from "your-favorite-graphql-client"const query = `query GetTokensandCounters {tokens(first: 50) {idowner}counters {idvalue}}`const { result: { tokens, counters } } = execute(query)
Este método melhorará o desempenho geral ao reduzir o tempo gasto na rede (pois, poupa-lhe de uma viagem ao redor da API) e fornecerá implementações mais concisas.
Uma boa ferramenta para escrever queries GraphQL é o GraphQL Fragment.
No seguinte query, perceba que alguns campos são repetidos em vários Selection-Sets ({ ... }
):
query {bondEvents {idnewDelegate {idactivestatus}oldDelegate {idactivestatus}}}
Estes campos repetidos (id
, active
, status
) trazem muitos problemas:
- difíceis de ler para consultas mais extensivas
- ao usar ferramentas que geram tipos TypeScript baseados em queries (mais sobre isto na última seção),
newDelegate
eoldDelegate
retornarão duas interfaces distintas em linha.
Refatorado, o query ficaria assim:
query {bondEvents {idnewDelegate {...DelegateItem}oldDelegate {...DelegateItem}}}# nós definimos um fragmento (subtipo) no Transcoder# para fatorizar campos repetidos no queryfragment DelegateItem on Transcoder {idactivestatus}
Usar o fragment
do GraphQL melhorará a legibilidade (especialmente em escala) e também melhorará a geração de tipos TypeScript.
Ao usar a ferramenta de geração de tipos, o query acima gerará um tipo DelegateItemFragment
apropriado (veja a última seção "Ferramentas").
A base do fragment deve ser um tipo
Um Fragment não pode ser baseado num tipo não aplicável; ou seja, um tipo sem campos:
fragment MyFragment on BigInt {# ...}
O BigInt
é um escalar (tipo "plano" nativo) que não pode ser usado como a base de um fragment.
Como espalhar um Fragment
Fragments são definidos em tipos específicos e devem ser usados de acordo nos queries.
Exemplo:
query {bondEvents {idnewDelegate {...VoteItem # Error! `VoteItem` cannot be spread on `Transcoder` type}oldDelegate {...VoteItem}}}fragment VoteItem on Vote {idvoter}
newDelegate
e oldDelegate
são do tipo Transcoder
.
Não é possível espalhar um fragment do tipo Vote
aqui.
Defina o Fragment como uma unidade de negócios atômica de dados
O Fragment GraphQL deve ser definido baseado no seu uso.
Para a maioria dos casos de uso, definir um fragment por tipo (no caso do uso repetido de campos ou geração de tipos) já basta.
Aqui estão algumas regras básicas para o uso de Fragments:
- quando campos do mesmo tipo repetem em um query, agrupe-os em um Fragment
- quando campos parecidos (mas não idênticos) repetem, crie múltiplos fragmentos, por ex:
# base fragment (mostly used in listing)fragment Voter on Vote {idvoter}# extended fragment (when querying a detailed view of a vote)fragment VoteWithPoll on Vote {idvoterchoiceIDpoll {idproposal}}
Pode ser até chato executar queries no seu aplicativo para iterar sobre elas. Por isto, não hesite em usar o para testar as suas queries antes de adicioná-las. O Graph Explorer lhe providenciará um ambiente de testes GraphQL pré-configurado para testar as suas queries.
Se procura uma maneira mais flexível de debugar/testar as suas consultas, há outras ferramentas baseadas em web, como e .
Para acompanhar as melhores práticas e regras sintáticas ditas acima, vale muito usar o workflow e as ferramentas IDE a seguir.
GraphQL ESLint
O ajudará-lhe a aperfeiçoar as melhores práticas no GraphQL sem nenhum esforço.
irá impor regras essenciais, como:
@graphql-eslint/fields-on-correct-type
: um campo está num tipo apropriado?@graphql-eslint/no-unused variables
: uma variável usada deve ficar sem uso?- e mais!
Isto permitirá-lhe detectar erros até mesmo sem testar queries no playground ou executá-las na produção!
VSCode e GraphQL
A é ótima para o seu workflow de desenvolvimento. Com ela, você consegue:
- destaque de sintaxe
- sugestões de autocomplete
- validação contra schema
- snippets
- definições para fragments e tipos de entrada
Se usa o graphql-eslint
, a é indispensável para visualizar corretamente os erros e avisos em inline no seu código.
WebStorm/Intellij e GraphQL
O melhorará muito a sua experiência de trabalho com o GraphQL, pois proverá:
- destaque de sintaxe
- sugestões de autocomplete
- validação contra schema
- snippets
Mais informações , que mostra todos os recursos principais do plugin.