For the last couple of weeks, I've been struggling to wrap my head around state management in a Single-Page Application (SPA). Or, perhaps more specifically, I've been struggling to understand how a library like Redux can be used to make state management easier to implement and to reason about. Last week, Matt Busche suggested that I look at Human Redux by Henrik Joreteg. This book had been helpful to Matt and he thought that it might clarify some things for me. Which it did; but, perhaps not exactly in the way intended. What Henrik Joreteg's book made me realize is that I've been approaching the problem a bit backwards - rather than trying to figure out how to use Redux to solve my state management problem, I should be figuring out how to create an abstraction that solves my problem; and then, see if Redux is the right implementation under the hood.
| || || |
| || |
| || || |
As I've been noodling on state management, I've tried my best to ignore the fact that I love Angular and that most people who use Redux love React. I didn't want this investigation to be tainted by framework choice; but, this approach has put me in a somewhat contentious mindset. See, one of the most powerful facets of the Angular platform is its dependency-injection (DI) abilities. And, by not acknowledging dependency-injection as a core tenant of the application architecture, it has made it hard to understand the "complexity" of the way in which Redux applications are wired together.
Since Redux isn't tied to a particular platform or framework, it can't make assumptions - it can't rely on dependency-injection. As such (or more likely by choice), it relies heavily on functional programming paradigms where various objects get partially applied to other partially applied objects which eventually get invoked and leverage lexical bindings in order to implement stateful behaviors.
Essentially, partial application does with functions and lexical bindings what classes do with instance properties. It's a different mechanism, but the outcome is the same: some execution context uses bound state in order to implement some behavior.
Now, to be clear, I suck at both classical programming and - especially - functional programming. My intent here is not to get into the strengths and weaknesses of different programming methodologies. My intent is only to say that by not fully embracing an Angular-oriented, dependency-injection view of the world, it has made it especially hard for me to reason about the choices being made in a Redux application.
But, put a pin in that for the moment.
Throughout Human Redux, Joreteg makes it a point to extol the power of "abstractions:"
Also, if down the road we decide that adding a todo should trigger an asynchronous fetch, the code that is calling the action creator doesn't need to change. Using action creators allows us to expresses the intent without having to worry about the implementation; this includes caring about whether it is async or not. So even if it has to make 50 different API calls that will re-position satellites in orbit before finishing, the intent has not changed. That's the power of abstraction. (Kindle location 1207)
In this case, he's talking about the abstraction of the action creator; but, in other portions of the book, he talks about the power of abstraction in terms of selectors. This all comes to a head - for me - towards the end of the book when Joreteg details the way in which he decorates his Stores. Essentially, he takes all of his action creators and all of his selectors and adds them to the key-space of the Store API.
Henrik you're nuts, you'll make a mess of the store instance! It does sound a bit messy, but with a bit of convention, it's quite manageable. Sure you'll have a bunch of methods on the store instance, but they'll either start with "select" or "do" which keeps things quite tidy. Plus, as it turns out, this makes a lot of other things way less messy. (Kindle location 4168)
At this point, what Joreteg has is an object (Store) with private state that exposes public methods for accessing (selectors) and mutating (action creators) said private state.
And this is where it all sort of clicked for me. What Joreteg created was an abstraction that completely encapsulated the very notion of Redux as the state container. Essentially, what he ends up with is a "strategy" object whose implementation could be swapped out with another implementation that doesn't use Redux at all.
Now, circling back to my earlier tangent about the differences between Angular and React, you don't even need to squint to see that, in an Angular application, this would simply be a Service class with private state and public methods that implements a "strategy" interface. In fact, it looks exactly like the Facade pattern discussed by Thomas Burleson and the Sandbox pattern discussed by Brecht Billiet - both of which are discussed in the context of Angular.
At this point, I want to share two other excellent pieces of insight that Joreteg puts in Human Redux:
If you don't actively fight for simplicity in software, complexity will win... and it will suck. (Kindle location 2283)
Many times, we over-engineer by creating generic solutions that include solutions for problems we don't actually have. (Kindle location 2863)
All together, what this helped me to realize was that I've been trying to implement a "React pattern" inside an "Angular application." And, by not fully embracing the fact that I'm in Angular - a completely different context - I'm creating code that is far more complex than it has to be. By not aligning my technique with my context, I'm not fighting for simplicity - I'm incurring complexity.
In other words, by starting with Redux and trying to shoehorn it into an Angular application, I'm starting at the wrong end of the plan. What I really should be doing is starting with the abstraction, constructing it in such a way that is Angular-friendly, and then seeing if Redux can be a helpful implementation detail.
You can tell that this is a decent conclusion because it doesn't change the benefits that Joreteg points out in the book:
.... when you have this level of separation between app functionality and the UI you can run the entire "app" in "headless" mode if you want. You don't even need to have built the UI yet, and you can still "run your app." It will trigger fetches, update URLs, etc. If you can do that, it's not hard to see how it would be much easier to make significant UI changes. (Kindle location 4025)
While my review is quite "meta" in so much as I talk more about how it made me feel than about what kind of content it had, the Redux-specific content of Human Redux was well done. Henrik Joreteg's explanations were straightforward and incremental. I don't agree with all of his choices (like the idea of the Store driving the URL); but, a lot of what he said made sense. Well, at least as much sense as anything else that uses functional programming patterns. If nothing else, I got a lot of value out of this book, even if it wasn't exactly the kind of value that Joreteg envisioned.
Take a look at Akita
I find it is easier to grasp than ngrx but still uses the redux mindset
It's also built for angular which means it uses di in it's favour.
Thanks for the suggestion -- a few people have suggested Akita now. And, the basic Store looks like its very similar to some experimentation that I've done so far. I'm sure I'll loop back to it. Just want to pick up some pieces first :D
After noodling on this some more, I'm starting to codify my mental model from the other side -- ie, the "problem" not the "implementation":