CodingItWrong / vuex-jsonapi

Library to access JSON API data via Vuex stores
22 stars 1 forks source link

Thoughts on vuex-jsonapi #1

Open mrichar1 opened 6 years ago

mrichar1 commented 6 years ago

Hi,

I've just been experimenting with Vuex and a jsonapi API and, having played around a bit with how I might model this, found this project, which quite closely aligns to what I was planning to implement!

Since you ask for people to give their thoughts on the project and how it might evolve, I thought I'd add some comments - though bear in mind I'm relatively new to both Vue/Vuex and JS so I'll try and keep things high-level since I might now always know the 'Best JS' way of doing things...

As I understand it, you are storing records as an array, with each item having a combination of type and id used as its 'key'.

When I started thinking about storing jsonapi data in Vuex, I was drawn towards using a normalized form in a nested object, i.e:

records: {
  widgets: {
    id: {
      attributes: { ... }
    }
  },
  things : { ... }
}

I subsequently discovered json-api-normalizr which does this, and have been using this in my experiments to good effect.

The advantage of this is that it's easier (and maybe faster?) to do operations on single items - especially updates/deletes.

The disadvantage is that Vuex doesn't handle nested objects so well, so you have to do things like the following in mutations (assuming result is already normalized):

update (state, result) {
  for (let key in result) {
    // Update state.api key with a fresh object containing a merge of state and result
    Vue.set(state.api, key, Object.assign({}, state.api[key], result[key]))
  }
}

I'd be keen to know if you'd be interested in refactoring vuex-jsonapi to store normalized data like this? (either done 'in-house' or using a 3rd party module). The alternative I guess would be to hook into the records state (either directly through vuex-jsonapi mutations, or using vuex watch/subscribe functions) and create a normalized state alongside records.

alexseik commented 6 years ago

Hi, we have developed a similar library in https://github.com/boream/vuex-jsonapi. (The name selected is same as yours purely by chance!!) . The idea is to have factory methods to create a http jsonapi generic service and a generic store module driven by model configurations.

createService (httpService, conf, baseUrl)

  1. httpService: Is a JS class with get,post,patch and delete methods.
  2. conf: A model configuration for the service. Below an example used in our private project
    configuration = {
    name: 'instanciaLetraCesion',
    type: 'node--instancia_letra_cesion',
    attributes: {
    uuid: {
      type: 'string',
      required: false,
      isArray: false,
      name: 'uuid',
    },
    title: {
      type: 'string',
      required: true,
      isArray: false,
      name: 'title',
    },
    valor: {
      type: 'float',
      required: false,
      isArray: false,
      name: 'valor_letra',
    },
    valorCesion: {
      type: 'float',
      required: false,
      isArray: false,
      name: 'importe_en_cesion',
    },
    },
    relationships: {
    letraOperation: {
      type: 'node--instancia_letra',
      required: false,
      isArray: false,
      name: 'letra_operacion',
    },
    },
    };
  3. baseUrl: The enpoint for this model. This url is used by the httpService you passed in.

Result: A jsonapi generic service which exports the following methods:

  1. create (POST)
  2. update (PATCH)
  3. remove (DELETE)
  4. fetch(GET)

createModule(service, namespaced = false)

  1. service: The service is created above.
  2. namespaced: If the created module will be a store namespaced module.

Result: The store generic module which has the following signature:

{
  state: {
    status: UPDATING,
    ids: [],
    entities: {},
    entitiesStatus: {},
    searchedEntities: []
  },
  mutations: { some internals mutations... },
  actions: {
    upsertEntityAction: /* update or create an entity */,
    removeEntityAction: /* remove */
    fetchAction: /* fetch entities by filters applied on server applying jsonapi filters and in local performing a local search */
  },
  getters : {
    entitiesGetter:{ [entityId]: {entity} },
    entitiesArrayGetter: [{ entity }],
    statusEntitiesGetter: { [entityId]: {status} },
    statusEntitiesArrayGetter: [{ status }],
  }
}

Also we provide some useful functions for searching in entities by id and a local search that performs the same search as is taken around in a jsonapi server when you use filters.

