查询 > 查询最佳实践

查询最佳实践

Reading time: 13 min

Graph提供了一种从区块链查询数据的去中心化方式。

Graph网络的数据通过GraphQL API公开,使得使用GraphQL语言查询数据更加容易。

本页将指导您了解基本的GraphQL语言规则和GraphQL查询最佳实践。


查询GraphQL API

链到本节

GraphQL查询的剖析

链到本节

与REST API不同,GraphQL API构建在定义可以执行哪些查询的模式之上。

例如,使用token查询获取代币的查询如下所示:

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

它将返回以下可预测的 JSON 响应(当传递适当的 $id 变量值时):

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

GraphQL 查询使用基于规范定义的 GraphQL 语言。

上面的 GetToken 查询由多个语言部分组成(下面用[...]占位符替换):

query [operationName]([variableName]: [variableType]) {
[queryName]([argumentName]: [variableName]) {
# "{ ... }" 代表一个选择集, 我们正在从`queryName`查询域。
[field]
[field]
}
}

虽然语法的“是”和“不是”的列表很长,但在编写 GraphQL 查询时,以下是需要记住的重要规则:

  • 每个操作只能使用一次 queryName
  • 每个field在选择中只能使用一次(我们不能在token下查询 id 两次)
  • 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.
  • 分配给参数的任何变量都必须匹配其类型。
  • 在给定的变量列表中,每个变量必须是唯一的。
  • 必须使用所有已定义的变量。

不遵循上述规则将以 GraphAPI 返回的错误结束。

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

向 GraphQL API 发送查询

链到本节

GraphQL 是一种通过 HTTP 传输的语言和一组协议。

这意味着您可以使用标准fetch(本机或通过@whatwg-node/提取isomorphic-fetch) 查询 GraphQL API。

但是,正如“从应用程序查询”中所说,我们建议您使用我们的graph-client,该客户端支持以下独特功能:

以下是如何使用 Graph-client 查询Graph:

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()

“从应用程序查询”中介绍了更多的 GraphQL 客户端替代方案。

现在我们已经介绍了 GraphQL 查询语法的基本规则,接下来让我们看看 GraphQL 查询编写的最佳实践。


Best Practices

链到本节

始终编写静态查询

链到本节

一个常见的(不好的) 实践是动态构建查询字符串,如下所示:

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

虽然上面的代码生成了一个有效的 GraphQL 查询,但是它有很多缺点:

  • 它使得从整体上理解查询变得更加困难
  • 开发人员负责安全地消毒字符串插值
  • 如果不将变量的值作为请求参数的一部分发送,则可能会阻止服务器端的缓存
  • 阻止工具静态分析查询(例如: Linter 或类型生成工具)

因此,建议始终将查询写为静态字符串:

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,
},
})

这样做有很多好处:

  • 易于阅读和维护查询
  • GraphQL 服务器处理变量的净化
  • 可以在服务器级别缓存变量
  • 查询可以通过工具进行静态分析(下面几节将详细介绍)

注意: 如何在静态查询中有条件地包括字段

我们可能希望仅在特定条件下包括 owner 字段。

为此,我们可以利用@include (if:...),如下所示:

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,
},
})

注意:相反的指令是@skip(if:…)。

Ask for what you want

链到本节

GraphQL以其“问你所想”的口号而闻名。

因此,在GraphQL中,不单独列出所有可用字段,就无法获取所有可用字段。

在查询GraphQL API时,请始终考虑只查询实际使用的字段。

过度获取的一个常见原因是实体集合。默认情况下,查询将获取集合中的100个实体,这通常比实际使用的实体多得多,例如,用于向用户显示的实体。因此,查询几乎总是首先显式设置,并确保它们只获取实际需要的实体。这不仅适用于查询中的一层集合,更适用于实体的嵌套集合。

例如,在以下查询中:

query listTokens {
tokens {
# 获取最多100个token
id
transactions {
# 获取最多100个交易
id
}
}
}

