Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document an alternative approach for Vuex modules #48

Closed
zewa666 opened this issue Jul 26, 2018 · 12 comments
Closed

Document an alternative approach for Vuex modules #48

zewa666 opened this issue Jul 26, 2018 · 12 comments

Comments

@zewa666
Copy link
Member

zewa666 commented Jul 26, 2018

Recently the question about how one can handle large states, or more specifically, how one can leverage a similar system to Vuex modules is popping up all over the places. Here's an example from discourse

Now I do have some thoughts about it, and replied in the topic as well, yet I have to say that I haven't used Vuex to that extent to be able to fully grasp what happens below the surface with modules.
It feels more like a very Vue oriented paradigm and something that could be also achieved with general concepts of RxJS.

So I'd like to get the discussion started about what everyone thinks, would be an use-case that is easily solved with modules and how this could be done with what Aurelia-Store offers for now.

It would be great to come up with a larger sample as well in order to demonstrate the concepts even better. So I'm happy about every contribution.

@serifine
Copy link

On the topic of Vuex modules, I think the attractiveness is the idea of being able to break your store up into smaller self contained chunks. I am not familiar either but it sounds like it still has a single store.

Due to using a single state tree, all state of our application is contained inside one big object. However, as our application grows in scale, the store can get really bloated.

To help with that, Vuex allows us to divide our store into modules. Each module can contain its own state, mutations, actions, getters, and even nested modules - it's fractal all the way down:

On a smaller scale I do not think this is much of an issue. I have a scenario with an application where the intent is to manage the architectural data for several organizations. This data, and the resulting screens to manage the data can nest very deep. Here is a simplified structure, and each node might have edit modes / pages or link back to other parts of the application.

├─ Account
└─ Organizations
   ├─ Organization Management
   ├─ Organization Dashboard
   ├─ Model Management
   │  ├─ Model Management Dashboard
   │  ├─ Master Element List
   │  │  ├─ View List
   │  │  ├─ Review New Additions
   │  │  └─ ...
   │  ├─ Upload
   │  │  ├─ Upload Details
   │  │  └─ Upload Elements
   │  │    ├─ View Element
   │  │    │  └─ ...
   │  │    ├─ View Related Master Element
   │  │    └─ ...
   │  └─Upload Review
   │    ├─ Review Details
   │    ├─ Failed Item List
   │    └─ Failed Item Details
   │       └─ ...
   └─ Projects
        └─ ...

On the subject of a new sample, I think a shopping cart could be a good example.

You have a user, who can be a guest or be logged in.
You have products to shop for.
You have a shopping cart to store the items you wish to buy.

├─ User
├─ Products
│  ├─ add product
│  ├─ search products
│  └─ remove product
└─ Shopping Cart
   ├─ add to cart
   ├─ update quantity
   └─ remove from cart

@jmezach
Copy link

jmezach commented Jul 26, 2018

