From 2f3e5782e72a37cfe2a689c62dc73f0ac4b9ef06 Mon Sep 17 00:00:00 2001 From: BoD Date: Tue, 3 Jun 2025 13:54:19 +0200 Subject: [PATCH 1/3] Improve welcome/readme and add section about trimming --- README.md | 12 +++++++++- Writerside/doc.tree | 1 + Writerside/topics/migration-guide.md | 13 ++++++---- Writerside/topics/trimming.md | 16 +++++++++++++ Writerside/topics/welcome.md | 36 ++++++++++++++++++++++++---- 5 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 Writerside/topics/trimming.md diff --git a/README.md b/README.md index ccd14625..3a06ce6a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,17 @@ ## 🚀 Apollo Kotlin Normalized Cache -This repository hosts [Apollo Kotlin](https://github.com/apollographql/apollo-kotlin)'s new normalized cache, aiming to replace the main repository version. +This repository hosts [Apollo Kotlin](https://github.com/apollographql/apollo-kotlin)'s new normalized cache, aiming to replace the main repository's version. + +Compared to the previous version, this new normalized cache brings: + +- [Pagination support](https://apollographql.github.io/apollo-kotlin-normalized-cache/pagination-home.html) +- [Cache control](https://apollographql.github.io/apollo-kotlin-normalized-cache/cache-control.html) (a.k.a. Time to live or expiration) +- [Garbage collection](https://apollographql.github.io/apollo-kotlin-normalized-cache/garbage-collection.html), and [trimming](https://apollographql.github.io/apollo-kotlin-normalized-cache/trimming.html) +- Partial results from the cache +- API simplifications +- Key scope support +- SQL cache improved performance ## 📚 Documentation diff --git a/Writerside/doc.tree b/Writerside/doc.tree index b8d91ebf..99652fa4 100644 --- a/Writerside/doc.tree +++ b/Writerside/doc.tree @@ -18,5 +18,6 @@ + diff --git a/Writerside/topics/migration-guide.md b/Writerside/topics/migration-guide.md index f04a9590..f8469996 100644 --- a/Writerside/topics/migration-guide.md +++ b/Writerside/topics/migration-guide.md @@ -1,9 +1,11 @@ # Migration guide -The Apollo Kotlin Normalized Cache used to be part of the [Apollo Kotlin main repository](https://github.com/apollographql/apollo-kotlin). -It is now hosted in this dedicated repository and published at its own cadence and versioning scheme. +The Normalized Cache is now hosted in this dedicated repository and published at its own cadence and versioning scheme. -This guide highlights the main differences between this library and the "classic" version, and how to migrate to it. +The Normalized Cache in the [Apollo Kotlin main repository](https://github.com/apollographql/apollo-kotlin) will not receive new features - they +are added here instead. In the future, the main repository Normalized Cache will be deprecated and then removed. + +This guide highlights the main differences between this library and the main repository version, and how to migrate from it. ## Artifacts and packages @@ -38,8 +40,6 @@ import com.apollographql.apollo.cache.normalized.api.MemoryCacheFactory import com.apollographql.cache.normalized.memory.MemoryCacheFactory ``` -In most cases, this will be enough to migrate your project, but there were a few renames and API breaking changes. Read on for more details. - ## Compiler plugin Configure the compiler plugin in your `build.gradle.kts` file: @@ -57,6 +57,9 @@ apollo { } ``` +In most cases, updating the coordinates/imports and adding the compiler plugin will be enough to migrate your project. +But there were also a few renames and API breaking changes - read on for more details. + ## Database schema The SQLite cache now uses a different schema. diff --git a/Writerside/topics/trimming.md b/Writerside/topics/trimming.md new file mode 100644 index 00000000..1c5d4618 --- /dev/null +++ b/Writerside/topics/trimming.md @@ -0,0 +1,16 @@ +# Trimming the cache + +By default, if you don't use [cache control](cache-control.md) and [garbage collection](garbage-collection.md), +the cache will grow indefinitely as more data is written to it. + +To prevent this, a few APIs are available: + +- [`ApolloStore.clearAll()`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/-apollo-store/index.html#-1013497887%2FFunctions%2F-1172623753): + clear the entire cache. +- [`ApolloStore.remove()`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/-apollo-store/index.html#-1351099158%2FFunctions%2F-1172623753): + remove specific records. +- [`ApolloStore.removeOperation()`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/remove-operation.html?query=fun%20%3CD%20:%20Operation.Data%3E%20ApolloStore.removeOperation(operation:%20Operation%3CD%3E,%20data:%20D,%20cacheHeaders:%20CacheHeaders%20=%20CacheHeaders.NONE):%20Set%3CString%3E) and [ + `ApolloStore.removeFragment()`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/remove-fragment.html?query=fun%20%3CD%20:%20Fragment.Data%3E%20ApolloStore.removeFragment(fragment:%20Fragment%3CD%3E,%20cacheKey:%20CacheKey,%20data:%20D,%20cacheHeaders:%20CacheHeaders%20=%20CacheHeaders.NONE):%20Set%3CString%3E): + remove the records associated to specific operations or fragments. +- [`ApolloStore.trim()`](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized/-cache-manager/trim.html): + trim the cache by a specified amount (by default 10%) if it exceeds a certain size. The oldest (according to their updated date) records are removed. diff --git a/Writerside/topics/welcome.md b/Writerside/topics/welcome.md index 4f04ac1b..bc780f2f 100644 --- a/Writerside/topics/welcome.md +++ b/Writerside/topics/welcome.md @@ -4,8 +4,20 @@ This is [Apollo Kotlin](https://github.com/apollographql/apollo-kotlin)'s Normal For an introduction please read the Normalized Cache [documentation](https://www.apollographql.com/docs/kotlin/caching/normalized-cache). -Note: the Normalized Cache used to be part of the [Apollo Kotlin main repository](https://github.com/apollographql/apollo-kotlin). -It is now hosted in this dedicated repository and published at its own cadence and versioning scheme. +The Normalized Cache is now hosted in this dedicated repository and published at its own cadence and versioning scheme. + +Compared to the previous version, this library brings: + +- [Pagination support](pagination-home.md) +- [](cache-control.md) (a.k.a. Time to live or expiration) +- [](garbage-collection.md), and [trimming](trimming.md) +- Partial results from the cache +- API simplifications +- Key scope support +- SQL cache improved performance + +The Normalized Cache in the [Apollo Kotlin main repository](https://github.com/apollographql/apollo-kotlin) will not receive new features - they +are added here instead. In the future, the main repository Normalized Cache will be deprecated and then removed. ## Use in your project @@ -14,7 +26,7 @@ It is now hosted in this dedicated repository and published at its own cadence a {style="warning"} -Add the dependencies to your project. +1. Add the dependencies to your project ```kotlin // build.gradle.kts @@ -27,4 +39,20 @@ dependencies { } ``` -If you were using the classic Normalized Cache before, please consult the [migration guide](migration-guide.md). +2. Configure the compiler plugin + +```kotlin +// build.gradle.kts +apollo { + service("service") { + // ... + + // Add this + plugin("com.apollographql.cache:normalized-cache-apollo-compiler-plugin:%latest_version%") { + argument("packageName", packageName.get()) + } + } +} +``` + +If you're already using the main repository Normalized Cache, please consult the [migration guide](migration-guide.md). From d702bcc9d3505d165b26c597ceb5369d5755d7da Mon Sep 17 00:00:00 2001 From: BoD Date: Tue, 3 Jun 2025 18:50:05 +0200 Subject: [PATCH 2/3] Add a Compiler plugin doc --- Writerside/doc.tree | 1 + Writerside/topics/compiler-plugin.md | 241 +++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 Writerside/topics/compiler-plugin.md diff --git a/Writerside/doc.tree b/Writerside/doc.tree index 99652fa4..088a405c 100644 --- a/Writerside/doc.tree +++ b/Writerside/doc.tree @@ -19,5 +19,6 @@ + diff --git a/Writerside/topics/compiler-plugin.md b/Writerside/topics/compiler-plugin.md new file mode 100644 index 00000000..0184c588 --- /dev/null +++ b/Writerside/topics/compiler-plugin.md @@ -0,0 +1,241 @@ +# Compiler plugin + +When setting up the Normalized Cache in your project, you need to configure the compiler plugin: + +```kotlin +// build.gradle.kts +apollo { + service("service") { + // ... + + // Add this + plugin("com.apollographql.cache:normalized-cache-apollo-compiler-plugin:%latest_version%") { + argument("packageName", packageName.get()) + } + } +} +``` + +This plugin generates some code to support the Normalized Cache features, such as declarative cache IDs, pagination and cache control. + +## Declarative cache IDs (`@typePolicy`) + +You can refer to the [declarative cache IDs documentation](https://www.apollographql.com/docs/kotlin/caching/declarative-ids) +for a general overview of this feature. + +Here are some additional details of what the compiler plugin does to support it. + +Let's consider this schema for example: + +```graphql +# schema.graphqls +type Query { + user(id: ID!): User +} + +type User { + id: ID! + email: String! + name: String! +} +``` + +```graphql +# extra.graphqls +extend type User @typePolicy(keyFields: "id") +``` + +### Generation of `typePolicies` + +A map of type names to `TypePolicy` instances is generated in a `Cache` object. + +In the example above, the generated code will look like this: + +```kotlin +object cache { + val typePolicies: Map = mapOf( + "User" to TypePolicy(keyFields = setOf("id")) + ) +} + +``` + +Pass this map to the `TypePolicyCacheKeyGenerator` when configuring the cache: + +```kotlin +val apolloClient = ApolloClient.Builder() + // ... + .normalizedCache( + // ... + cacheKeyGenerator = TypePolicyCacheKeyGenerator(Cache.typePolicies) + ) + .build() +``` + +### Addition of key fields and `__typename` to selections + +The compiler automatically adds the key fields declared with `@typePolicy` to the selections that return that type. +This is to ensure that a `CacheKey` can be generated for the record. + +When you query for `User`, e.g.: + +```graphql +# operations.graphql +query User { + user(id: "1") { + email + name + } +} +``` + +The compiler plugin will automatically add the `id` and `__typename` fields to the selection set, resulting in: + +```graphql +query User { + user(id: "1") { + __typename # Added by the compiler plugin + email + name + id # Added by the compiler plugin + } +} +``` + +Now, [TypePolicyCacheKeyGenerator](https://apollographql.github.io/apollo-kotlin-normalized-cache/kdoc/normalized-cache/com.apollographql.cache.normalized.api/-type-policy-cache-key-generator.html?query=fun%20TypePolicyCacheKeyGenerator(typePolicies:%20Map%3CString,%20TypePolicy%3E,%20keyScope:%20CacheKey.Scope%20=%20CacheKey.Scope.TYPE):%20CacheKeyGenerator) +can use the value of `__typename` as the type of the returned object, and from that see that there is one key field, `id`, for that type. + +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. +> +> In that example the cache key would be `42` instead of `User:42`. + +#### Unions and interfaces + +Let's consider this example: + +```graphql +# schema.graphqls +type Query { + search(text: String!): [SearchResult!]! +} + +type Product { + shopId: String! + productId: String! + description: String! +} + +type Book { + isbn: ID! + title: String! +} + +union SearchResult = User | Post +``` + +```graphql +# extra.graphqls +extend type Product @typePolicy(keyFields: "shopId productId") +extend type Book @typePolicy(keyFields: "isbn") +``` + +```graphql +# operations.graphql +query Search($text: String!) { + search(text: $text) { + ... on Book { + title + } + } +} +``` + +The plugin needs to add the key fields of all possible types of `SearchResult`, like so: + +```graphql +query Search($text: String!) { + search(text: $text) { + __typename # Added by the compiler plugin + ... on Book { + title + } + # Added by the compiler plugin + ... on Book { + isbn + } + ... on Product { + shopId + productId + } + } +} +``` + +The principle is the same with interfaces, for instance: + +```graphql +# schema.graphqls +type Query { + search(text: String!): [SearchResult!]! +} + +interface SearchResult { + summary: String! +} + +type Product implements SearchResult { + summary: String! + shopId: String! + productId: String! +} + +type Book implements SearchResult { + summary: String! + isbn: ID! + title: String! +} +``` + +The modified query would look the same as above, with the key fields of `Product` and `Book` added to the selection set. + +> If key fields are defined on the interface itself, they only need to be added once, instead of once per possible type. + +## Resolving to cache keys (`@fieldPolicy`) + +When a field returns a type that has key fields, and takes arguments that correspond to these keys, you can use the `@fieldPolicy` directive. + +For instance, + +```graphql +# schema.graphqls +type Query { + user(id: ID!): User +} + +type User { + id: ID! + email: String! + name: String! +} +``` + +```graphql +# extra.graphqls +extend type User @typePolicy(keyFields: "id") + +extend type Query @fieldPolicy(forField: "user", keyArgs: "id") +``` + +From this, when selecting e.g. `user(id: 42)` the `FieldPolicyCacheResolver` knows to return `User:42` as a `CacheKey`, +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. + +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. + From 92c159b3e92ca5fdb3f43d849ebf258beffe4a7e Mon Sep 17 00:00:00 2001 From: BoD Date: Tue, 3 Jun 2025 19:07:55 +0200 Subject: [PATCH 3/3] Remove redundant plugin configuration --- Writerside/topics/cache-control.md | 14 -------------- Writerside/topics/migration-guide.md | 2 +- Writerside/topics/welcome.md | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Writerside/topics/cache-control.md b/Writerside/topics/cache-control.md index d46b9d0e..cfc318ab 100644 --- a/Writerside/topics/cache-control.md +++ b/Writerside/topics/cache-control.md @@ -102,20 +102,6 @@ extend type Book @cacheControlField(name: "cachedTitle", maxAge: 30) extend type Reader @cacheControlField(name: "book", inheritMaxAge: true) ``` -Then configure the Cache compiler plugin in your `build.gradle.kts`: - -```kotlin -apollo { - service("service") { - packageName.set(/*...*/) - - plugin("com.apollographql.cache:normalized-cache-apollo-compiler-plugin:%latest_version%") { - argument("packageName", packageName.get()) - } - } -} -``` - This will generate a map in `yourpackage.cache.Cache.maxAges`, that you can pass to the `SchemaCoordinatesMaxAgeProvider`: ```kotlin diff --git a/Writerside/topics/migration-guide.md b/Writerside/topics/migration-guide.md index f8469996..697267bb 100644 --- a/Writerside/topics/migration-guide.md +++ b/Writerside/topics/migration-guide.md @@ -42,7 +42,7 @@ import com.apollographql.cache.normalized.memory.MemoryCacheFactory ## Compiler plugin -Configure the compiler plugin in your `build.gradle.kts` file: +Configure the [compiler plugin](compiler-plugin.md) in your `build.gradle.kts` file: ```kotlin apollo { diff --git a/Writerside/topics/welcome.md b/Writerside/topics/welcome.md index bc780f2f..8fa66af5 100644 --- a/Writerside/topics/welcome.md +++ b/Writerside/topics/welcome.md @@ -39,7 +39,7 @@ dependencies { } ``` -2. Configure the compiler plugin +2. Configure the [compiler plugin](compiler-plugin.md) ```kotlin // build.gradle.kts