Skip to content

Update migration guide #182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Writerside/doc.tree
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<toc-element topic="cache-control.md" />
<toc-element topic="garbage-collection.md" />
<toc-element topic="trimming.md" />
<toc-element topic="partial-cache-reads.md" />
<toc-element topic="compiler-plugin.md" />

</instance-profile>
24 changes: 19 additions & 5 deletions Writerside/topics/compiler-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ object cache {
}

```
This map is passed to the `TypePolicyCacheKeyGenerator` when calling [the `cache()` extension](#cache-extension-function).

Pass this map to the `TypePolicyCacheKeyGenerator` when configuring the cache:
If you need more control over the configuration, use the `normalizedCache()` extension and pass this map to the `TypePolicyCacheKeyGenerator`:

```kotlin
val apolloClient = ApolloClient.Builder()
Expand Down Expand Up @@ -107,7 +108,7 @@ can use the value of `__typename` as the type of the returned object, and from t

From that it can return `User:42` as the cache key for that record.

> If your schema has ids that are unique across the service, you can pass `CacheKey.Scope.SERVICE` to the `TypePolicyCacheKeyGenerator` constructor to save space in the cache.
> If your schema has ids that are unique across the service, you can pass `CacheKey.Scope.SERVICE` to the [`cache()`](#cache-extension-function) extension or `TypePolicyCacheKeyGenerator` constructor to save space in the cache.
>
> In that example the cache key would be `42` instead of `User:42`.

Expand Down Expand Up @@ -234,8 +235,21 @@ thus saving a network request if the record is already in the cache.
#### Unions and interfaces {id="field-policy-unions-and-interfaces"}

If a field returns a union or interface it is not possible to know which concrete type will be returned at runtime, and thus prefixing
the cache key with the correct type name is not possible. Avoiding a network call is not possible here.
the cache key with the correct type name is not possible. A network call can't be avoided here.

However, if your schema has ids that are unique across the service, you can pass `CacheKey.Scope.SERVICE` to the `FieldPolicyCacheResolver` constructor to skip the type name in the cache key.
Network call avoidance will still work in that case.
However, if your schema has ids that are unique across the service, you can pass `CacheKey.Scope.SERVICE` to the [`cache()`](#cache-extension-function) extension or `FieldPolicyCacheResolver` constructor to skip the type name in the cache key.
Network call avoidance will work in that case.


## `cache()` extension function

An `ApolloClient.Builder.cache()` extension function is generated by the compiler plugin, which configures the `CacheKeyGenerator`, `MetadataGenerator`, `CacheResolver`, and `RecordMerger` based
on the type policies, connection types, and [max ages](cache-control.md) configured in the schema:

```kotlin
val apolloClient = ApolloClient.Builder()
// ...
.cache(cacheFactory = /*...*/)
.build()
```
Optionally pass a `defaultMaxAge` (infinity by default) and `keyScope` (`CacheKey.Scope.TYPE` by default).
24 changes: 23 additions & 1 deletion Writerside/topics/migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Previously, write methods had 2 flavors:
- a `suspend` one that accepts a `publish` parameter to control whether changes should be published to watchers
- a non-suspend one (e.g. `writeOperationSync`) that doesn't publish changes

Now only the non-suspend ones exist and don't publish. Manually call `publish()` to notify watchers of changes.
Now only the suspend ones exist and don't publish. Manually call `publish()` to notify watchers of changes.

```kotlin
// Replace
Expand Down Expand Up @@ -163,6 +163,12 @@ val apolloClient = ApolloClient.Builder()

### Other changes

- All operations are now `suspend`.<br>
Note that they may **suspend** or **block** the thread depending on the underlying cache
implementation. For example, the SQL cache implementation on Android will **block** the thread while accessing the disk. As such,
these operations **must not** run on the main thread. You can enclose them in a `withContext` block with a `Dispatchers.IO` context to ensure
that they run on a background thread.

- `readFragment()` now returns a `ReadResult<D>` (it previously returned a `<D>`). This allows for surfacing metadata associated to the returned data, e.g. staleness.
- Records are now rooted per operation type (`QUERY_ROOT`, `MUTATION_ROOT`, `SUBSCRIPTION_ROOT`), when previously these were all at the same level, which could cause conflicts.

Expand Down Expand Up @@ -190,6 +196,9 @@ interface CacheResolver {

`resolveField` can also now return a `ResolvedValue` when metadata should be returned with the resolved value (e.g. staleness).

If you wish to use the [Cache Control](cache-control.md) feature, a [`CacheControlCacheResolver`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized.api/-cache-control-cache-resolver/index.html) should be used.
You can call the generated `cache()` extension on `ApolloClient.Builder` which will use it by default if max ages are configured in the schema.

### `TypePolicyCacheKeyGenerator`

You can now pass the type policies to the `TypePolicyCacheKeyGenerator` constructor, and it is recommended to do so.
Expand Down Expand Up @@ -248,3 +257,16 @@ For consistency, the `CacheKey` type is now used instead of `String` in more API

- `ApolloCacheHeaders.EVICT_AFTER_READ` is removed. Manually call `ApolloStore.remove()` when needed instead.
- `NormalizedCache.remove(pattern: String)` is removed. Please open an issue if you need this feature back.

## Other changes {id="other-changes-2"}

An `ApolloClient.Builder.cache()` extension function is generated by the compiler plugin, which configures the `CacheKeyGenerator`, `MetadataGenerator`, `CacheResolver`, and `RecordMerger` based
on the type policies, connection types, and max ages configured in the schema:

```kotlin
val apolloClient = ApolloClient.Builder()
// ...
.cache(cacheFactory = /*...*/)
.build()
```
Optionally pass a `defaultMaxAge` (infinity by default) and `keyScope` (`TYPE` by default).
54 changes: 54 additions & 0 deletions Writerside/topics/partial-cache-reads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Partial cache reads

The cache supports partial cache reads, in a similar way to how GraphQL supports partial responses.
This means that if some fields are missing from the cache, the cache will return the available data along with any errors for the missing fields.

## With `ApolloStore`

The [`ApolloStore.readOperation()`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/-apollo-store/read-operation.html)
API returns an `ApolloResponse<D>` that can contain partial `data` and non-empty `errors` for any missing (or stale) fields in the cache.

> `ApolloResponse.cacheInfo.isCacheHit` will be false when any field is missing.
> `ApolloResponse.cacheInfo.isStale` will be true when any field is stale.

If a cache miss exception is preferred, you can use the `ApolloResponse<D>.errorsAsException()` extension function that returns
a response with an exception if there are any errors.

## With `ApolloClient`

When executing operations, the built-in fetch policies ([`FetchPolicy.CacheFirst`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/-fetch-policy/-cache-first/index.html?query=CacheFirst),
[`FetchPolicy.CacheOnly`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/-fetch-policy/-cache-only/index.html),
etc.) will treat cache misses as exceptions (partial results are disabled).

To benefit from partial cache reads, implement your own fetch policy interceptor as shown in this example:

```kotlin
object PartialCacheOnlyInterceptor : ApolloInterceptor {
override fun <D : Operation.Data> intercept(
request: ApolloRequest<D>,
chain: ApolloInterceptorChain
): Flow<ApolloResponse<D>> {
return chain.proceed(
request = request
.newBuilder()
// Controls where to read the data from (cache or network)
.fetchFromCache(true)
.build()
)
}
}

// ...

val apolloClient = ApolloClient.Builder()
/*...*/
.serverUrl("https://example.com/graphql")
.fetchPolicyInterceptor(PartialCacheOnlyInterceptor)
.build()
```

## Error stored in the cache

Errors from the server are stored in the cache, and will be returned when reading it.

By default, errors don't replace existing data in the cache. You can change this behavior with [`errorsReplaceCachedValues(true)`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/errors-replace-cached-values.html?query=fun%20%3CT%3E%20MutableExecutionOptions%3CT%3E.errorsReplaceCachedValues(errorsReplaceCachedValues:%20Boolean):%20T).