This is an interesting topic that I would like to chime in on. We have a fairly large enterprise application build with Aurelia, which we have modularized into separate components that are being developed and deployed independently (see this talk on how we're doing this).

We've recently started implementing aurelia-store into our application and we've been wondering about how we're going to manage the state of the individual modules. For now, we've settled on simply defining an interface for the state in each module, which derive from an interface defined in the hosting application. That way at least we have some compile time checks to avoid sharing state between modules.

That being said, the modules approach does sound interesting. We haven't quite figured out how we're going to clean up state for example, as we move between modules. Keeping all that state in memory for no reason feels wasteful.

@zewa666 zewa666 changed the title Document and alternative approach for Vuex modules Document an alternative approach for Vuex modules Jul 26, 2018
@zewa666
Copy link
Member Author

zewa666 commented Jul 26, 2018

So as far as I understand one of the benefits with VueX Modules is that you can define actions, a slice of the whole state and potentially even middlewares (plugins in VueX) per module.

My idea how to target these things in a larger app, as an example based upon @ZHollingshead's shopping cart, perhaps even with a modularized approach like @jmezach mentioned, could look like the following:

One single state

I'd still put everything in one state object, which seems to be the same thing VueX does internally as well. So it could look like:

// state.ts
export interface State {
    // extendable state slices
    [slice: string]: StateSlice,

    // some global state properties used for the host app
    activeTheme: "light" | "dark",
    currentPage: string; 
}

export interface StateSlice {

}

State slices

Now every module defines it's own slice of the state, which gets mapped to a property of the whole state.

// product/product.ts
export class Product { name: string; description: string; }

// product/state-slice.ts
export interface ProductsStateSlice extends StateSlice {
    products: Product[];    
}



// shopping-cart/shoping-cart-item.ts
export class ShoppingCartItem { product: Product; quantity: number; }

// shopping-cart/state-slice.ts
export interface ShoppingCartStateSlice extends StateSlice {
    items: ShoppingCartItem[];
}

Modules register their custom actions / middlewares

Since Aurelia-Store is built in a way where you can dynamically register/unregister actions and middlewares, once a module gets started do the registration and on unload unregister them again.

// product/actions.ts
const initialSliceState:  ProductsStateSlice = { products: [] };

export function registerProductSlice(state: State) {
  return Object.assign({}, state, { product: initialSliceState });
}

// imaginary main entry of the product-module.ts
import { Store } from "aurelia-store";

function registerModuleInWhateverWay(aurelia: Aurelia) {
  const store = aurelia.container.get(Store);
  store.registerAction("registerProductModule", registerProductSlice)
  store.registerMiddlware(...)
}

Module registration of the slice

Depending on how you structure your app, either each module is an aurelia.feature or dynamically loaded with other means. Nevertheless there is one point in time where it registers with Aurelia's DI and at that time it could update the global state, by dispatching a new state and adding the modules state slice as property.

function registerModuleInWhateverWay(aurelia: Aurelia) {
  ...
  store.dispatch(registerProductSlice);
}

Obtaining only the slice as the whole state per module

If a module is only interested in it's own slice as the sole state, a module would subscribe to the following:

// imaginary main entry of the product-module.ts
@connectTo({
  selector: (store) => store.state.pluck("product"),
  target: "state"
})
class ProductModule {
  private state: ProductsStateSlice;
}

This way the whole state for the module is only the respective slice. If both, the slice and whole state are required we could do something like:

// imaginary main entry of the product-module.ts
@connectTo({
  selector: {
    state: (store) => store.state,
    productSlice: (store) => store.state.pluck("product")
  }
})
class ProductModule {
  private state: State;
  private productSlice: ProductsStateSlice;
}

Cleaning up after module unload

Once a module gets disposed/unloaded, we would dispatch a new state and unset the slice

// product/actions.ts

export function unsetProductSlice(state: State) {
  const { product ...withoutProduct } = state;

  return withoutProduct;
}

That way the overall state now does not contain anymore the unnecessary slice

@zewa666
Copy link
Member Author

zewa666 commented Jul 31, 2018

any concerns or ideas with regards to above example @ZHollingshead and @jmezach ?

@jmezach
Copy link

jmezach commented Jul 31, 2018

@zewa666 I think I like this approach, this keeps things nice and clean.

@dannyBies Could you have a look at this as well? I think this could be useful for our scenario, although we need to figure out what would actually trigger unloading a module in our case.

@dannyBies
Copy link
Contributor

My initial thoughts are that the above examples seem good to me. I do think it would be nice if you could scope middleware/actions on the module level to keep the modules clean and separated from each other.

In the application me and @jmezach are working on we have a modularized approach in which users may end up seeing just one of the modules and never interacting with the others. Scoping the interactions to a module instead of the whole state sounds like an idea worth exploring. I think it could help keep larger applications more organised.

@zewa666
Copy link
Member Author

zewa666 commented Aug 1, 2018

@dannyBies what would be the effect of scoping though? As far as I see it from Vuex, actions would then only operate on a substate and thus would return a substate, which gets merged with the fullstate. Aside from that I do not see any difference. Wonder what you're exactly thinking about. Maybe you could provide a sample in pseudo-code or whichever way suites you.

@dannyBies
Copy link
Contributor

Ah I misunderstood, in cleaning up a module we can of course unregister the middleware/actions related to the module. This approach looks good to me!

@serifine
Copy link

serifine commented Aug 1, 2018

@zewa666 I looked at this and probably over-analyzed it to come to the conclusion that I really like this approach. I think it covers all the major points that I liked about Vuex and seems like something I would enjoy using.

@zewa666
Copy link
Member Author

zewa666 commented Aug 1, 2018

Awesome, now what really would be cool is to actually build this minimal sample. I'm not sure I can get to it in next time so if anybody wants to take the lead on this it would be very appreciated.

@zewa666
Copy link
Member Author

zewa666 commented Aug 18, 2018

Ok, I took some time today to play around with it. Here's a sample https://stackblitz.com/edit/aurelia-typescript-y3x3ca

Especially take note of my fantastic design skills 🤣

@zewa666
Copy link
Member Author

zewa666 commented Aug 25, 2018

seems like we're good here. Closing this for now

@zewa666 zewa666 closed this as completed Aug 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants