跳转到主要内容
Chinese, Simplified

GraphQL的真正威力…从客户端请求一直到数据库指定所请求的字段。

Image for post

问题

GraphQL最强大的特性之一是客户端可以指定从响应返回的字段,从而减少通过网络传输的数据,从而避免过度获取数据。

然而,我们真的做了更少的工作吗?后端服务器和数据库仍然必须执行查询数据库的所有工作,获取被请求对象的所有字段,然后仅通过GraphQL/Network层返回被请求的字段。

因此,通过发送较小的响应大小,我们只是节省了网络时间,但我们的后端服务器和数据库仍然在做额外的不必要的工作,以获取所请求对象的所有字段。这基本上是浪费了很多时间,我们可以优化。

解决方案

如果有一种方法可以确定客户机请求哪些字段,并且只从数据库层返回这些字段,那该怎么办?

大多数现代数据库都提供了“投影”或“选择”特性,其中数据库驱动程序允许使用者指定应该从与数据库查询匹配的文档中返回哪些字段,以避免返回所有不必要的字段,并加快对数据库的查询。

这对数据库查询性能有相当大的影响,因为它避免了不必要的工作,如果您想知道潜在的节省是什么样子的,可以看看我之前写的这篇文章,它展示了在MongoDB中使用/不使用投影的一些性能指标。

Apollo-Server解析器函数

因此,我们可以从网络层和数据库层实现这一点,唯一缺少的是在我们的服务器/解析器中解决它。

用apollo-server编写的graphQL解析器函数通常接受四个输入参数。根,args,上下文和信息。如果我们要写一个解析器,返回所有用户从数据库举例,它会像这样:

resolvers = {
  getUsers: function (root, args, context, info) {
    const User = mongoose.model('User')
    return User.find({})
  }
}

根对象通常是Schema中请求的操作/字段的父对象。args是可以传递给该操作的任何参数,而上下文包括传递到上下文对象的任何参数(通常用于身份验证)。但是info对象呢?它似乎包括一些关于传入请求的信息。

所以我在apollographql.com上查阅了它的文档,这是我找到的

Info:这个参数只应该在高级情况下使用,但是它包含关于查询执行状态的信息,包括字段名、从根到字段的路径等等。它只在GraphQL.js源代码中有文档说明。

不是很有用,但我认为它可能包含有关传入请求和请求字段的必要信息,在查看了可能能够执行我想要的操作的在线库之后,我发现了graphql-fields。

graphql-fields

我遇到了graphql-fields,它可以根据info参数返回被请求的字段,它还可以处理高级情况,如联合类型、片段、子字段( union types, fragments, sub-fields)等。

下面是它所能做的,对于这样一个返回所有用户姓名和电子邮件的查询

{
  users {
    name
    email
  }
}

结果就是:[' name ', ' email ']

用法:

const graphqlFields = require(‘graphql-fields’);
function userResolver(root, args, context, info) {
  const topLevelFields = Object.keys(graphqlFields(info));
  console.log(topLevelFields)
  // prints [‘name’, ‘email’]
}

很好,现在我们可以使用这个结果从数据库中只选择这些字段,而不是为每个用户从数据库中请求整个用户文档。

 

使用结果

在我的例子中,我使用MongoDB和Mongoose作为数据库驱动程序,所以Mongoose投影所需的语法与我从graphql-fields得到的结果有一点不同。

我想实现的等价查询如下所示:

User.find({}).select({name: 1, email: 1}).lean()

将来自graphql-fields的结果转换为猫鼬需要的选择对象的helper函数如下:

export function getMongooseSelectionFromSelectedFields (info, fieldPath = null) {
  const selections = graphqlFields(info)
  const mongooseSelection = Object
  .keys(fieldPath ? selections[fieldPath] : selections)
  .reduce((a, b) => ({ …a, [b]: 1   }), {})
  return mongooseSelection
}

好了,现在客户端请求的字段就是数据库请求和返回的字段,这使我们的解析器更快了!

这在自动持久化查询(APQ)和缓存中完美地工作。另外两个表现容易获胜。

我添加了一个fieldPath参数来支持响应中的一层嵌套,以防从数据库请求数据的操作不是顶层操作。

这是新的解析器的样子:

resolvers = {
  getUsers: function (root, args, context, info) {
    const mongooseSelection = getMongooseSelectionFromRequest(info)
    return User.find({}).select(mongooseSelection).lean()
  }
}

结论

GraphQL是相当强大的,它不仅优化性能的客户端应用,但也可以用于优化后端性能,毕竟我们得到的特定请求字段在我们的解析器免费。

 

原文:https://itnext.io/graphql-performance-tip-database-projection-82795e434b44

本文:http://jiagoushi.pro/node/1176

讨论:请加入知识星球【首席架构师智库】或者小号【jiagoushi_pro】

 

Tags
 
Article
知识星球
 
微信公众号
 
视频号