Urql: RFC: Fragment-level caching

Created on 25 May 2018  路  13Comments  路  Source: FormidableLabs/urql

Instead of shipping a complex, normalising cache by default (it might still be implemented as a third-party exchange, I suppose) we can ship a simple fragment-level cache.

This caching strategy would live alongside the current query-level cache. It can also use the same store/cache entity and just use separate keys.

Strategy

  • Detect queries containing fragments
  • Inspect each response for each of the given fragments
  • Cache given fragments by "Fragment + Typename + ID" (default key extractor)
  • Don't invalidate these keys like cached queries are, instead keep them up-to-date with each incoming response

Hence a most-recent version of a cached fragment will always be available.

Usage

Once this cache is in place we're able to integrate primitive APIs to access these cached fragments. On a high-level API, we can introduce a FragmentCache component, used like so:

<FragmentCache fragment={fragmentStr} query={{ __typename: 'Item', id: 'test' }}>
  {({ data, stale }) => ()}
</FragmentCache>

An example can be found here: https://gist.github.com/kitten/3a9136fa731678838013292a32f7daaa

Optimistic updates can also at this point be introduced. A mutation could specify optimisticFragments, which provide a list of optimistic responses from the mutation which _only_ update the fragment-level cache.

Why?

  • We'd avoid a complex cache by default and instead have two highly predictable caches
  • We still provide the ability to solve the "90%" case, accessing the cache explicitly when a query is still loading or other custom use-cases
  • The cache component can be adapted to also work for the query component, thus giving us some breathing-room for changing the default cache/network-control behaviour (cache-first instead of -only?)

How?

We can maybe implement this as a separate package first, since it's relatively contained, so that it's possible to try this concept out without changing urql's public API at all.

It could be published as urql-exchange-fragment-cache or sth

cc @kenwheeler

All 13 comments

@mxstbr While you're involved in the list-issue, this is of course tangential, but would you think this and the smart list would be sufficient solutions for Spectrum, just as an example, as a whole?

Yeah probably! I'm not 100% aware of the tradeoffs between this and the current cache, but we use fragments all over the place, so it should be fine?

@mxstbr it's a concept to run alongside the current query-level cache that stores entire query results by a hash key. So we'd have a separate fragment-level cache with a separate component to _just_ access the cached fragments.

If this fragment cache worked with the list cache in #84, in a fairly transparent way, could it almost replace the Apollo Client @connection query field directive? Or would/could/should that be a new wunder-directive/API or something (maybe called chunder, cause it spits out everything?)?

@jpstrikesback that's unrelated to this issue. The @connection directive changes how a key is computed for the entire part of the query to be cached, so not how fragments are cached. Fragments will always adhere to a stable key, by default or for example: x.__typename + x.id.

Pagination on the other hand will need some kind of exception to be able to cache the query in a stable manner, since we'll otherwise run into different problems too. However, we can likely pull this exception up/out into the component somehow since we will explicitly _know_ that it needs to be cached differently.

Is this necessary for optimistic updates? Couldn't we provide an optimistic update resolver function to the mutation that would immediately resolve prior to the resolved response from the server?

@blorenz I'd consider that sth that's quite simple to do with a component's local state, so probably not quite useful on its own, considering how specific urql's cache is to individual queries

@kitten Thank you for the guidance. Do you have any best practice for utilizing urql to drive data from local state? I image this should be handled with componentDidUpdate() and an assortment of logic on the urql-injected props. I only ask this because when I was initially using Apollo, I tried to direct the data through Redux which in turn led to a mess. I then became aware that I did not to manage the data via Redux itself and I had a much better experience with Apollo.

If no one is taking this up, I'd like to give it a try would be for next week though if anyone is taking it up feel free to say so

@jovidecroock awesome! @jevakallio has basically come up with the same / very similar idea independently which is why I reopened this, but he's thought it through a lot more. So it's safer to wait since he'll post an extended and new RFC here which we can discuss first 馃檶

@kitten @JoviDeCroock I've written up my fragment proposal here at https://github.com/FormidableLabs/urql/issues/317

Any discussion regarding the proposal itself should live that RFC.

Any discussion regarding fragment-level caching more broadly should remain in this issue.

To fully inform the discussion, can I request a little more documentation about how the cacheExchange currently works? I went through the code but couldn't quite grok how the default is really designed to operate

Closing in favour of #317

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TLadd picture TLadd  路  4Comments

ivosequeros picture ivosequeros  路  5Comments

wodnjs6512 picture wodnjs6512  路  3Comments

BjoernRave picture BjoernRave  路  3Comments

alexraginskiy picture alexraginskiy  路  3Comments