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

Support toReference(obj, true) to persist obj into cache. #5970

Merged
merged 3 commits into from
Feb 20, 2020

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Feb 20, 2020

The options.toReference helper function is provided to custom read and merge functions via their options parameter, as well as to cache.modify modifier functions, via their details parameter (#5909). See the tests included in this commit for examples of all three usages.

The original purpose of toReference was to return a Reference object, { __ref: <entity ID> }, given an object with a __typename and the necessary key fields for that type. If these expectations are not met, toReference(obj) can return null, to indicate that the object could not be identified.

Another toReference failure mode (besides returning null) is returning a Reference object that doesn't actually refer to anything stored in the cache. While this case is handled gracefully during cache reads (the non-existent object simply appears empty), you may have encountered cases where you do want to persist the object that was passed to toReference into the cache, such as when a custom read function knows how to provide a perfectly adequate default object based on options.args.

For those use cases, you can now call toReference(object, true) to merge the fields of the object into the cache, assuming it could be identified. Because this merging uses the same EntityStore#merge method as any other cache update, any dependent queries will be appropriately invalidated.

The fields of the object are merged into the cache as-is, without any transformations to handle field arguments, aliases, directives, or variables. For those cases, cache.writeQuery or cache.writeFragment are still the best options. Also, toReference is not exposed outside of
read/merge/modify functions, so if you're not in one of those functions you will have to call the normal writeQuery or writeFragment methods.

Implementation-wise, since toReference may now need to call store.merge, it was most convenient to move the definition of the toReference method from the Policies class to the EntityStore class.

The options.toReference helper function is provided to custom read and
merge functions, as well as to cache.modify modifier functions, via their
details parameter (see #5909). See the tests included in this commit for
examples of all three usages.

The original purpose of toReference was to return a Reference object,
{ __ref: <ID> }, given an object with a __typename and the necessary key
fields for that type. If these expectations are not met, toReference(obj)
can return null, to indicate that the object could not be identified.

Another toReference failure mode (besides returning null) is returning a
Reference object that doesn't actually refer to anything stored in the
cache. While this case is handled gracefully while reading from the cache
(the non-existent object simply appears empty), you may have encountered
cases where you _do_ want to persist the object that was passed to
toReference into the cache, such as when a custom read function knows how
to provide a perfectly adequate default object based on options.args.

For those use cases, you can now call toReference(object, true) to merge
the fields of the object into the cache, assuming it could be identified.

The fields of the object are merged into the cache as-is, without any
transformations to handle field arguments, aliases, directives, or
variables. For those cases, cache.writeQuery or cache.writeFragment are
still the best options. Also, toReference is not exposed outside of
read/merge/modify functions, so if you're not in one of those functions
you will have to call the normal writeQuery or writeFragment methods.

Implementation-wise, since toReference may now need to call store.merge,
it was most convenient to move the definition of the toReference method
from the Policies class to the EntityStore class.
@benjamn benjamn added this to the Release 3.0 milestone Feb 20, 2020
@benjamn benjamn self-assigned this Feb 20, 2020
@benjamn benjamn changed the title Support toReference(obj, true) to persist obj into store. Support toReference(obj, true) to persist obj into cache. Feb 20, 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.

Looks great @benjamn - this will definitely come in handy!

@codecov
Copy link

codecov bot commented Feb 20, 2020

Codecov Report

Merging #5970 into master will increase coverage by 0.02%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #5970      +/-   ##
==========================================
+ Coverage   95.04%   95.06%   +0.02%     
==========================================
  Files          90       90              
  Lines        3710     3711       +1     
  Branches      881      881              
==========================================
+ Hits         3526     3528       +2     
+ Misses        161      160       -1     
  Partials       23       23              

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0540f98...5600ca6. Read the comment docs.

@benjamn benjamn merged commit 4cca16e into master Feb 20, 2020
@benjamn benjamn deleted the persistent-options.toReference branch February 20, 2020 16:31
benjamn added a commit that referenced this pull request May 16, 2020
The `cache.modify` API was first introduced in #5909 as a quick way to
transform the values of specific existing fields in the cache.

At the time, `cache.modify` seemed promising as an alternative to the
`readQuery`-transform-`writeQuery` pattern, but feedback has been mixed,
most importantly from our developer experience team, who helped me
understand why `cache.modify` would be difficult to learn and to teach.

While my refactoring in #6221 addressed concerns about broadcasting
`cache.modify` updates automatically, the bigger problem with
`cache.modify` is simply that it requires knowledge of the internal
workings of the cache, making it nearly impossible to explain to
developers who are not already Apollo Client 3 experts.

As much as I wanted `cache.modify` to be a selling point for Apollo Client
3, it simply wasn't safe to use without a firm understanding of concepts
like cache normalization, references, field identity, read/merge functions
and their options API, and the `options.toReference(object, true)` helper
function (#5970).

By contrast, the `readQuery`-transform-`writeQuery` pattern may be a bit
more verbose, but it has none of these problems, because these older
methods work in terms of GraphQL result objects, rather than exposing the
internal data format of the `EntityStore`.

Since `cache.modify` was motivated to some extent by vague power-user
performance concerns, it's worth noting that we recently made `writeQuery`
and `writeFragment` even more efficient when rewriting unchanged results
(#6274), so whatever performance gap there might have been between
`cache.modify` and `readQuery`/`writeQuery` should now be even less
noticeable.

One final reason that `cache.modify` seemed like a good idea was that
custom `merge` functions can interfere with certain `writeQuery` patterns,
such as deleting an item from a paginated list. If you run into trouble
with `merge` functions running when you don't want them to, we recommend
calling `cache.evict` before `cache.writeQuery` to ensure your `merge`
function won't be confused by existing field data when you write your
modified data back into the cache.
benjamn added a commit that referenced this pull request May 18, 2020
The `cache.modify` API was first introduced in #5909 as a quick way to
transform the values of specific existing fields in the cache.

At the time, `cache.modify` seemed promising as an alternative to the
`readQuery`-transform-`writeQuery` pattern, but feedback has been mixed,
most importantly from our developer experience team, who helped me
understand why `cache.modify` would be difficult to learn and to teach.

While my refactoring in #6221 addressed concerns about broadcasting
`cache.modify` updates automatically, the bigger problem with
`cache.modify` is simply that it requires knowledge of the internal
workings of the cache, making it nearly impossible to explain to
developers who are not already Apollo Client 3 experts.

As much as I wanted `cache.modify` to be a selling point for Apollo Client
3, it simply wasn't safe to use without a firm understanding of concepts
like cache normalization, references, field identity, read/merge functions
and their options API, and the `options.toReference(object, true)` helper
function (#5970).

By contrast, the `readQuery`-transform-`writeQuery` pattern may be a bit
more verbose, but it has none of these problems, because these older
methods work in terms of GraphQL result objects, rather than exposing the
internal data format of the `EntityStore`.

Since `cache.modify` was motivated to some extent by vague power-user
performance concerns, it's worth noting that we recently made `writeQuery`
and `writeFragment` even more efficient when rewriting unchanged results
(#6274), so whatever performance gap there might have been between
`cache.modify` and `readQuery`/`writeQuery` should now be even less
noticeable.

One final reason that `cache.modify` seemed like a good idea was that
custom `merge` functions can interfere with certain `writeQuery` patterns,
such as deleting an item from a paginated list. If you run into trouble
with `merge` functions running when you don't want them to, we recommend
calling `cache.evict` before `cache.writeQuery` to ensure your `merge`
function won't be confused by existing field data when you write your
modified data back into the cache.
benjamn added a commit that referenced this pull request May 18, 2020
The `cache.modify` API was first introduced in #5909 as a quick way to
transform the values of specific existing fields in the cache.

At the time, `cache.modify` seemed promising as an alternative to the
`readQuery`-transform-`writeQuery` pattern, but feedback has been mixed,
most importantly from our developer experience team, who helped me
understand why `cache.modify` would be difficult to learn and to teach.

While my refactoring in #6221 addressed concerns about broadcasting
`cache.modify` updates automatically, the bigger problem with
`cache.modify` is simply that it requires knowledge of the internal
workings of the cache, making it nearly impossible to explain to
developers who are not already Apollo Client 3 experts.

As much as I wanted `cache.modify` to be a selling point for Apollo Client
3, it simply wasn't safe to use without a firm understanding of concepts
like cache normalization, references, field identity, read/merge functions
and their options API, and the `options.toReference(object, true)` helper
function (#5970).

By contrast, the `readQuery`-transform-`writeQuery` pattern may be a bit
more verbose, but it has none of these problems, because these older
methods work in terms of GraphQL result objects, rather than exposing the
internal data format of the `EntityStore`.

Since cache.modify was motivated to some extent by vague power-user
performance concerns, it's worth noting that we recently made writeQuery
and writeFragment even more efficient when rewriting unchanged results (#6274),
so whatever performance gap there might have been between cache.modify
and readQuery/writeQuery should now be even less noticeable.

One final reason that `cache.modify` seemed like a good idea was that
custom `merge` functions can interfere with certain `writeQuery` patterns,
such as deleting an item from a paginated list. If you run into trouble
with `merge` functions running when you don't want them to, we recommend
calling `cache.evict` before `cache.writeQuery` to ensure your `merge`
function won't be confused by existing field data when you write your
modified data back into the cache.
@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