Redux or aurelia-store

so I started looking into redux, per a colleague, then ran into aurelia-store. I’m trying to figure out what I’d lose by using aurelia store, are there any disadvantages to aurelia-store? or pros besides easily integrated?

I’d recommend reading through the official Stores Guide https://aurelia.io/docs/plugins/store
Especially the first section explains the idea whereas the end compares it against known other solutions like Redux.

I’m at bit biased since I’ve created the plugin so I’d really be interested in whether the guide is convincing you :wink:

I’ve read through it, even the section at the end, I’ve also been working my way through it to some extent. I’ve noticed the document often doesn’t show where a reference is actually coming from, or how to actually make use of it (say from the UI). In the end section I’ve noticed that the redux plugins are some 3 years old, so it becomes hard for me to say “this is still relevant”. As far as code simplification, maybe? no examples, all of this seems relatively complicated given the length of the document. as for RxJs vs Redux’s approach, as a person new to this, that doesn’t really tell me anything.

Alright, lets go through this step by step.

great you made it :wink:

Could you give me a concrete example of what you’re referring to with “doesn’t show where a reference is actually coming from”? Most of the examples are simply working with this basic state introduced at the begin.

export const initialState = {
    frameworks: ['Aurelia', 'React', 'Angular']
};

As for the UI part, there really isn’t a lot to it except binding to the results of the current state. What would you expect to see here?

Also right at the begin of the guide you’ll find a link to a samples collection which might be helpful to get started by looking at whole projects.

If you’re referring to the mentioned Aurelia-Redux plugin, yep thats quite old, but there isn’t really a lot that has changed since it’s merely a wrapper. You actually don’t need a plugin at all and can (perhaps even should) use Redux vanilla as it’s described in this article which is also linked in the guide.

State management is a whole new concept to grasp that is true. That is true for every design pattern or architectural concept. Modern state management borrows a few concepts from CQRS which itself is quite a block to get :wink: Sadly said, it’s not something expressed in few sentences I fear and requires reading up on the topic. Talking about reading, I’d like to share this article I’ve written up a long time ago, before the Aurelia Plugin. Perhaps this can shed some more light on what all the fuzz is about?

The difference between RxJS and Redux is a complete different topic as one is a stream manipulation library vs a full blown state-library.

Your SO link seems to be leading to a 404.

2 Likes

Yes I removed it, maybe I shouldn’t have, I figured out that one. On to the next one, how do I update the bound variables (or otherwise) a specific element in a list. The examples in the documentation seem to assume that all of your elements work on the global state.

1 Like

Looking at CQRS / ES for the first time, I thought: “Wait a minute…this seems like the command pattern evolved to something like redux for the backend? With read and write seperation…oh, now i have to learn DDD to grok it.” :smiley:

@xenoterracide
If I got it right, and all this state-management / store-stuff is new to You, I´d like to point to some articles, I come back to every once in a while. This articles are not aurelia-specific, but they´re all about handling state.

2 Likes

And theres a lot of good stuff in this forum of course:

2 Likes

I’ve learned DDD and am familiar with CQS, CQRS and Event Sourcing, I get the benefits.

In my experience on that… “I don’t need it yet”, becomes “requires major refactoring to implement and we don’t have time now” becomes “moving at a snails pace, we think rewriting is a good option” becomes “failed new project”. So I try to implement the things I think I need when starting, even though it’s a bit slower to get moving.

What I was really hoping for here was a detailed breakdown of why aurelia store didn’t use redux, why it’s use of rxjs is better, why it doesn’t seem more integrated, or even what makes redux a better choice than aurelia store, anything that could convince me on why I’d choose one over the other. At a glance they both seem to be event source stores, and suggest some CQS separation. I’d also love to know why rxjs was chosen over the built in event system in aurelia. Basically I get the concepts, I don’t understand the details of the technologies. It might be a bit like saying I understand SQL, but I don’t yet know why I would choose MySQL or PostgreSQL (honestly I don’t know if I can answer this today, as both have progressed to the point of which I can’t answer distinguishing features).

Currently my best plan is, complete my aurelia-store implementation, and later try to do the same thing with redux.

2 Likes

Alright thats something I can work with.

So the reason for RxJS as the backbone is the following:

  • State management is essentially a series of states over time. RxJS is a framework for handling streams. That makes it a perfect fit as you can see an app as a stream of states
  • The observable / subscriber architecture fulfils both an independent producer/subject (BehaviorSubject) and individual subscribers.
  • A subject thereby can emit new states, while the BehaviorSubject also has the side-feature of offering the latest value (aka state) to each subscriber as start value (this is how the initialState is passed)
  • With RxJS also being a toolbox of stream manipulation utilities you have everything you need to determine HOW you want to subscribe (e.g skip every nth, merge two streams, just take the first …)
  • You can easily create new derived streams, aka mapping state values to a series of individual props.
  • You can directly bind to resulting subscriptions/observables via e.g https://github.com/zewa666/aurelia-async-binding
  • It eliminates the trouble with async dispatches as the stream emits once the previous action resolved

