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

Apollo Kotlin中的规范化缓存


提供了两个内置的规范化缓存来存储和重用操作的

  • 一个内存缓存MemoryCache
  • 一个SQLite支持的缓存SqlNormalizedCache

您可以在应用程序中使用其中之一(或两个!)以提高其大多数操作的响应能力。

要开始使用更粗略的缓存策略,速度设置更快,请查看HTTP缓存

什么是规范化缓存?

在一个 中,规范化缓存会将每个 GraphQL 响应拆分成包含的各个单独对象。然后,每个对象根据其 缓存 ID 作为 独立的条目 进行缓存。这意味着如果多个响应中包含相同的对象,该对象可以 deduplicated 成单个缓存条目。这降低了整体缓存大小,并有助于保持缓存数据的连贯性和新鲜度。

您还可以将规范化缓存作为 UI 的单一事实来源,使其能够响应缓存中的更改。要了解更多关于 过程的信息, 请参阅这篇博客文章

规范化响应

看看这个示例

query GetFavoriteBook {
favoriteBook { # Book object
id
title
author { # Author object
id
name
}
}
}

这个 查询 返回一个 Book 对象,该对象反过来又包含一个 Author 对象。来自 的示例响应如下:

{
"favoriteBook": {
"id": "bk123",
"title": "Les Guerriers du silence",
"author": {
"id": "au456",
"name": "Pierre Bordage"
}
}
}

规范化缓存默认不会直接存储此响应。相反,它会将其拆分成以下条目:

缓存
"favoriteBook": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{favoriteBook.author}"}
"favoriteBook.author": {"id": "au456", "name": "Pierre Bordage"}
"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference{favoriteBook}"}

注意

这些默认生成的缓存ID(favoriteBookfavoriteBook.author)对于数据去重来说是不理想的。请参阅指定缓存ID

  • 注意,author字段现在包含字符串ApolloCacheReference{favoriteBook.author}。这是对Author缓存条目的引用。
  • 注意QUERY_ROOT条目也是始终存在的,如果你至少缓存了一个查询的结果。该条目包含对每个包含在查询中的顶层字段(例如,favoriteBook)的引用。

提供的缓存

内存缓存

Apollo Kotlin的MemoryCache是一个规范化的、内存缓存,用于存储GraphQL操作的对象。要使用它,首先添加apollo-normalized-cache artifact到你的build.gradle[.kts]

build.gradle[.kts]
dependencies {
implementation("com.apollographql.apollo:apollo-normalized-cache:4.0.0")
}

然后,将其缓存包含在您的 ApolloClient 初始化中,如下所示:

// Creates a 10MB MemoryCacheFactory
val cacheFactory = MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024)
// Build the ApolloClient
val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
// normalizedCache() is an extension function on ApolloClient.Builder
.normalizedCache(cacheFactory)
.build()

由于标准化缓存是可选的,normalizedCache() 是一个定义在 apollo-normalized-cache 元件中的 ApolloClient.Builder() 扩展函数。它以 NormalizedCacheFactory 作为参数,以便在需要时在主线程之外创建缓存。

一个 MemoryCache 是一个最近最少使用(LRU)缓存。它根据以下条件在内存中保持条目:

名称描述
maxSizeBytes缓存的最高大小(以字节为单位)。
expireAfterMillis现有缓存条目超时的超时时间(以毫秒为单位)。默认情况下,没有超时。

当您的应用程序停止时,MemoryCache 中的数据将永久丢失。如果需要持久化数据,可以使用 SQLite缓存

SQLite 缓存

Apollo Kotlin 的 SQLite 缓存使用 SQLDelight 来存储数据。您可以使用它来跨应用重启持久化数据,或者如果您的缓存数据太大而无法放入内存中。

要启用 SQLite 缓存支持,将 apollo-normalized-cache-sqlite 依赖项添加到您的项目的 build.gradle 文件中:

build.gradle.kts
dependencies {
implementation("com.apollographql.apollo:apollo-normalized-cache-sqlite:4.0.0")
}

然后根据您的平台目标将 SQLite 缓存包含在您的 ApolloClient 初始化中(不同平台使用不同的驱动器):

// Android
val sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("apollo.db")
// JVM
val sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("jdbc:sqlite:apollo.db")
// iOS
val sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("apollo.db")
// Build the ApolloClient
val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.normalizedCache(sqlNormalizedCacheFactory)
.build()

您可以使用 SQLite 缓存的方式与使用 MemoryCache 的方式相同。

链式缓存

为了充分利用标准化缓存,您可以将 MemoryCacheFactorySqlNormalizedCacheFactory 链起来:

val memoryFirstThenSqlCacheFactory = MemoryCacheFactory(10 * 1024 * 1024)
.chain(SqlNormalizedCacheFactory(context, "db_name"))

每当 Apollo Kotlin 尝试读取缓存数据时,它会按顺序检查每个链式缓存,直到找到匹配项。然后,它会立即返回该缓存数据,而不读取任何其他缓存。

