加入我们,从10月8日至10日在纽约市,了解关于GraphQL联邦和API平台工程最新技巧、趋势和新闻。加入我们参加2024年纽约市的GraphQL峰会
文档
免费开始

Apollo Kotlin 中的程序化缓存 ID


在以下情况下声明式缓存 ID 不适用于您的用例时,您可以为 程序化 生成您标准化缓存中的对象类型缓存 ID。

您可以从以下两个来源之一生成给定 ' 的缓存在 ID:

来源描述
从响应对象的字段(例如,Book.id)CacheKeyGenerator这会在 网络请求之后 发生,对于将查询结果与现有缓存数据合并至关重要。这是最常见的情况。
从GraphQL操作的参数(例如,author(id: "au456"))中缓存键解析器这会发生在一个网络请求之前,使你能够在缓存中已经存在所有请求的数据的情况下避免网络往返。这是一个可选的优化,可以避免一些缓存缺失。

提供了一个类,可以从每个这些来源生成缓存ID。

CacheKeyGenerator

CacheKeyGenerator允许您从对象的值中生成自定义缓存ID。以下是一个基本示例,从每个对象类型的id字段生成其缓存ID:

val cacheKeyGenerator = object : CacheKeyGenerator {
override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {
// Generate the cache ID based on the object's id field
return CacheKey(obj["id"] as String)
}
}

要使用您的自定义CacheKeyGenerator,将其包含在您的缓存初始化代码中,如下所示:

val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.normalizedCache(
normalizedCacheFactory = cacheFactory,
cacheKeyGenerator = cacheKeyGenerator,
)
.build()

您可以从context对象中获取当前对象的typename并将其包含在生成的ID中,如下所示:

val cacheKeyGenerator = object : CacheKeyGenerator {
override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {
val typename = context.field.type.rawType().name
val id = obj["id"] as String
return CacheKey(typename, id)
}
}

您还可以使用当前对象的typename来针对不同的对象类型使用不同的缓存ID生成逻辑。

请注意,为了使缓存ID生成有效,您的操作的必须返回自定义代码所依赖的任何字段(如上例中的id)。如果查询没有返回所需字段,则缓存ID将不一致,导致数据重复。此外,对于接口和联合类型,使用context.field.type.rawType().name属性会返回与该类型在规范中声明时相同的typename,而不是响应中接收到的类型的运行时值。相反,查询会更安全。为了确保所有操作都包含__typename,设置以下addTypename gradle配置:

apollo {
service("service") {
addTypename.set("always")
}
}

缓存键解析器

The CacheKeyResolver 类允许您从一个字段's 中生成自定义缓存 ID。这个基本示例从现有id 生成每种对象类型的缓存 ID:如果该参数存在。

val cacheKeyResolver = object: CacheKeyResolver() {
override fun cacheKeyForField(field: CompiledField, variables: Executable.Variables): CacheKey? {
// [field] contains compile-time information about what type of object is being resolved.
// Even though we call rawType() here, we're guaranteed that the type is a composite type and not a list
val typename = field.type.rawType().name
// argumentValue returns the runtime value of the "id" argument
// from either the variables or as a literal value
val id = field.argumentValue("id", variables).getOrNull()
if (id is String) {
// This field has an id argument, so we can use it to compute a cache ID
return CacheKey(typename, id)
}
// Return null to use the default handling
return null
}
}

要使用您自己的 CacheKeyResolver,请将其包含在您的缓存初始化代码中,如下所示:

val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.normalizedCache(
normalizedCacheFactory = cacheFactory,
cacheKeyGenerator = cacheKeyGenerator,
cacheResolver = cacheKeyResolver
)
.build()

请注意使用自定义 CacheKeyResolver的以下事项:

  • 对于返回复合类型的操作中的每个字段,都会调用 cacheKeyForField 函数,因此如果您不想处理特定字段,则非常重要地返回 null
  • 对于返回复合类型列表的字段,不调用函数。

处理列表

让我们假设我们有一个这样的查询:

query GetBooks($ids: [String!]!) {
books(ids: $ids) {
id
title
}
}

要使缓存查找 ids 列表中的所有书籍,我们需要覆盖 listOfCacheKeysForFieldCacheKeyResolver:

override fun listOfCacheKeysForField(field: CompiledField, variables: Executable.Variables): List<CacheKey?>? {
// Note that the field *can* be a list type here
val typename = field.type.rawType().name
// argumentValue returns the runtime value of the "id" argument
// from either the variables or as a literal value
val ids = field.argumentValue("ids", variables).getOrNull()
if (ids is List<*>) {
// This field has an id argument, so we can use it to compute a cache ID
return ids.map { CacheKey(typename, it as String) }
}
// Return null to use the default handling
return null
}

为了简单起见,仅支持一个列表级别。要支持更多嵌套列表,可以实现 CacheResolverCacheResolverCacheKeyResolver 的一般化,可以返回缓存中的任何值,甚至 值:

val cacheResolver = object: CacheResolver {
override fun resolveField(
field: CompiledField,
variables: Executable.Variables,
parent: Map<String, @JvmSuppressWildcards Any?>,
parentId: String,
): Any? {
var type = field.type
var listDepth = 0
while (true) {
when (type) {
is CompiledNotNullType -> type = type.ofType
is CompiledListType -> {
listDepth++
type = type.ofType
}
else -> break
}
}
// Now type points to the leaf type and lestDepth is the nesting of lists required
// Return a kotlin value for this field
// No type checking is done here, so it must match the expected GraphQL type
if (listDepth == 2) {
return listOf(listOf("0", "1"))
}
// CacheResolver must always call DefaultCacheResolver last or all fields will be null else
return DefaultCacheResolver.resolveField(field, variables, parent, parentId)
}
}
上一页
声明式缓存 ID
下一页
监视缓存数据
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,即Apollo GraphQL.

隐私策略

公司