加入我们,从10月8日至10日,在新رویان یعنی یونان نیویورک سity,了解关于 GraphQL 联盟和 API 平台工程的最新技巧、趋势和新闻。加入我们参加2024年在纽约市举行的 GraphQL 峰会
文档
免费开始
您正在查看该软件以前版本的文档。 切换到最新稳定版本。

Apollo Kotlin 中的程序化缓存 ID


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

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

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

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

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://...")
.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 = obj["__typename"] as String
val id = obj["id"] as String
return CacheKey(typename, id)
}
}

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

请注意,为了让缓存ID生成工作,您的 必须返回自定义代码所依赖的任何(如上例中的id)。如果一个没有返回所需的字段,则缓存ID将不一致,导致数据重复。此外,使用context.field.type.leafType().name可以得到联合的typename,而不是预期在响应中收到的Union的运行时值。相反__typename更安全。为了确保所有操作都包含__typename,请设置adTypename gradle 配置:

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

CacheKeyResolver

缓存键解析器类(CacheKeyResolver)允许您从一个字段的参数中生成自定义缓存键。以下基本示例从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 leafType() here, we're guaranteed that the type is a composite type and not a list
val typename = field.type.leafType().name
// resolveArgument returns the runtime value of the "id" argument
// from either the variables or as a literal value
val id = field.resolveArgument("id", variables)
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://...")
.normalizedCache(
normalizedCacheFactory = cacheFactory,
cacheKeyGenerator = cacheKeyGenerator,
cacheResolver = cacheKeyResolver
)
.build()

使用自定义CacheKeyResolver时请注意以下几点:

  • 当您的操作返回组合类型时,cacheKeyForField函数会对您的每个字段进行调用,因此如果您不想处理某个特定字段,请返回null
  • 对于返回组合类型列表的字段,函数不会调用。见下文。

处理列表

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

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

为了让缓存查找ids列表中的所有图书,我们需要在CacheKeyResolver中覆盖listOfCacheKeysForField方法:

override fun listOfCacheKeysForField(field: CompiledField, variables: Executable.Variables): List<CacheKey?>? {
// Note that the field *can* be a list type here
val typename = field.type.leafType().name
// resolveArgument returns the runtime value of the "id" argument
// from either the variables or as a literal value
val ids = field.resolveArgument("ids", variables)
if (ids is List<*>) {
// This field has an id argument, so we can use it to compute a cache key
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)
}
}
上一页
声明式缓存键
下一页
监视缓存数据
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,代号Apollo GraphQL。

隐私政策

公司