I found that Normalized Cache does not work with GraphQl Subscriptions (opened via websockets), and I didn't find anything in the apollo-android code that would prove the opposite.
Could you confirm it and probably clarify the reasons why it was designed so?
I found a workaround for that but this should probably be done by default:
Rx2Apollo.from(call)
.flatMapSingle((Response<D> response) -> Rx2Apollo
.from(apolloClient.apolloStore().writeAndPublish(subscription, response.data()))
.onErrorReturnItem(false) // no need to cancel subscription because of the failed cache update
.map($ -> response))
Thanks!
Currently subscriptions doesn't support normalized cache. Not sure if it make sense to add such support as subscriptions represents live queries via web socket connection.
Thanks for the confirmation!
IMHO it makes perfect sense to utilize normalized cache here, as it's the most common use case of websockets: delivery of some updates initiated by the backend to the app.
I'm not sure what to do if CacheKeyResolver produces NO_CACHE_KEY, but for the data which we can calculate cache key for (using id or smth), why wouldn't we want to put it into the cache?
hm, maybe you are right.
So you want to be able get cached response for the subscription before running a subscription?
@sav007 There is a pattern that teams widely using where devs use cache as the source of the truth and use subscriptions to refill cache. More about this approach in this blog:
https://medium.com/@wtr/data-query-patterns-for-graphql-clients-af66830531aa#1150
In Apollo-client (js) users can simply use subscribeToMore to get more data
@sav007 We have investigated having an interceptor layer attached to subscriptions. Are you open to accept Pull requests on this or you do think that this is a bad idea?
I have seen so many workarounds for lack of cache on subscriptions and I think that there should be at least the ability to attach this extra interceptor for developers who would like to use cache as a source of truth. Without that, it is hard to work with cache enabled app.
Let's imagine that we have a query cached on some separate activity. Now we subscribed to receive new results that are pushed directly to UI (which is Kinda antipattern on its own). Then we go back to the previous view and go back to the original view again. Since results are in the cache we get old results. Most of the developers at this point are really confused and simply do:
Things can go even worse as the subscription will now deliver new results.
There are also problems with race conditions and deduplication of data in the cache (but that is kinda usual thing)
IMHO There are two ways to solve this problem:
subscribeToMore that is available in js@wtrocki do you know if queries and subscriptions on JS share the same root cache key?
Right now our normalized cached for queries has QUERY_ROOT as root cache key, for instance:
"1002" : {
"__typename" : Human
"id" : 1002
"name" : Han Solo
}
"QUERY_ROOT" : {
"hero({"episode":"NEWHOPE"})" : CacheRecordRef(2001)
}
"1003" : {
"__typename" : Human
"id" : 1003
"name" : Leia Organa
}
"1000" : {
"__typename" : Human
"id" : 1000
"name" : Luke Skywalker
}
"2001" : {
"__typename" : Droid
"id" : 2001
"name" : R2-D2
"friends" : [
CacheRecordRef(1000)
CacheRecordRef(1002)
CacheRecordRef(1003)
]
}
So should the subscription share the same root cache key as query?
subscription TestSubscription($episode: Episode!) {
hero("episode": $episode) {
...
}
}
Actually never mind, after investigation our QUERY_ROOT is a root for all queries (even mutations). So should be the same for subscription.
Now do you think having 2 fetch policies makes sense for the subscription:
NETWORK_ONLY - bypass cache (will be a default policy)CACHE_AND_NETWORK - first hit the cache then networkdo you know if queries and subscriptions on JS share the same root cache key?
There are a couple of cache implementations in JS.
The Apollo - InMemoryCache uses the following structure:
{
Query: {},
Mutation: {},
Subscriptions: {},
... cachedData
}
Query only links to the data that is listed in the root.
There are two ways to work with subscriptions in js client:
SubscribeToMore works as follows:
refetchQueries method can be executed So should the subscription share the same root cache key as query?
Good question. TBH I'm not sure about that.
Regular subscriptions may not need it, but subscribeToMore method can enable this flow.
Now do you think having 2 fetch policies makes sense for the subscription
I do think that subscriptions should simply enable saving data to the cache (when using normalized cache option).
Then developers should be able to specify what queries will be affected by subscription.
This blog post from my colleague describes the subscription/mutation update problem for js:
https://dev.to/aerogear/automatically-update-apollo-cache-after-mutations-20n7
However, there could be another approach to get it simply by subscribing to the data again, we could get the first results from what was already cached. This approach can cause some duplication issues between query and subscription.
Generally, problem is not trivial and it will require some discussion basing on how Android developers would expect to work with the live data.
IMHO
I do think that the best way to make subscriptions work for Android would be to simply add ability to connect subscription with the query. Subscription will provide more data to query and also update the query cache.
EDIT:
Lets get more feedback on this topic from the community.
Cache support is a must for subscriptions. Default policy here for after CRUD ops is to simply invalidate the cache for a given key (using server side pagination and sorting).
Now... that being said, when subscription data arrives (say a new record) same policy would be needed, but, alas, no access to cache.
Long story short... I've resorted to 'network-only' fetches after a subscription fires to keep data consistent in the UI :/
Most helpful comment
@sav007 There is a pattern that teams widely using where devs use cache as the source of the truth and use subscriptions to refill cache. More about this approach in this blog:
https://medium.com/@wtr/data-query-patterns-for-graphql-clients-af66830531aa#1150
In Apollo-client (js) users can simply use subscribeToMore to get more data