如果您最近开始使用 GraphQL,或者审查了它的优缺点,您无疑听说过诸如“GraphQL 不支持缓存”或“GraphQL 不关心缓存”之类的话。 对于大多数人来说,这是一个大问题。
官方的 GraphQL 文档 提到了缓存技术,因此,很明显,它背后的团队确实关心缓存及其性能优势。
关于 GraphQL 与缓存不兼容的看法是我想在这篇文章中解决的问题。 我想逐步介绍不同的缓存技术以及如何利用缓存来处理 GraphQL 查询。
让 GraphQL 自动缓存
考虑以下查询来获取帖子及其作者
query getPost {
post(slug: "working-with-graphql-caching") {
id
title
author {
id
name
avatar
}
}
}
使 GraphQL 自动缓存起作用的“诀窍”是所有 GraphQL API 都会公开的__typename
元字段。
顾名思义,__typename
返回对象类型的名称。 甚至可以手动将此字段添加到现有查询中 - 大多数时候,GraphQL 客户端或 CDN 会为您完成此操作。 urql 就是这样的一个 GraphQL 客户端。 服务器可能接收类似这样的查询
query getPost {
post(slug: "working-with-graphql-caching") {
__typename
id
title
author {
__typename
id
name
}
}
}
包含 __typename
的响应可能看起来有点像这样
{
data: {
__typename: "Post",
id: 5,
title: "Working with GraphQL Caching",
author: {
__typename: "User",
id: 1,
name: "Jamie Barton"
}
}
}
__typename
是 GraphQL 缓存拼图中的关键部分,因为我们现在可以缓存此结果,并且知道它包含帖子 ID 5 和用户 ID 1。
然后有一些库,例如 Apollo 和 Relay,它们也具有一些我们可以用于自动缓存的内置缓存。 由于它们已经知道缓存中有什么,因此它们可以委托给缓存而不是远程 API 来获取客户端在查询中请求的内容。
在发生更改时自动使缓存失效
假设帖子的作者使用editPost
变更来编辑帖子的标题
mutation {
editPost(input: { id: 5, title: "Working with GraphQL Caching" }) {
id
title
}
}
由于 GraphQL 客户端会自动添加 __typename
字段,因此此变更的结果会立即告知缓存帖子 ID 5 已更改,并且任何包含该帖子的缓存查询结果都需要失效
{
data: {
__typename: "Post",
id: 5,
title: "Working with GraphQL Caching"
}
}
下次用户发送相同的查询时,该查询会从源头获取新数据,而不是从缓存中提供陈旧的结果。 太神奇了!
规范化的 GraphQL 缓存
许多 GraphQL 客户端不会缓存完整的查询结果。
相反,它们会将缓存数据规范化为两个数据结构;一个将每个对象与其数据关联起来(例如 帖子 #5: { … }
、用户 #1: { … }
等);另一个将每个查询与其包含的对象关联起来(例如 获取帖子: { 帖子 #5, 用户 #1}
等)。
请查看 urql 关于规范化缓存的文档 或 Apollo 的“深入了解缓存规范化” 以获取特定示例和用例。
GraphQL 缓存的边缘情况
GraphQL 缓存无法自动处理的一个主要边缘情况是向列表中添加项目。 因此,如果创建帖子
变更通过缓存,它不知道要将该项目添加到哪个特定列表中。
最简单的“解决方法”是在变更中查询父类型(如果存在)。 例如,在下面的查询中,我们查询了帖子
上的社区
关系
query getPost {
post(slug: "working-with-graphql-caching") {
id
title
author {
id
name
avatar
}
# Also query the community of the post
community {
id
name
}
}
}
然后我们也可以从创建帖子
变更中查询该社区,并使任何包含该社区的缓存查询结果失效
mutation createPost {
createPost(input: { ... }) {
id
title
# Also query and thus invalidate the community of the post
community {
id
name
}
}
}
虽然不完美,但类型化的模式和__typename
元字段是使 GraphQL API 成为缓存理想选择的关键。
您现在可能在想,所有这些都是一种变通方法,GraphQL 仍然不支持传统的缓存。 你并没有错。 由于 GraphQL 通过 POST
请求运行,您需要将它卸载到应用程序客户端,并使用上述“技巧”来利用 GraphQL 与现代浏览器缓存的优势。
也就是说,这种操作并不总是可行,而且对于管理客户端上的缓存来说也没有太多意义。 GraphQL 客户端让您 手动更新缓存 以处理更棘手的情况,但像 GraphCDN 这样的服务提供了“类似服务器”的缓存体验,它还公开了一个 手动清除 API,使您可以执行类似的操作来实现更好的缓存控制
# Purge all occurrences of a specific object
mutation {
purgeUser(id: [5])
}
# Purge by query name
mutation {
_purgeQuery(queries: [listUsers, listPosts])
}
# Purge all occurrences of a type
mutation {
purgeUser
}
现在,无论您在何处使用 GraphCDN 端点,都不需要再在所有客户端逻辑(移动设备、网络等)中重新实现缓存策略。 边缘缓存使您的 API 感觉超级快,并通过在您的用户之间共享缓存来减少负载,并将缓存与每个用户的客户端隔离开。
最近在一个项目中使用了 GraphCDN,它帮我处理了在客户端或服务器上配置缓存的问题,让我能够继续进行我的项目。 例如,我可以将我的端点替换为 GraphCDN 并免费获得查询复杂度(即将推出)、分析等。
那么,GraphQL 是否关心缓存? 它当然关心! 它不仅提供了内置于其中的某些自动缓存方法,而且许多 GraphQL 库还提供了其他方法来执行缓存甚至管理缓存。
希望这篇文章能让你对 GraphQL 缓存故事有所了解,以及如何在客户端实现它,以及如何利用 CDN 来为你完成所有工作。 我的目标不是让你在所有项目中都使用 GraphQL,但如果你在查询语言之间进行选择,而缓存是一个大问题,那么请知道 GraphQL 完全能够胜任这项任务。
如果能说明一下这是关于客户端缓存的会比较好。 我认为服务器端缓存才是 GraphQL 的真正挑战,因为 GraphQL 可能会非常消耗服务器和数据库资源。
100% - 我会建议编辑一下以突出这一点。
还值得一提的是,像 Envelop 这样的开源项目以及像 GraphCDN 这样的服务可以轻松管理服务器端缓存。
https://www.envelop.dev/docs/guides/adding-a-graphql-response-cache
好的,那么代理缓存呢? 看起来作者并不知道 HTTP 缓存有多重要。 缓存和“缓存”是不一样的。 而且,不,GraphQL 不关心缓存。 解决方案应该是修复 GraphQL 以尊重 HTTP 惯例,而不是制造新的问题。
您好,GraphQL 对缓存的处理方式与您习惯的方式略有不同,这当然也是它引起争议的原因。 这篇文章应该可以帮助您了解 GraphQL 如何使用客户端库进行缓存的一些机制。
PS。 我知道 HTTP 缓存有多重要 :) 这就是我提供 GraphCDN 这样的服务器端选项的原因。 虽然它并不完美(GraphQL 缓存),但这是我们在实现 GraphQL 时可用的方法。