每当 Apollo Kotlin 向缓存写入数据时,这些写入操作将向下传播到链中的所有缓存中。

设置获取策略

在您将标准化缓存添加到您的 ApolloClient 初始化之后,Apollo Kotlin 会自动使用 FetchPolicy.CacheFirst 作为默认(客户端范围) 获取策略 对所有查询。要更改默认值,您可以在客户端构建器上调用 fetchPolicy

val apolloClient = ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.fetchPolicy(FetchPolicy.NetworkOnly)
.build()

您还可以针对特定查询自定义缓存的使用方式,即设置该查询的获取策略。

以下代码片段显示了如何设置所有可用的获取策略及其行为:

val response = apolloClient.query(query)
// (Default) Check the cache, then only use the network if data isn't present
.fetchPolicy(FetchPolicy.CacheFirst)
// Check the cache and never use the network, even if data isn't present
.fetchPolicy(FetchPolicy.CacheOnly)
// Always use the network, then check the cache if network fails
.fetchPolicy(FetchPolicy.NetworkFirst)
// Always use the network and never check the cache, even if network fails
.fetchPolicy(FetchPolicy.NetworkOnly)
// Check the cache and also use the network
.fetchPolicy(FetchPolicy.CacheAndNetwork)
// Execute the query and collect the responses
.toFlow().collect { response ->
// ...
}

请注意,缓存未命中产生一个非空的响应,其中包含.exception,这意味着这些策略中的一些可以产生多个值:

  • CacheFirst可以产生1或2个值
  • NetworkFirst可以产生1或2个值
  • CacheAndNetwork将产生2个值

注意:.execute()会过滤掉缓存或网络错误,以返回单个成功响应,然而由于CacheAndNetwork可以产生2个成功响应,当使用该策略时,你应该使用.toFlow()

normalizedCache(NormalizedCacheFactory)一样,fetchPolicy(FetchPolicy)ApolloClient.Builder()的扩展函数,因此你需要将apollo-normalized-cache添加到你的类路径中才能使它生效。

由于规范化缓存可以去除重复的数据,它可以让你对缓存更改做出反应。你通过监视器来这样做,监视器会监听缓存更改。了解有关查询监视器的更多信息。

指定缓存ID

默认情况下,Apollo Kotlin使用对象GraphQL字段的路径作为其缓存ID。例如,回想以下查询及其之前的结果缓存条目:

query GetFavoriteBook {
favoriteBook { # Book object
id
title
author { # Author object
id
name
}
}
}
缓存
"favoriteBook": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{favoriteBook.author}"}
"favoriteBook.author": {"id": "au456", "name": "Pierre Bordage"}
"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference{favoriteBook}"}

现在,如果我们执行一个不同的查询以获取具有相同 Author id au456对象会怎样?

query AuthorById($id: String!) {
author(id: $id) {
id
name
}
}
}

执行此查询后,我们的缓存看起来像这样:

缓存
"favoriteBook": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{favoriteBook.author}"}
"favoriteBook.author": {"id": "au456", "name": "Pierre Bordage"}
"author(\"id\": \"au456\")": {"id": "au456", "name": "Pierre Bordage"}
"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference{favoriteBook}", "author(\"id\": \"au456\")": "ApolloCacheReference{author(\"id\": \"au456\")}"}

现在,我们正在缓存两个相同Author对象的条目!这有几个不好的地方:

  • 它占用更多的空间。
  • 修改这些对象中的一个不会通知另一个对象的监视器。

我们想通过确保它们在写入时分配相同的缓存ID来去除这些条目,从而得到一个看起来更像这样的缓存:

缓存
"Book:bk123": {"id": "bk123", "title": "Les guerriers du silence", "author": "ApolloCacheReference{Author:au456}"}
"Author:au456": {"id": "au456", "name": "Pierre Bordage"}
"QUERY_ROOT": {"favoriteBook": "ApolloCacheReference(Book:bk123)", "author(\"id\": \"au456\")": "ApolloCacheReference{Author:au456}"}

幸运的是,我们所有的对象都有一个id字段,我们可以用它来达到这个目的。如果id在你的中对所有对象都是唯一的,你可以直接使用它的值作为缓存ID。如果它对每个对象类型都是唯一的,你可以在这个值前加上类型名称(如上所示)。

方法

有两种方法可以指定对象的缓存ID

  • 声明性(推荐)。你可以指定模式,告诉代码生成器在哪些字段中查找ID,并确保在编译时所有这些字段都在操作中请求,以确保所有对象都可以被识别。声明性ID还为每个ID加上类型名称前缀,以确保全局唯一性。
  • 程序化.您可以实现自定义API,为对象提供缓存ID。因为您可以执行任意代码,所以此解决方案更加灵活,但也更容易出错,并且要求您的操作请求

清除缓存

调用 apolloClient.apolloStore.clearAll() 以清除所有条目的缓存。注意,如果未配置缓存,调用 apolloClient.apolloStore 将抛出异常。

上一页
简介
下一页
声明性缓存ID
评价文章评价在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,即Apollo GraphQL。

隐私政策

公司