该响应可以包含100个代币中的100个交易。

如果应用程序只需要10个交易,那么查询应该首先在交易字段上显式设置:first: 10

Use a single query to request multiple records

链到本节

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

链到本节

您的应用程序可能需要查询多种类型的数据,如下所示:

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)
)

虽然这个实现是完全有效的,但它需要使用GraphQL API进行两次交互。

幸运的是,在同一GraphQL请求中发送多个查询也是有效的,如下所示:

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)

这种方法将通过减少在网络上花费的时间来提高整体性能(为您节省API交互的时间),并提供更简洁的实现

利用GraphQL片段

链到本节

编写GraphQL查询的一个有用功能是GraphQL片段。

查看以下查询,您会注意到某些字段在多个选择集({ ... }) 中重复:

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

此类重复字段(idactivestatus)会带来许多问题:

  • 当查询更广泛时将更难阅读
  • 当使用基于查询生成TypeScript类型的工具时(上一节将详细介绍),newDelegateoldDelegate将产生两个不同的内联接口。

查询的重构版本如下:

query {
bondEvents {
id
newDelegate {
...DelegateItem
}
oldDelegate {
...DelegateItem
}
}
}
# 我们在代码转换器定义了一个碎片(子类型)
# 将查询中重复的域分解
fragment DelegateItem on Transcoder {
id
active
status
}

使用GraphQLfragment将提高可读性(特别是在规模上),也将使更好的TypeScript类型生成。

当使用类型生成工具时,上述查询将生成一个正确的DelegateItemFragment类型(请参阅上一节“工具”)。

GraphQL片段的注意事项

链到本节

片段必须是一种类型

片段不能基于不适用的类型,简而言之,基于没有字段的类型

fragment MyFragment on BigInt {
# ...
}

BigInt是一个标量(原生“纯”类型),不能用作片段的基础类型。

如何传播片段

片段是在特定类型上定义的,应该在查询中相应地使用。

例子:

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

newDelegateoldDelegate 属于Transcoder类型。

无法在此处传播Vote类型的片段。

将片段定义为数据的原子业务单元。

GraphQL Fragment必须根据其用法进行定义。

对于大多数用例,为每个类型定义一个片段(在重复使用字段或生成类型的情况下)就足够。

以下是使用Fragment的经验法则:

  • 当相同类型的字段在查询中重复时,将它们分组为片段
  • 当重复类似但不相同的字段时,创建多个片段,例如:
# base fragment (主要在上架中使用)
fragment Voter on Vote {
id
voter
}
# extended fragment (当查询投票中一个选项的细节)
fragment VoteWithPoll on Vote {
id
voter
choiceID
poll {
id
proposal
}
}

重要工具

链到本节

GraphQL基于web的浏览器

链到本节

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.

GraphQL Linting

链到本节

为了跟上提到的最佳实践和语法规则,强烈建议使用以下工作流和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: 字段是否用于正确的类型?
  • @graphql-eslint/no-unused variables: 给定的变量是否应该保持未使用状态?
  • 还有更多!

这将允许您捕获错误,而无需在playground上测试查询或在生产环境中运行查询!

IDE插件

链到本节

VSCode和GraphQL

GraphQL VSCode扩展是对开发工作流的一个极好补充,可以获得:

  • 语法高亮显示
  • 自动完成建议
  • 根据模式验证
  • 片段
  • 转到片段和输入类型的定义

如果您使用的是graphql-eslitESLintVSCode扩展是正确可视化代码中内联的错误和警告的必备工具。

WebStorm/Intellij和GraphQL

JS GraphQL插件将通过提供以下功能显著改善您在使用GraphQL时的体验:

  • 语法高亮显示
  • 自动完成建议
  • 根据模式验证
  • 片段

有关这篇WebStorm文章的更多信息,其中展示了插件的所有主要功能。

编辑

上页
管理您的 API 密钥
下页
从应用程序中进行查询
编辑