I’m learning about the new aurelia-store as a consideration for use for a new project having little prior background knowledge regarding other store solutions. I’m a backend developer in nature who does occasional frontend. I’m having trouble understanding some of the concepts and design choices regarding the aurelia-store and store solutions in general and would love to get some feedback on my own ignorance and/or maybe even start a discussion or guides towards some alternatives.
Primarily I’m having trouble understanding why a single global state and why it is implemented in the way that it is. A general trend on the web is to move towards microservices where each microservice owns its data and thereby state. Mutations are performed based on events and actions.
For example: The .NET Orleans framework support EventSourcing however this is an implementation detail of the microservice. If the service decides to use EventSourcing, Its actions can raise events that will mutate its state and it is able to replay those events and expose them to the outside world in whatever way fitting.
Within the rest of this post I look at a well done microservice architecture as the counterpart to using aurelia-store.
Why there is only one
With aurelia-store, I’m encouraged to register the store before startup with an initial state as a global store. No matter how many modules my app may be having, all of them have unrestricted access to all data with the ability to mutate in any way, shape or form (immutable or not). Although I’m somewhat able to trace who mutated what, it still seems like a mess to me.
The docs state an advantage:
here is only one source of truth for your data and all modifications happen in a predictable way
That may be true if you’re a single developer on a project and you’re somehow immune to introducing bugs, otherwise you’re bound to accidentally mutate previous state sooner or later, but to a more important note: This is not predictable at all, If I’m Service A and I owe some data, if that data is shared globally, anyone can touch it really… Possibly by accident with aurelia’s two-way databinding being an obvious candidate for trouble.
The docs state:
Next, we need to register the created action with the store. That is done by calling the store’s
registerActionmethod. By doing so we can provide a name which will be used for all kinds of error-handlers, logs, and even Redux DevTools. As a second argument, we pass the action itself.
This seems counter to aurelia’s design philosophy. When calling dispatch with an action (function, not name), that implicitly provides the name of the function as well whatever else I’d like to log (like the module that dispatched that action), what is the added benefit of having to register functions? I know when an action has changed by simply comparing the reference. Calling functions by name seems one use case however that would not be a good reason to force all functions to be registered. In a test project on my end I’m having my actions scoped to a module- then I’m having to run a registerActions method from somewhere within my apps bootstrapping logic to register each modules set of known actions (of course hoping to not forget to register an action somewhere). Finally and to be on the safe side, I should also unregister actions… Why? When they are out of scope or inaccessible for me, that should be it right? What am I overlooking?
Services and actions or actions and services or actions
If I accept the above for my own ignorance of not realizing the full potential with this architecture then I’m still confused on how to use it. I have my ideas however the docs and samples seem to counter those.
To portray a scenario: I have an API who’s result should be written in the store so that the app can react on it. Traditionally I would have a service that encapsulates that API call and either or both returns the value and/or dispatches an event using some mechanism like the event-aggregator or RX to which other components then could listen and react. With the Aurelia-store I would do pretty much the same except for rather than returning/publishing an event, I would dispatch an action on the store from within the service/caller with the mutation returned as a response from the API call.
In the github repo called aurelia-store-examples there is the
Services demo of how to combine services and store actions (where it has a remark stating that Services are ‘Old school’ ). Within this demo, actions are used to trigger the http call through a service (Which smells like a bug since the service is potentially pulled in from DI before the app has a chance to boot). This of course flips things around, now actions are responsible for much more than updating the store. Actions, who by nature are just functions with no real lifecycle on their own. This feels like an anti-pattern to me but perhaps I’m missing things again.
One of the advantages of using a state management solution like this that keeps popping up is time travel (being able to rollback events). Aurelia-store does a nice job in providing us with the ability to look back in time. It would just make much more sense if services owned their own stores, that way we could opt-in to this feature per service depending on needs. Now we basically cant use it in production because eventually history will overflow.
I like to go out of my way of getting to understand the next ‘hot’ thing- even though its occasionally a trap. Central state management has its benefits but we’ve discovered those decades ago, that’s why we’re still using N-tier today. Apart from some develop convenience for having to do less in order to support time travel, I fail to see the benefit of having a central store- except for some edge cases. I’d like to dive a bit deeper into this and learn more about what I’m missing or your opinion.
I’m also considering launching an opinionated aurelia setup where I explore the ‘Old school’ approach to setting up a project considering interdependencies and even time travel. If you’re interested in that, please comment below.
Thanks for your time!