Redux store should be one for each module or one for whole application comprising of a multiple mudules

Viewed 837

I am integrating or sort of rewriting my Angular 1 application with Redux. It has the following architecture:

app1 => App1 running on diff subdomain app2 => App2 running on diff subdomain and so on... shared => Shared modules(with multiple components) b/w app1 and app2

An app is made of multiple modules specific to it.
Shared directory has one or many modules that are shared between two or more apps

So my question is should I have app level store or a module level store.

By app level store and module level store I mean, for example:

  1. Let's say we have a shared module SM1 which is shared between two apps.
  2. SM1 should use the store of app1 when used inside app1 and should use the store of app2 when used inside app2. But a module should be a self-sustained entity and it should not rely on any other thing for its store.
  3. Or the SM1 could have its own store not sharing with any app. But when the SM1 is used inside app1 will its store conflict with the store of app1.
  4. Also if I use a module level store then the module and the app both need to be provided with the ngRedux dependencies which seem kind of redundant.

What is the best solution here in terms of a scalable architecture and redux core principles?

2 Answers

redux-subspace was created for a very similar use case (in fact, we have also been migrating 2 seperate apps from angular 1 to react/redux with shared components between them). It allows you to have a single store and isolate a section of it for your shared components.

The basic concept is that the parent app combine's the component's reducer into it's store and then sub stores are created that return a reduced state tree and namespace dispatched actions.

Note: I have not use angular w/ redux and I have no idea how you integrate the two, so I'm only going to show how you can create the sub-stores and hope it works for you (if you do get it working, I'd love to know so we can expand our supported frameworks in our docs).

import { createStore, combineReducers } from 'redux'
import { subspace, namespaced } from 'redux-subspace'

const component1 = (state = { value: 1 }, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { ...state, value: state.value + 1 }
        default:
            return state
    }
}

const component2 = (state = { value: 10 }, action) => {
    switch (action.type) {
        case 'DECREMENT':
            return { ...state, value: state.value - 1 }
        default:
            return state
    }
}

const reducer = combineReducers({
    component1: namespaced('component1')(component1),
    component2: namespaced('component2')(component2)
})

const store = createStore(reducer)

const component1Store = subspace((state) => state.subApp1, 'subApp1')(store)
const component2Store = subspace((state) => state.subApp2, 'subApp2')(store)

console.log('store state:', store.getState()) // { "component1": { "value": 1 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 1 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

Now dipatching actions into those sub-stores will also only affect their state

component1Store.dispatch({ type: 'INCREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

component2Store.dispatch({ type: 'INCREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

Notice that the INCREMENT action dipatched into the component2Store did not change the state of component1Store. The situation is reversed if we dispatch a DECREMENT action

component1Store.dispatch({ type: 'DECREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 10 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 10 }

component2Store.dispatch({ type: 'DECREMENT'})

console.log('store state:', store.getState()) // { "component1": { "value": 2 }, "component2": { "value": 9 } }
console.log('component1Store state:', component1Store.getState()) // { "value": 2 }
console.log('component2Store state:', component2Store.getState()) // { "value": 9 }

At this point, you would need to work out how to inject these sub-stores into your shared components.

Hope this is useful for you.

Disclaimer: I am the author of redux-subspace

I would go for loose coupling as much as you can:

  • app1 - own store (integrates sm store)
  • app2 - own store (integrates sm store)
  • sm - own store
Related