Skip to content
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

Disallow reading entire StoreObject from EntityStore by ID. #5828

Merged
merged 4 commits into from
Jan 23, 2020

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Jan 23, 2020

One of the most important internal changes in Apollo Client 3.0 is that the result caching system (introduced in #3394) now tracks its dependencies at the field level, rather than at the level of whole entity objects, thanks to #5617.

As proof that this transformation is complete, we can finally remove the ability to read entire entity objects from the EntityStore by ID, so that the only way to read from the store is by providing both an ID and the name of a field. The logic I improved in #5772 is now even simpler, without needing multiple overloaded EntityStore#get signatures.

In the process, I noticed that the EntityStore#has(ID) method was accidentally depending on any changes to the contents of the identified entity, even though store.has only "cares" about the existence of the ID in the store. This was a problem only if we called store.has during a larger read operation, which currently only happens during readFragment, which calls InMemoryCache#read with options.rootId.

The symptoms of this oversight went unnoticed for two reasons:

  1. The ApolloCache#readFragment method was computing a fresh fragment query document every time it was called, even if it had seen the given fragment document before (see f039d40). In other words, fragments were not benefitting from result caching at all, which masked the problem of result caching being too sensitive for fragments.
  2. With that bug fixed, the caching of readFragment results was still unnecessarily sensitive to changes to unrelated fields within the given entity, due to the problem with store.has. The results themselves were still logically correct, but their caching was not as effective as it could be. Fortunately, I was able to model this dependency with an ID plus a fake field name called __exists, which is only dirtied when the ID is newly added to (or removed from) the store.

That's the beauty and the curse of caching: you might not notice when it isn't working, because all your tests still pass, and nobody complains that their application is broken. Now we have more aggressive tests of fragment result caching behavior, so this kind of oversight won't happen again.

Because we were computing a fresh fragment query document each time
readFragment was called, readFragment was effectively never benefitting
from result caching.
One of the most important internal changes in Apollo Client 3.0 is that
the result caching system now tracks its dependencies at the field level,
rather than at the level of whole entity objects: #5617

As proof that this transformation is complete, we can finally remove the
ability to read entire entity objects from the EntityStore by ID, so that
the only way to read from the store is by providing both an ID and the
name of a field. The logic I improved in #5772 is now even simpler,
without the need for overloading multiple EntityStore#get signatures.

In the process, I noticed that the EntityStore#has(ID) method was
accidentally depending on any changes to the contents of the identified
entity, even though store.has only cares about the existence of the ID in
the store. This was only a problem if we called store.has during a larger
read operation, which currently only happens when InMemoryCache#read is
called with options.rootId. The symptoms of this oversight went unnoticed
because the caching of readFragment results was just a little more
sensitive to changes to the given entity. The results themselves were
still logically correct, but their caching was not as effective as it
could be. That's the beauty and the curse of caching: you might not notice
when it isn't working, because all your tests still pass, and nobody
complains that their application is broken.

Fortunately, we can model this dependency with an ID plus a fake field
name called "__exists", which is only dirtied when the ID is newly added
to or removed from the store.
@benjamn benjamn added this to the Release 3.0 milestone Jan 23, 2020
@benjamn benjamn requested a review from hwillson January 23, 2020 15:38
@benjamn benjamn self-assigned this Jan 23, 2020
@benjamn benjamn changed the title Disallow reading whole store object by Disallow reading entire StoreObject from EntityStore by ID. Jan 23, 2020
Copy link
Member

@hwillson hwillson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch @benjamn!

@benjamn benjamn merged commit 1dcaf29 into master Jan 23, 2020
@benjamn benjamn deleted the disallow-reading-whole-StoreObject-by-ID branch January 23, 2020 16:28
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants