Closed martinbonnin closed 2 years ago
About the directive: would it make sense for it to be @cacheControl(maxAge: 60)
to be like the Apollo Server one - or maybe we don't want to on purpose (or can't)?
When discussing the declarative cache directives, we decided to not reuse the federation @key
and go for @typePolicy
instead as we were not sure the semantics would be exactly identical.
I think it's safer to do that assumption here as well.
That being said, I don't have any strong opinions about naming at this stage. This was more of a placeholder than anything else.
Oh interesting! Makes sense!
Updated the initial description after the https://github.com/apollographql/apollo-kotlin/issues/3709 discussion.
https://github.com/apollographql/apollo-kotlin/pull/4104 and https://github.com/apollographql/apollo-kotlin/pull/4156 introduce a new (experimental) SQLite cache backend (named Blob
) that can store a date alongside the fields. This can be used to implement both client driven expiration based on the received date and server driven expiration based on the “Cache-Control"
header.
val client = ApolloClient.Builder()
.normalizedCache(
normalizedCacheFactory = SqlNormalizedCacheFactory(name = fileName, withDates = true),
cacheKeyGenerator = TypePolicyCacheKeyGenerator,
cacheResolver = ReceiveDateCacheResolver(maxAge)
)
.storeReceiveDate(true)
.serverUrl("https://...")
.build()
val apolloClient = ApolloClient.Builder()
.normalizedCache(
normalizedCacheFactory = SqlNormalizedCacheFactory(name = fileName, withDates = true),
cacheKeyGenerator = TypePolicyCacheKeyGenerator,
cacheResolver = ExpireDateCacheResolver()
)
.storeExpirationDate(true)
.serverUrl("https://...")
.build()
Both these APIs leverage the CacheResolver
API that has access to both the __typename
and the cache date so it should be reasonably doable to implement custom logic there.
I'm going to close this issue as it was quite broad and follow up in more focused ones:
@martinbonnin follow-up question on this, If i were to use the NormalizedCache
direclty, without using ApolloClient
would it be possible to somehow use this new CacheResolver
api with the direct usage of NormalizedCache
itself?
I would say so. The CacheResolver
API is used by readFromCache
so nothing is preventing you to use that without an ApolloClient
.
All in all, I like to think of the CacheResolver
API as a "mini-backend" that is embedded in the client and can serve data from an unstructured key-value store.
One question you will bump into if you're planning to use the NormalizedCache
directly is how to handle concurrency. Right the ApolloStore
is handling that. If you're using NormalizedCache
directly, you'll certainly have to come up with something. It might be as simple as a big lock but it's something to keep in mind.
When using the normalized cache, it should be possible to expire data:
There are currently very little options for this
HttpCache
has "CACHE_EXPIRE_TIMEOUT_HEADER" which is client controlled and doesn't read the server valueMemoryCache
has "expireAfterMillis" which is client controlled and doesn't read the server value (and works at the Record level)SqlNormalizedCache
has no expirationtentative API
By default, the
maxAge
should come from the server. Given this response:A client requesting this data after 60s will fail from cache and fallback to the network:
It should be possible to override the maxAge from the client when getting the data:
Or programmatically on the store itself:
Because some stale data is better than nothing, the cache should be configured with a maxSize and not remove entries immediately:
This way, a client can use stale data if it wants to:
Client-side expiration logic
If the server didn't send any cache information, we could think of "hardcoding" the rules in the client:
Or that could be a runtime thing too:
I'm not too sold on that idea yet because that adds friction between the client and the server but that'd be a way to communicate per-field cache information out of band.
Related work:
Part of apollographql/apollo-kotlin#2331