Feathers-vuex: [discussion] using feathers-vuex as a client-side incremental cache

Created on 24 Aug 2017  Â·  8Comments  Â·  Source: feathersjs-ecosystem/feathers-vuex

One of the wonderful things about feathers-vuex is that it loads back-end data, keeps them in browser's memory (vuex states), and updates them via socketio when needed. This makes it a handy tool to do client-side cache so that we don't need to waste band-width to request the same data again and again.

Here I want to discuss what we should do to make it easier to turn feathers-vuex into an out-of-box client-side incremental cache.

Previously I use raw client-side feathers services to cache data, and use feathers-reactive and vue-rx to let vue components be able to subscribe these data from the client-side feathers services. This approach requires considerable knowledge of rxJS, and not all team members feel comfortable with it.

Another possibility is to use the newly developed feathers-offline-realtime and feathers-offline-publications. Pros: they provide out-of-box data-replica and syncing between client and server, and furthermore, allow easy configuration and optimization of event filtering on the the server side. Cons: 1) they are designed to sync the whole back-end service data or a subset of the data with a given query, so they do not support incremental cache (i.e. querying and caching some records at first, and then run new find\get methods to add more records into the cache later); 2) Still need to write some rxJS code to glue feathers data and vue components.

I have discussed similar topic with @eddyystop on the feathers-offline-realtime project (https://github.com/feathersjs/feathers-authentication-management/issues/8#issuecomment-313148338). Later I realized the realtime strategy, by its nature, is for replication of a given service over a given query, not for incremental caching.

I think feathers-vuex is currently the best way to fulfill a full-functional client-side incremental cache for applications using feathers and vue. It naturally allows incrementally adding new records into the store, and since it uses vuex, no need for rxJS subscriptions.

Based on what we already have, I propose several new APIs for the service module, which can make the caching process easier:

1) a new option in the params object for the get action, which makes the get firstly look for the given id in the vuex state (similar to the get gettter). If the item exists, bypass the remote request and just return the item in the vuex state with a resolved promise; if not, request the item from remote server normally, cache it in the vuex store, and return it (similar to the original get action).

2) a new option in the params object for the update and patch actions, which enables optimistic mutation, i.e., mutating the data in the vuex state first, letting the back-end do the same mutation, and in case of back-end error, restoring the old data to the vuex state. Currently we can write some code to use the copy object to do this job, but it would be nice to include this in the offical API.

3) (not necessary) some volume-control strategies to prevent the unlimited growing of the cache in the vuex store. It would be wonderful to provide some API to let user determine the maximum size of the keyedById object of a certain service module, and use LRU or random strategy to expire some old records in the module when it grows too big. Or we can have an expiration time for all the records in the service module.

What do you guys think of it?

Fixed in next release enhancement

Most helpful comment

This has been released in [email protected]

All 8 comments

I like these ideas. I've wanted to implement a fall-through cache since I made the first version. I've spent all my time thinking of how to implement it for find. Pagination makes it difficult. Implementing get will be very easy, though.

For #1, I'd like to see some internal connection detection and have the default behavior be checking the store, first, immediately returning any cached result while simultaneously making the remote query. When it returns, we'd update the data. We could use the param you mention to disable the request.

I really want to implement fall-through caching for find, but it's a bit difficult when it comes to pagination. $skip is particularly problematic, because you don't have all of the same records on the client as you might on the server. Still looking for a solution that works well.

For #2 (similar to #1) I also wanted to make optimistic mutation the default and allow the ability to turn it off two ways: a param or an init option.

For #3, maybe we can keep a timestamp of when every record is changed and allow a configurable expiration to wipe out old data. It could occur at an interval OR manually.

I have been thinking of the fall-through find for a long time, and then realised that in most cases people just do not need such an intelligent find. When we do get we always are not sure if the item wanted has been cached by a previous find or not; on the contrary when we do find we are probably aware of whether we want to find in cache or from server.

So in my opinion this is not a very commonly required feature, and we can try to dig it further in the future.

Hi @marshallswain it has been long time since this discussion. Now I am managing to do what you have suggested above:

For #1, I'd like to see some internal connection detection and have the default behavior be checking the store, first, immediately returning any cached result while simultaneously making the remote query. When it returns, we'd update the data. We could use the param you mention to disable the request.

It can be done with the following logic. Before I write tests for it and submit a PR, I just want to make sure this is what you wanted:

When the params skipRequestIfExists is false or missing, if the requested id is already in the store, then the 'get' action will resolve with the existing data immediately, and also do a remote request at the same time. Since promises can only resolve once, the caller of the 'get' action cannot directly know when the remote data arrive. After the remote data arrives, it will go into the store to replace the old data, automatically update component/DOM, but the 'get' action has been resolved long ago.

Am I getting it right?

    get ({ state, getters, commit, dispatch }, args) {
      let id
      let params
      let skipRequestIfExists

      if (Array.isArray(args)) {
        id = args[0]
        params = args[1]
      } else {
        id = args
        params = {}
      }

      if ('skipRequestIfExists' in params) {
        skipRequestIfExists = params.skipRequestIfExists
        delete params.skipRequestIfExists
      } else {
        skipRequestIfExists = state.skipRequestIfExists
      }

      function getFromRemote () {
        commit('setGetPending')
        return service.get(id, params)
          .then(item => {
            dispatch('addOrUpdate', item)
            commit('setCurrent', item)
            commit('unsetGetPending')
            return item
          })
          .catch(error => {
            commit('setGetError', error)
            commit('unsetGetPending')
            return Promise.reject(error)
          })
      }

      // If the records is already in store, return it
      const existedItem = getters.get(id, params)
      if (existedItem) {
        commit('setCurrent', existedItem)
        if (!skipRequestIfExists) getFromRemote()
        return Promise.resolve(existedItem)
      }
      return getFromRemote()
    }

@beeplin this makes perfect sense for .get requests, and I definitely support implementing it. Please branch all PRs from the models branch, since I’m about to release, and it has a lot of changes. ;)

sure, will check the models branch soon.

This has been released in [email protected]

Hello @marshallswain we have been creating an app that needs to work in online / offline modes and we're using feathers-vuex. It works great in offline mode (i.e. state keep values cached) but when app starts in offline mode it fails on 'authenticate()' method. Is there a solution to this?

Yes. Don’t authenticate. Instead, decode the JWT and grab the userid

Have a great day!
Marshall


From: HireBrains notifications@github.com
Sent: Wednesday, August 14, 2019 10:04 AM
To: feathers-plus/feathers-vuex
Cc: Marshall Thompson; Mention
Subject: Re: [feathers-plus/feathers-vuex] [discussion] using feathers-vuex as a client-side incremental cache (#42)

Hello @marshallswainhttps://github.com/marshallswain we have been creating an app that needs to work in online / offline modes and we're using feathers-vuex. It works great in offline mode (i.e. state keep values cached) but when app starts in offline mode it fails on 'authenticate()' method. Is there a solution to this?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/feathers-plus/feathers-vuex/issues/42?email_source=notifications&email_token=AAA7OWPMGPITD4BG4N7YMHDQEQUI5A5CNFSM4DYGK7T2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4JJAYQ#issuecomment-521310306, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AAA7OWMOWIZNAQPB6XLGQKDQEQUI5ANCNFSM4DYGK7TQ.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

samburgers picture samburgers  Â·  7Comments

ontoneio picture ontoneio  Â·  7Comments

DreaMinder picture DreaMinder  Â·  6Comments

ericirish picture ericirish  Â·  5Comments

HarisHashim picture HarisHashim  Â·  6Comments