Evaluate state management strategies for frontend

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

  • Close this issue

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

Example: https://gitlab.com/gitlab-org/gitlab/blob/master/ee/app/assets/javascripts/geo_nodes/store/geo_nodes_store.js

  • ✅ 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

Example: https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/create_cluster/eks_cluster/store/index.js#L23

  • ✅ 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 A should dispatch action, Component B silently depends that data, requested in Component A is already in store)
  • ❓ We are heavily using namespaced modules. Are this is just a way to maintain several "logically separate" stores under the same instance of Vuex.Store or 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

Example: https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js#L15

  • ✅ 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 using readFragment
    • Example which is hard to implement in Apollo: "Please find in local cache User with username john and set his todoCount to 3. You can only do that if you (somehow) know john id
  • 🛑 Local resolvers are deprecated in Apollo Client v3. While managing field reads via typePolicy might 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? 🤔

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
Edited Sep 26, 2025 by 🤖 GitLab Bot 🤖
Assignee Loading
Time tracking Loading