Finally, we want to publish this library with the name of vuex-jsonapi. For the moment I will use jsonvuex instead. But we would love work with you in a better library for the same purpose. Our library has important improvements to do:

  1. Use subrequests
  2. Cache results. (Fetching strategy, update strategy, localstorage, indexdb, etc...)
  3. Put on place some service worker strategy for http requests. For sure impact on 2.
  4. Write the Docs
  5. Authentication and Authorization
  6. TEST, TEST, TEST
mrichar1 commented 6 years ago

@alexseik interesting to see an alternative approach. My only comment would be that I'd be wary of a model config that uses it's own custom schema/validation - I'd much prefer the use of an existing schema (OpenAPI springs to mind) - especially where this can be sourced from the API provider and included automatically.

alexseik commented 6 years ago

@mrichar1 you are right about

(OpenAPI springs to mind) - especially where this can be sourced from the API provider and included automatically

. I need to learn more about this technology.

Thanks

mrichar1 commented 6 years ago

I've forked this project and had a bit of a play around with the code to see how easy it would be to refactor to use an object for state, and what changes this would requires/advantages or disadvantages this would have. I've got the code working to show what I mean, though I've not touched filtered/related, tested thoroughly or made any effort to make it tidy or efficient yet...

It looks like there are several things of note:

  1. There's no longer a need to distinguish between getting single items and collections, so STORE_RECORD goes away and there's now a single STORE_RECORDS mutator/storeRecords function.

  2. Normalizing the data would allow for the separate 'endpoint' modules to collapse into a single one, since there can now be one store with nested data, rather than one store per-endpoint. The advantage here is that there only needs to be one 'set' of actions/getters added to the Vue component, rather than one-per-endpoint.

2a. This would then mean that the endpoint becomes an argument to the method - i.e. it changes from this.$store.dispatch('widgets/loadAll') to this.$store.dispatch('loadAll', {type: 'widgets'}).

2b. This could then mean that the loadAll and loadById methods could collapse, so that the presence or absence of an id in the data object indicates if this is a collection or an item request.

My gut feeling is that the core changes would be beneficial in making the library easier to work with overall, and perhaps also some of the resulting options to simplify the module. But I'm keen to know what you think of this approach...

The branch with PoC changes is here:

https://github.com/mrichar1/vuex-jsonapi/tree/normalize

mrichar1 commented 6 years ago

So I recently started work on a project to implement the ideas in the PoC above, which is now mostly ready.

I'd been holding off pushing it out to github until it was more polished, but I now see that you've made some changes to this project, so I thought it might be a good idea to publish sooner rather than later, since if this project is becoming active then there may be scope for pooling resource, rather than running 2 very similar projects side-by-side.

It does most of what is described in the PoC above - in addition it does some restructuring/normalization of the data returned by actions/getters to make it more user-friendly than raw JSONAPI. It doesn't (yet) have support for filtering and the other JSONAPI extensions, though I have work underway to implement this.

The project is at https://github.com/mrichar1/jsonapi-vuex

I've not actually done much e2e testing yet, and have mainly focused on the unit testing, so there may be issues where the implementation doesn't quite match how Vuex expects moduels to behave. Additionally I'm relatively new to javascript (my day job is mostly python), so there may be lots of places where the implementation could be tidier/more efficient.

I'm interested to know what you think!

CodingItWrong commented 6 years ago

@mrichar1 Thanks for your thoughts. This project mostly went dormant after I made my initial version, but now I'm moving toward implementing both a Vuex and MobX JSON API state library, using a shared JSON API client library under the hood. If I keep up momentum on it, it will definitely be good for us to talk.

Developer ergonomics are a big priority for me, so I'm all for keeping separate actions if they make the code more clearly express intent. Having separate modules may also contribute to that clarity; I'm not sure yet. Hearing your thoughts, I think I'd like to continue moving this library forward on my own for now, to reach a point where it's fully fleshed out with my initial ideas on ergonomics. Then I'll be in a better position to consider alternatives.

I'll look through your fork a bit more when I can. Looking forward to staying in touch!