9 minutos
Etiqueta de Query
O The Graph fornece uma maneira descentralizada de buscar dados de blockchains (queries). Os dados são expostos por uma API da GraphQL, o que facilita queries com a linguagem GraphQL.
Aprenda as regras linguísticas essenciais da GraphQL e as melhores práticas para otimizar o seu subgraph.
Queries numa API GraphQL
A Anatomia de um Query em 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:
1query GetToken($id: ID!) {2 token(id: $id) {3 id4 owner5 }6}
…assim retornando a seguinte resposta previsível em JSON (ao passar o valor variável $id apropriado):
1{2 "token": {3 "id": "...",4 "owner": "..."5 }6}
Queries em GraphQL usam a linguagem GraphQL, definida sobre uma especificação.
O query GetToken
acima é composto de várias partes da linguagem (substituído abaixo com [...]
temporários):
1query [operationName]([variableName]: [variableType]) {2 [queryName]([argumentName]: [variableName]) {3 # "{ ... }" expressa um Selection-Set, estamos a consultar campos do `queryName`.4 [field]5 [field]6 }7}
Regras para Escrever Queries em GraphQL
- Cada
queryName
só pode ser usado uma vez por operação. - Cada
field
deve ser usado apenas uma vez numa seleção (não podemos solicitar aid
duas vezes sob otoken
) - Alguns
field
s ou queries (comotokens
) retornam tipos complexos que exigem uma seleção de sub-campo. Caso uma seleção não seja fornecida quando esperada (ou fornecida quando não é esperada — por exemplo, emid
), aparecerá um erro. Para conhecer um tipo de campo, consulte o Graph Explorer. - 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 The Graph.
Para uma lista completa de regras com exemplos de código, veja o guia de Validações da GraphQL.
Como enviar um query a uma API GraphQL
GraphQL é uma linguagem e um conjunto de convenções transportadas 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, conforme mencionado em “Como Fazer Queries de Um Aplicativo”, recomendamos usar o graph-client
, que apoia as seguintes funções únicas:
- Gestão de Subgraph Entre Chains: Queries de múltiplos subgraphs numa única consulta
- Rastreamento Automático de Blocos
- Paginação Automática
- Resultado totalmente digitado
Aqui está como fazer queries para o The Graph com o 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()
Para mais alternativas de clientes para GraphQL, veja “Como Fazer Queries de Um Aplicativo”.
Boas práticas
Sempre escreva consultas estáticas
É (um erro) comum construir strings de query dinamicamente, como no exemplo a seguir:
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...
Enquanto o trecho acima produz um query válido na GraphQL, isto traz muitas desvantagens:
- deixa o query mais difícil de entender
- os programadores são responsáveis por higienizar a interpolação de string com segurança
- não enviar os valores das variáveis como parte dos parâmetros de pedido impede um possível caching no lado do servidor
- isto impede as ferramentas de analisar o query estaticamente (por ex. Linter ou ferramentas de geração de tipos)
Por isto, é recomendado sempre escrever queries como strings estáticas:
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})
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 guardadas em cache no nível do servidor
- Queries podem ser analisados estaticamente por ferramentas (mais sobre isto nas secções seguintes)
Como incluir campos condicionalmente em queries estáticos
Talvez queira incluir o campo owner
apenas com uma condição particular.
Para isto, use a diretiva @include(if:...)
a seguir:
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})
Observação: a diretiva oposta é @skip(if: ...)
.
Pergunte pelo que queres
A GraphQL ficou famosa 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.
- Tenha certeza que os queries só retornarão o máximo necessário 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. Isto serve não só para coleções de alto nível em um query, mas também — especialmente — para coleções aninhadas de entidades.
Por exemplo, no query seguinte:
1query listTokens {2 tokens {3 # will fetch up to 100 tokens4 id5 transactions {6 # will fetch up to 100 transactions7 id8 }9 }10}
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.
Use uma única query para pedir vários registros
Por padrão, subgraphs têm uma entidade singular para um registo. Para múltiplos registos, use as entidades plurais e o filtro: where: {id_in:[X,Y,Z]}
ou where: {volume_gt:100000}
Um exemplo de query ineficaz:
1query SingleRecord {2 entity(id: X) {3 id4 name5 }6}7query SingleRecord {8 entity(id: Y) {9 id10 name11 }12}
Um exemplo de query otimizado:
1query ManyRecords {2 entities(where: { id_in: [X, Y] }) {3 id4 name5 }6}
Combine múltiplas queries em um único pedido
O seu aplicativo pode exigir queries de múltiplos tipos de dados, como a seguir:
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)
Enquanto esta implementação é totalmente válida, ela exigirá duas rondas totais com a API da GraphQL.
Felizmente, também vale enviar múltiplos queries no mesmo pedido à GraphQL, como a seguir:
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)
Este método melhorará o desempenho em geral ao reduzir o tempo gasto na rede (porque poupa uma viagem ao redor da API) e fornecerá implementações mais concisas.
Como Aproveitar Fragmentos GraphQL
O GraphQL Fragment é uma ferramenta útil para a escrita de queries em GraphQL.
No seguinte query, perceba que alguns campos são repetidos em vários Selection-Sets ({ ... }
):
1query {2 bondEvents {3 id4 newDelegate {5 id6 active7 status8 }9 oldDelegate {10 id11 active12 status13 }14 }15}
Estes campos repetidos (id
, active
, status
) trazem muitos problemas:
- Queries mais extensos ficam difíceis de ler.
- Ao usar ferramentas que geram tipos TypeScript baseados em queries (mais na última secção),
newDelegate
eoldDelegate
retornarão duas interfaces distintas em inline.
Fatorizado novamente, o query ficaria assim:
1query {2 bondEvents {3 id4 newDelegate {5 ...DelegateItem6 }7 oldDelegate {8 ...DelegateItem9 }10 }11}1213# nós definimos um fragmento (subtipo) no Transcoder14# para fatorizar campos repetidos no query15fragment DelegateItem on Transcoder {16 id17 active18 status19}
Usar o fragment
(“fragmento”) da 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 secção “Ferramentas”).
O que fazer e o que não fazer em Fragments GraphQL
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:
1fragment MyFragment on BigInt {2 # ...3}
O BigInt
é um escalar (tipo “plano” nativo) que não pode ser usado como a base de um fragmento.
Como espalhar um Fragment
Fragmentos são definidos em tipos específicos e devem ser usados de acordo nos queries.
Exemplo:
1query {2 bondEvents {3 id4 newDelegate {5 ...VoteItem # Error! `VoteItem` cannot be spread on `Transcoder` type6 }7 oldDelegate {8 ...VoteItem9 }10 }11}1213fragment VoteItem on Vote {14 id15 voter16}
newDelegate
e oldDelegate
são do tipo Transcoder
.
Não é possível espalhar um fragmento do tipo Vote
aqui.
Defina o Fragment como uma unidade de negócios atômica de dados
Fragment
s da GraphQL devem ser definidos com base no seu uso.
Para a maioria dos casos de uso, definir um fragmento por tipo (no caso do uso repetido de campos ou geração de tipos) já é o suficiente.
Aqui estão algumas regras básicas para o uso de Fragmentos:
- Quando campos do mesmo tipo se repetem em um query, agrupe-os em um
Fragment
. - Quando campos parecidos (mas não idênticos) se repetem, crie múltiplos fragmentos, por exemplo:
1# base fragment (mostly used in listing)2fragment Voter on Vote {3 id4 voter5}67# extended fragment (when querying a detailed view of a vote)8fragment VoteWithPoll on Vote {9 id10 voter11 choiceID12 poll {13 id14 proposal15 }16}
Ferramentas Essenciais
Exploradores do GraphQL baseados em web
Pode ser até chato executar queries no seu aplicativo para iterar sobre elas. Por isto, não hesite em usar o Graph Explorer para testar os seus queries antes de adicioná-los. O Graph Explorer fornecerá um ambiente de testes GraphQL pré-configurado para testar os seus queries.
Se procura uma maneira mais flexível de depurar/testar seus queries, há outras ferramentas semelhantes baseadas na web, como Altair e GraphiQL.
GraphQL Linting
Para acompanhar as melhores práticas e regras sintáticas explicadas acima, vale muito a pena utilizar as ferramentas IDE e de fluxo de trabalho a seguir.
ESLint — GraphQL
O ESLint da GraphQL te ajudará a acompanhar as melhores práticas da GraphQL sem sofrimento.
Organize a configuração “operations-recommended” para executar regras essenciais como:
@graphql-eslint/fields-on-correct-type
: um campo está num tipo apropriado?@graphql-eslint/no-unused variables
: should a given variable stay unused?- e mais!
Isto permitirá-lhe detetar erros mesmo sem testar queries no playground ou mesmo executá-los na produção!
Plugins IDE
VSCode e GraphQL
A extensão VSCode da GraphQL é uma adição excelente ao seu fluxo de programação que permite:
- Destaque de sintaxe
- Sugestões de preenchimento automático
- Validação perante schema
- Snippets (blocos de código reutilizáveis)
- Definições de fragmentos e tipos de entrada
Se utilizar o graphql-eslint
, a extensão VSCode para o ESLint é essencial para visualizar corretamente erros e avisos embutidos no seu código.
WebStorm/Intellij e GraphQL
O plugin JavaScript para a GraphQL melhorará muito a sua experiência com a GraphQL com:
- Destaque de sintaxe
- Sugestões de preenchimento automático
- Validação perante schema
- Snippets (blocos de código reutilizáveis)
Para mais informações sobre este tópico, veja o artigo do WebStorm, que demonstra todas as funções principais do plugin.