Evaluate state management strategies for frontend
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Problem
ATM in our Vue codebase we have multiple approaches to global state management. This issue summarizes our existing approaches, their pros and cons
Handwritten stores/services
-
✅ Focused & simple. Does not carry any framework abstraction -
🛑 Originally non-reactive, becomes reactive in process of integrating into any Vue app (some parts of store become silently reactive when passed as props), leads to unpredictable results -
🛑 Does not have well-defined schema - maintaining long-living complex store is a pain -
📄 Often used with legacy event hubs and to communicate with old non-Vue code
I believe we consider this as technical debt and we should avoid adding more of them
Vuex
-
✅ Whole concept of FLUX architecture is simple and well-known across frontend -
✅ Easy to integrate with any kind of data sources (REST endpoint, local storage, GraphQL data, etc.) -
🛑 Does not have well-defined schema - maintaining long-living complex store is a pain in terms of data structure integrity -
🛑 Forces a lot of boilerplate code for typical operations (loading REST-endpoint + loading state as most widely known example) -
🛑 Managing data consistency across store might be not trivial and every times is written from scratch -
🛑 Components who access store might rely on "silent" assumption data is already in the store - it creates hard-to-track dependencies between invocation order of components (Component Ashould dispatch action,Component Bsilently depends that data, requested inComponent Ais already in store) -
❓ We are heavily usingnamespacedmodules. Are this is just a way to maintain several "logically separate" stores under the same instance ofVuex.Storeor we're utilizing them in other ways?
Vuex for a long time was default solution in Vue ecosystem in general and in GitLab specifically. With our new direction to GraphQL Vuex sometimes becomes too verbose & hard-to-maintain: any attempt to use GraphQL just as data provider for Vuex store results in duplicating the functionality Apollo provides out-of-the-box (keeping data consistent, managing loading flags). Additional problems raise when we would like to reuse same data between different Vue apps - this creates additional complexity layer
Apollo + Apollo local state
-
✅ Forces a well-defined schema of storage, verifies it in runtime (in dev) -
✅ Simplifies most common tasks (fetching data & keeping data integrity during updates) -
🛑 Extremely verbose when it comes to optimistic updates -
🛑 Requires a lot of boilerplate when integrating with non-GraphQL data sources -
🛑 Limits access to local state. Only two ways of querying state is either via graphql query or accessing specific entry by typename + id by usingreadFragment- Example which is hard to implement in Apollo: "Please find in local cache
Userwith usernamejohnand set histodoCountto3. You can only do that if you (somehow) knowjohnid
- Example which is hard to implement in Apollo: "Please find in local cache
-
🛑 Local resolvers are deprecated in Apollo Client v3. While managing field reads viatypePolicymight be a good replacement for local reducers, deprecation mutations kills important killer feature of current v2 approach - component does not know any data source specifics - both local and remote data mutation are just GraphQL mutations- Important: local resolvers are deprecated, but not removed in Apollo Client v3. No immediate action required
-
🛑 (related to previous one). Yet another reactive system (reactive vars) if we move to Apollo Client v3
Identified requirements for desired solution:
- Should provide as easy as possible migration path from any of following approach or easy way to iteratively move from one to another
- Should provide "default" way for implementing typical tasks (like accessing REST endpoint and having loading flag for that)
- TBD: define "typical tasks"
- Should keep in mind "data sharing" between Vue apps on page
- Should allow efficient management of local data
Research notes
It seems that in general the best and still boring enough solution is to stick to GraphQL-based state manager. We need to have docs or relevant helpers to simplify common tasks (fetching REST endpoint for example, testing GraphQL-based things).
If we keep Apollo as state management solution, following things should be evaluated:
- Complexity of maintaining local mutations if Apollo team decides to remove them
- or have a unified strategy for handling local state updates
- Ability for better management of local state (enumerating cache entries by type as most important one)
- Might be done via custom replacement of
InMemoryCache?🤔
- Might be done via custom replacement of
Other research directions to consider:
- Keep Vuex as main local state manager. Provide "toolkit" on top for reducing boilerplate, integrating with GraphQL and solving major tasks
- Investigate other GraphQL solutions instead of Apollo
- Easy (ideally: automated) migration part is topmost importance