Let's take a chat application for example.
A user has access to multiple chat threads, each with multiple messages.
The user interface consists of a list of threads on the left side (listThreads
) that contains the name of the other party, the last message, the unread message count and the date & time of the last message, and the actual messages (viewThread
) and a reply box on the right hand side (think facebook messenger).
When the user selects a message thread, the viewThread
component subscribes to a query something along the lines of:
query thread {
threads(id: 'xxxx') {
id
other_party { id name }
unread_messages
messages {
sent_by { id }
timestamp
text
}
}
To make updates live, it is setting q.subscriptToMore
with a subscription along the lines of:
subcription newMessages {
newMessage(thread_id: 'xxx') {
sent_by { id }
timestamp
text
}
}
This works perfectly, the new messages show up as they should.
To list the available message threads, a less detailed view of all threads are queried:
query listThreads {
threads {
id
other_party { id name }
unread_messages
last_updated_at
}
}
To keep the list in sync the same subscription is used, without filtering on the thread_id
, and the thread list data is updated manually
This also works fine.
However if a thread A
is selected, the messages of thread A
are cached.
If thread B
is selected afterwards the subscription to the query getting the detailed info of thread A
is destroyed since the observable is destroyed when the router excanges the viewThread
component.
If then a message arrives to thread A
while the user is viewing thread B
, the threadList
is updated (since that subscription is live), but if the user switches back to thread A
, the messages are loaded from the cache which are now outdated, since there was no subscription for that particular message thread that would have updated or invalidated the cache.
In other circumstances where the user navigates to an entirely different page, where thread list would not be in view the problem is even more obvious, as there is nothing related to the chat messages that are actively subscribed to, so nothing to invalidate the cached data when a new message arrives, although the server theoretically provides a way to do that by offering new message subscription events.
My question is the following:
What are the best practices on keeping data in sync / invalidating that has been cached by Apollo, which are not actively "in use"? What are the best practices on keeping nested data in sync (messages of threads of an event [see below]). I don't feel like having to implement the logic on how to subscribe to and update message data in the event query is a clean solution.
Using .subscribeToMore
works for keeping data that is actively used in sync, but once that query is no longer in use the data remains in the cache which may or may not get outdated with time. Is there a way to remove cached data when an observable goes out of scope? As in keep this data cached as long as there is at least one query using it, because i trust that it also implements logic that will keep it in sync based on the server push events.
Should a service be used that subscribes (through the whole lifecycle of the SPA) to all subscription events and contains the knowledge on how to update each type of cached data, if present in the cache? (this service could be notified on what data needs to be kept in sync to avoid using more resources than necessary) (as in a service that subscribes to all newMessage
events, and pokes the cache based on that)? Would that automatically emit new values for queries that have returned objects that have references to such data? (would updating message:1
make a thread query that returned the same message:1
in its messages field emit a new value automatically) Or those queries have to also be updated manually?
This starts to be very cumbersome when extending this model with say Events
that also have their own chat thread, so querying event { thread { messages { ... } }
now needs to subscribe to the newMessage
subscription which breaks encapsulation and the single responsibility principle.
It is also problematic that to subscribe to newMessage
data one would need to provide the id of the message thread associated with the event, but that is not known before the query returns. Due to this .subscribeToMore
cannot be used, because at that point I don't have the thread_id
available yet.