That is what you get pretty much for free by using RxJS, which in my opinion is already a killer subset compared to vanilla Redux. E.g the async behavior is what generates endless issues with Redux where you have to decide for either thunks, sagas or other concepts. Also the subscription mechanism is plain simple and the ability to subscribe directly to the stream is something that shouldn’t be undervalued when comparing the two.


Now what Aurelia Store adds on top of these is the following:

  • A deterministic async queue. That means the order of dispatched actions is preserved by waiting until those resolve. No race conditions !!!
  • By having a reliable stream of actions we can now also add middlewares that operate Before or After the actual action gets executed (contrast this with only post middlewares in redux)
  • Building on top of that, you get free history support with navigation over previous/future states
  • Pipe-able actions -> instead of having to push x dispatches one after another, you can group multiple of them in one go with a piped dispatch

I’m not listing the usual suspects such as rehydration, perf logging or DevTools support as they work pretty much everywhere the same.

So I hope that gets you a bit further with your analysis.

I still don’t get what you mean with “why it doesn’t seem to be more integrated” could you elaborate on that? What additional integration would you foresee?

EDIT: no matter what, I’d anyways recommend you try out the same thing with Redux afterwards, so you really get a good feeling of how things differ.

7 Likes

@xenoterracide I didn’t hear back from you on this one. How is your journey progressing with Store vs Redux? Did my explanations from the last answer help you in any way?

1 Like

they helped, my current journey is that I haven’t tried redux yet, but I have had some interesting discussions with people about reducing the boilerplate required for store.

here’s the relevant pieces

xenoterracide: @Fred you were asking yesterday for an example of where store could be simpler, this is what I was doing to do the same thing with store

import { autoinject, bindable, customElement } from 'aurelia-framework';
import { connectTo, Store } from 'aurelia-store';
import { produce } from 'immer';
import { pluck } from 'rxjs/operators';
import { State } from 'State';

import { NavBarState } from './NavBarState';

@connectTo<State>({
  selector: (store) => store.state.pipe(pluck('gui')).pipe(pluck('navbar')).pipe(pluck('burgerIsOpen')),
  target: 'burgerIsOpen',
})
@autoinject()
@customElement('nav-bar')
export default class NavBar implements NavBarState {
  private static readonly BURGER_STATE = "burgerState";

  @bindable() burgerIsOpen = false;
  constructor(
    private readonly store: Store<State>,
    ) {
    store.registerAction(NavBar.BURGER_STATE, storeBurgerState);
  }

  burgerClicked() {
    this.store.dispatch(NavBar.BURGER_STATE, !this.burgerIsOpen)
  }
}


const storeBurgerState = async ( current: State, burgerIsOpen: boolean ) => {
  return produce( current, state => {
    state.gui.navbar.burgerIsOpen = burgerIsOpen;
  } );
}

@fjanon

Feels a bit React-ish with the amount of boilerplate. Should be doable with a binding behavior or something along those lines. Maybe like this?

<a id="burger" role="button" click.delegate="burgerIsOpen = !burgerIsOpen & register:'burgerState'">

@CuddleBunny on doing it with react

basically something like this:

// action-types.ts

export const BURGER_STATE_ACTION = "burgerState";

// actions.ts

export const updateBurgerState = (newBurgerState) => ({
    type: BURGER_STATE_ACTION,
    data: newBurgerState
});

// reducers.ts

export function uiReducer(state = initialState, action) {
    return produce(state, draft => {
       switch(action.type) {
           case BURGER_STATE_ACTION:
                Object.keys(action.data).forEach(key => {
                    draft[key] = action.data[key];
                })
            default:
       } 

       return;
    });
}

// nav-bar.ts

@autoinject()
@customElement('nav-bar')
export default class NavBar implements NavBarState {

  @bindable() burgerIsOpen = false;

  constructor(private readonly store: MyStore) {
    this.unsubscribe = this.store.subscribe(() => {
        this.burgerIsOpen = this.store.getState().gui.navbar.burgerIsOpen;
    });
  }

  burgerClicked() {
    this.store.dispatch(BURGER_STATE_ACTION, !this.burgerIsOpen)
  }

  unbind() {
      this.unsubscribe();
  }
}

I don’t think this is significantly less boilerplate.

also discussed with @fjanon

Yeah there are plans to properly integrate router and store. There’s already the idea of stateful routes / route history that @jwx has been working on. A proper store integration to allow bindables to become part of state history, combined with the same thing for navigation actions, could result in a nice coherent experience there

my current journey in A2, is wait, and hope that easy things become easy and hard things remain possible.

2 Likes