为 SQL 数据库和面向服务的架构优化 GraphQL 解析器
Optimizing GraphQL resolvers for SQL databases and in service-oriented architectures
我的公司有一个面向服务的架构。因此,我的应用程序的 GraphQL 服务器必须调用其他服务来完成来自前端的数据请求。
让我们假设我的 GraphQL 模式定义了类型 User
。此类型的数据来自两个来源:
- 公开 REST 端点以获取用户的
username
、age
和 friends
的用户帐户服务。
- 我的应用仅使用 SQL 数据库来存储仅与我的应用相关的
User
相关数据:favoriteFood
、favoriteSport
.
假设用户帐户服务的端点自动 returns username
和 age
,但您必须传递查询参数 friends=true
才能检索friends
数据,因为这是一项昂贵的操作。
鉴于该背景,以下查询在 getUser
解析器中提出了一些优化挑战:
query GetUser {
getUser {
username
favoriteFood
}
}
挑战 #1
当 getUser
解析器向用户帐户服务发出请求时,它如何知道是否还需要请求 friends
数据?
挑战 #2
当解析器查询我的应用程序的数据库以获取额外的用户数据时,它如何知道要从数据库中检索哪些字段?
对于这两个挑战,我能找到的唯一解决方案是通过解析器收到的第四个 info
参数检查解析器中的查询。这将允许它找出是否应该在对用户帐户服务的 REST 调用中请求 friends
,并且它将能够构建正确的 SELECT
查询以从我的应用程序的数据库中检索所需的数据.
这是正确的做法吗?这似乎是一个用例,GraphQL 实现必须一直 运行,因此我希望遇到一个被广泛接受的解决方案。但是,我没有找到很多解决这个问题的文章,似乎也没有广泛使用的 NPM 模块(graphql-parse-resolve-info is part of PostGraphile but only has ~12k weekly downloads, while graphql-fields 每周下载量约为 18.5k)。
因此,我担心我遗漏了一些有关如何完成此操作的基本知识。我是吗?或者检查 info
参数是否是解决这些优化挑战的正确方法?以防万一,我正在使用 Apollo Server。
如果您想根据请求的选择集修改您的解析器,实际上只有一种方法可以做到这一点,那就是解析请求的查询的 AST。根据我的经验,graphql-parse-resolve-info
是减轻解析痛苦的最完整的解决方案。
我想这并不像您想象的那么普遍,因为我想大多数人都属于以下两组之一:
- Postgraphile、Hasaura、Prisma、Join Monster 等框架或库的用户,它们会为您进行此类优化(至少在数据库方面)。
- 不关心服务器端的过度获取并且只请求所有列而不考虑选择集的用户。
在后一种情况下,表示关联的字段被赋予了自己的解析器,因此除非实际请求,否则不会触发对数据库的后续调用。 Data Loader 然后用于帮助批处理所有这些对数据库的额外调用。对于最终调用其他数据源的字段也是如此,比如 REST API.
在这种特殊情况下,Data Loader 对您帮助不大。最好的方法是为 getUser
使用一个解析器,从数据库和 REST 端点获取用户详细信息。然后,您可以按照您已经计划的那样,根据请求的字段调整这些调用(或完全跳过它们)。这可能很麻烦,但会按预期工作。
此方法的替代方法是简单地获取所有内容,但使用缓存来减少对数据库和 REST 的调用次数 API。这样,您将每次获取完整的用户,但您将从内存中获取,除非缓存失效或过期。这会占用更多内存,并且缓存失效总是很棘手,但它可以显着简化您的解析器逻辑。
我的公司有一个面向服务的架构。因此,我的应用程序的 GraphQL 服务器必须调用其他服务来完成来自前端的数据请求。
让我们假设我的 GraphQL 模式定义了类型 User
。此类型的数据来自两个来源:
- 公开 REST 端点以获取用户的
username
、age
和friends
的用户帐户服务。 - 我的应用仅使用 SQL 数据库来存储仅与我的应用相关的
User
相关数据:favoriteFood
、favoriteSport
.
假设用户帐户服务的端点自动 returns username
和 age
,但您必须传递查询参数 friends=true
才能检索friends
数据,因为这是一项昂贵的操作。
鉴于该背景,以下查询在 getUser
解析器中提出了一些优化挑战:
query GetUser {
getUser {
username
favoriteFood
}
}
挑战 #1
当 getUser
解析器向用户帐户服务发出请求时,它如何知道是否还需要请求 friends
数据?
挑战 #2 当解析器查询我的应用程序的数据库以获取额外的用户数据时,它如何知道要从数据库中检索哪些字段?
对于这两个挑战,我能找到的唯一解决方案是通过解析器收到的第四个 info
参数检查解析器中的查询。这将允许它找出是否应该在对用户帐户服务的 REST 调用中请求 friends
,并且它将能够构建正确的 SELECT
查询以从我的应用程序的数据库中检索所需的数据.
这是正确的做法吗?这似乎是一个用例,GraphQL 实现必须一直 运行,因此我希望遇到一个被广泛接受的解决方案。但是,我没有找到很多解决这个问题的文章,似乎也没有广泛使用的 NPM 模块(graphql-parse-resolve-info is part of PostGraphile but only has ~12k weekly downloads, while graphql-fields 每周下载量约为 18.5k)。
因此,我担心我遗漏了一些有关如何完成此操作的基本知识。我是吗?或者检查 info
参数是否是解决这些优化挑战的正确方法?以防万一,我正在使用 Apollo Server。
如果您想根据请求的选择集修改您的解析器,实际上只有一种方法可以做到这一点,那就是解析请求的查询的 AST。根据我的经验,graphql-parse-resolve-info
是减轻解析痛苦的最完整的解决方案。
我想这并不像您想象的那么普遍,因为我想大多数人都属于以下两组之一:
- Postgraphile、Hasaura、Prisma、Join Monster 等框架或库的用户,它们会为您进行此类优化(至少在数据库方面)。
- 不关心服务器端的过度获取并且只请求所有列而不考虑选择集的用户。
在后一种情况下,表示关联的字段被赋予了自己的解析器,因此除非实际请求,否则不会触发对数据库的后续调用。 Data Loader 然后用于帮助批处理所有这些对数据库的额外调用。对于最终调用其他数据源的字段也是如此,比如 REST API.
在这种特殊情况下,Data Loader 对您帮助不大。最好的方法是为 getUser
使用一个解析器,从数据库和 REST 端点获取用户详细信息。然后,您可以按照您已经计划的那样,根据请求的字段调整这些调用(或完全跳过它们)。这可能很麻烦,但会按预期工作。
此方法的替代方法是简单地获取所有内容,但使用缓存来减少对数据库和 REST 的调用次数 API。这样,您将每次获取完整的用户,但您将从内存中获取,除非缓存失效或过期。这会占用更多内存,并且缓存失效总是很棘手,但它可以显着简化您的解析器逻辑。