13 分钟
查询最佳实践
Graph提供了一种去中心化的方式来查询区块链中的数据。它的数据是通过GraphQL API公开的,这使得使用GraphQL语言进行查询更加容易。
学习基本的 GraphQL 语言规则和最佳做法,以优化您的子图。
查询GraphQL API
GraphQL查询的剖析
与REST API不同,GraphQL API构建在定义可以执行哪些查询的模式之上。
例如,使用token
查询获取代币如下所示:
1query GetToken($id: ID!) {2 token(id: $id) {3 id4 owner5 }6}
它将返回以下可预测的 JSON 响应(当传递适当的 $id 变量值时):
1{2 "token": {3 "id": "...",4 "owner": "..."5 }6}
GraphQL 查询使用基于规范定义的 GraphQL 语言。
上述GetToken
查询由多语言部分组成(用[...]
占位符替换):
1query [operationName]([variableName]: [variableType]) {2 [queryName]([argumentName]: [variableName]) {3 # "{ ... }" 代表一个选择集, 我们正在从`queryName`查询域。4 [field]5 [field]6 }7}
写入 GraphQL 查询规则
- 每个操作只能使用一次
queryName
。 - 每个
字段
必须只能在选择中使用一次 (我们不能在token
下查询id
两次)。 - 有些
字段
或查询(如tokens
)返回了需要选择子字段的复杂类型。 预期时不提供选择(或在不预期时提供选择――例如在id
上――会引起错误。 要知道一个字段类型,请参阅Graph Explorer。 - 分配给参数的任何变量都必须匹配其类型。
- 在给定的变量列表中,每个变量必须是唯一的。
- 必须使用所有已定义的变量。
注意:如果不遵循这些规则,将导致从 The Graph API中发生错误。
有关包含代码示例的完整规则列表,请查看GraphQL Validations guide。
向 GraphQL API 发送查询
GraphQL 是一种通过 HTTP 传输的语言和一组协议。
这意味着您可以使用标准的 fetch
(nomatily or through @whatwg-node/fetch
或 isomorphic-fetch
)查询一个 GraphQL API。
然而,正如[“查询申请”](/subgraphs/querying/from-an-application/)中提到的那样,建议使用 graph-client
,支持以下独特功能:
以下是如何使用 Graph-client
查询The Graph:
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()
更多GraphQL客户端替代方案在“从应用程序查询”中介绍。
最佳实践
始终编写静态查询
一个常见的(不好的) 实践是动态构建查询字符串,如下所示:
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...
虽然上面的代码片段产生了一个有效的 GraphQL 查询,但它有许多缺点 :
- 它使得更难理解 整个查询
- 开发者负责安全清理字符串内插值
- 不将变量的值作为请求参数的一部分发送以防止服务器侧可能缓存
- 它阻止工具静态分析查询 (例如: Linter, 或类型代工具)
因此,建议始终将查询写为静态字符串:
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})
这样做会带有许多优点:
- 易读和维护 查询
- GraphQL 服务器处理变量净化
- **变量可以缓存到 **服务器级别
- 查询可以通过工具进行静态分析 (在以下章节中更多关于此问题)
如何在静态查询中有条件地包含字段
您可能只想在特定条件下包含 ‘所有者’ 字段。
为此,你可以将@include(if:...)
的指令应用如下:
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})
注意: 相反的指令是 @skip(if: ...)
。
问你所想
GraphQL以其“问你所想”的口号而闻名。
因此,在GraphQL中,不单独列出所有可用字段,就无法获取所有可用字段。
- 在查询GraphQL API时,请始终考虑只查询实际使用的字段。
- 请确保查询仅获取您实际需要的更多实体。 默认情况下,查询将会在集合中获取100个实体,这通常比实际使用的要多得多,如用于显示用户。这不仅适用于查询中的顶层集合,更适用于实体嵌套集合。
例如,在以下查询中:
1query listTokens {2 tokens {3 # 获取最多100个token4 id5 transactions {6 # 获取最多100个交易7 id8 }9 }10}
该响应可以包含每100个代币交易中的100个交易。
如果应用程序只需要10笔交易,查询应在交易字段中明确设置 first: 10
。
使用单个查询请求多个记录
默认情况下,子图表有一个单数实体用于一个记录。对于多个记录,请使用复数实体和过滤器:其中:{id_in:[X,Y,Z]}
或者其中: {volume_gt:100000}
。
低效查询示例:
1query SingleRecord {2 entity(id: X) {3 id4 name5 }6}7query SingleRecord {8 entity(id: Y) {9 id10 name11 }12}
优化查询示例:
1query ManyRecords {2 entities(where: { id_in: [X, Y] }) {3 id4 name5 }6}
在单个请求中合并多个查询
您的应用程序可能需要查询多种类型的数据,如下所示:
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)
虽然这个实现是完全有效的,但它需要使用GraphQL API进行两次交互。
幸运的是,在同一GraphQL请求中发送多个查询也是有效的,如下所示:
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)
这个方法将改进整体性能,减少网络上花费的时间(将你节省一次回程旅行到 API),并提供一个更简洁的实现。
利用GraphQL片段
编写GraphQL查询的一个有用功能是GraphQL片段。
查看以下查询,您会注意到某些字段在多个选择集({ ... }
) 中重复:
1query {2 bondEvents {3 id4 newDelegate {5 id6 active7 status8 }9 oldDelegate {10 id11 active12 status13 }14 }15}
此类重复字段(id
, active
, status
)会带来许多问题:
- 更广泛的查询变得更难阅读。
- 当使用基于查询生成类型的 TypeScript 类型的工具 (more on that in the last section),
newDelegate
和oldDelegate
将导致两个不同的内联接口。
查询的重构版本如下:
1query {2 bondEvents {3 id4 newDelegate {5 ...DelegateItem6 }7 oldDelegate {8 ...DelegateItem9 }10 }11}1213# 我们在代码转换器定义了一个碎片(子类型)14# 将查询中重复的域分解15fragment DelegateItem on Transcoder {16 id17 active18 status19}
使用 GraphQL fragment
将提高可读性(尤其在规模上),并导致更好的 TypeScript 类型生成。
当使用类型生成工具时,上面的查询将生成一个适当的DelegateItemFragment
类型(_see last “Tools” section _)。
GraphQL片段的注意事项
片段必须是一种类型
片段不能基于不适用的类型,简而言之,基于没有字段的类型:
1fragment MyFragment on BigInt {2 # ...3}
BigInt
是一个 scalar (原生”plain” 类型),不能用作碎片的基础。
如何传播片段
片段是在特定类型上定义的,应该在查询中相应地使用。
例子:
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
和oldDelegate
都是Transcoder
的类型。
不可能在这里散布Vote
型号的片段。
将片段定义为数据的原子业务单元。
必须根据它们的用途来定义GraphQL Fragment
。
对于大多数用例,为每个类型定义一个片段(在重复使用字段或生成类型的情况下)就足够了。
以下是使用Fragment的经验法则:
- 当同类字段重复查询时,将它们分组为
片段
。 - 当重复类似但不相同的字段时,创建多个片段,例如:
1# base fragment (主要在上架中使用)2fragment Voter on Vote {3 id4 voter5}67# extended fragment (当查询投票中一个选项的细节)8fragment VoteWithPoll on Vote {9 id10 voter11 choiceID12 poll {13 id14 proposal15 }16}
重要工具
GraphQL基于web的浏览器
通过在您的应用程序中运行这些查询来绕过它们,可能是繁琐的。 为此,请不要犹豫地使用 Graph Explorer 来测试您的查询,然后将它们添加到您的应用程序中。 Graph Explorer将为您提供一个预配置的 GraphQL 播放场,以测试您的查询。
如果您正在寻找一种更灵活的方式来调试或测试您的查询, 其他类似的网络工具也可用,如 Altair 和 GraphiQL。
GraphQL Linting
为了跟上提到的最佳实践和语法规则,强烈建议使用以下工作流和IDE工具。
GraphQL ESLint
GraphQL ESLint将帮助您保持在 GraphQL 最佳做法之外的零努力状态。
设置“推荐操作”配置将强制执行基本规则,例如:
@graphql-eslint/fields-on-correct-type
: 字段是否用于适当类型?@graphql-eslint/no-used 变量
: 某个变量是否不使用?- 还有更多!
这将允许您在运作场上捕获错误——甚至不需要测试查询 或者在生产中运行它们!
IDE插件
VSCode和GraphQL
[GraphQL VSCode扩展](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql)是开发工作流程中一个很好的补充,可以获得:
- 语法高亮
- 自动完成建议
- 针对模式的验证
- 代码片段
- 转到片段和输入类型的定义
如果您使用 graphql-eslint
, ESLint VSCode 扩展 是一个必须直观的错误和警告包含在您的代码中是正确的。
WebStorm/Intellij和GraphQL
JS GraphQL插件 在使用 GraphQL 时将通过提供以下方式大大改进您的体验:
- 语法高亮
- 自动完成建议
- 针对模式的验证
- 代码片段
了解更多关于此主题的信息,请查阅WebStorm 文章,该插件的所有主要功能。