In recent years I’ve observed a strong trend towards using reducers in software architectures. While this is not a new pattern, its recent prominence in React / Redux and other platforms offer benefits to software quality worth discussing.
In this article I’ll go over the role of reducer functions in state management and talk about some of the main benefits it offers. Finally, I’ll close by discussing some tradeoffs I’ve seen in Reducer-centric architectures.
I’m going to leave discussion of Model View Update (MVU) to a future article in order to keep this article narrowly constrained to the idea of using reducer functions for state management.
Reducers 101
Let’s look at an example reducer from an Angular NgRx application:
And another from an F# Elmish.WPF application:
Both of these examples illustrate various flavors of reducers, but both take in a starting state and an action and return a single new version of that state.
At it’s core, this is what a reducer does.
In this article, we’ll explore what’s so powerful about this and the problems this helps solve.
Meeting Reducers for the First Time
A few years ago I was doing some very heavy single page application (SPA) development in Angular. I built an Angular Single Page Application. And then another. And another.
I loved the framework (and still do), but pretty soon I began to notice difficult to manage complexity when working with multiple asynchronous operations at once.
This is a point that many in the Angular community have gotten before where rare state management bugs emerge and the order of operations and network latency can introduce a large degree of complexity.
The Angular community rose to the challenge with some reducer-based state management libraries like NgRx. This library was based off of the popular Reduxstate management library commonly associated with React.
Note that Redux is commonly associated with React because both are managed by Facebook, but Redux isn’t actually part of React. Put simply, Redux is a powerful reducer-based state management library for JavaScript applications. It’s commonly seen integrated in React via the React-Redux library, but there’s nothing intrinsically React-specific about Redux itself.
Based on what I now know about reducers, the shift in the Angular community to use reducer-based state management systems once state management hits a certain complexity threshold is the right one.
Power to the Reducer
Let’s take a look at why reducers are so good for software quality.
Pure State Transformations
Instead of relying on repository classes that hold ever-changing state values, reducers are pure functions that take in an action and a previous state and output a new state based on those inputs.
The term pure function means that the function can be called forever with the same inputs and always return the same output without having side effects on anything else.
This concept is extremely important to understanding the quality benefits of a reducer function.
Because a reducer is all about repeatable state transformations given specific inputs, it is incredibly easy to test.
Centralized State Management
An interesting aspect of reducers is that it puts all application state in one centralized place.
This makes it easier to look at the entire state of the application, but more importantly, it moves any manipulation of the application state into a central place. This removes the doubt as to what parts of your application are modifying your state.
This improvement is very important because a surprising number of bugs arise from inconsistent behavior in state management.
Debugging Capabilities
On top of that, if a bug arises, all you need to know is the inputs to the reducer function in order to be able to recreate and resolve the issue.
By logging state before and after reducer functions, debugging is exponentially quicker in scenarios where you are uncertain which operation resulted in arriving in an invalid state.
Note here that I’m not advocating for a specific reducer-based library or technology, but more the pattern in general.
Reducer Drawbacks
If you’ve been around in the technology world for awhile, you know that any decision has pros and cons associated with it. I can’t advocate for reducers without discussing common pitfalls and drawbacks associated with them.
Let’s take a look at those potential drawbacks now:
- Learning Curve – Reducers are a little different and have a mild learning curve associated with them – particularly when starting a new project without patterns in place to emulate.
- Boiler Plate Code – Many reducer-based frameworks I’ve looked at have at least a little bit of what I would call boiler plate code. This is code that has little reason for existence other than the framework requires it. It’s hard to get into this without looking at a specific implementation, so just know that you may need to write some repetitive code to integrate reducers into an existing framework.
- Complexity – Given the complexity and overhead of reducers, they don’t necessarily make sense for small applications or applications that don’t rely much on state manipulation. Just like you don’t need a moving truck to go to the grocery store, reducers don’t always make sense in small applications.
- Large Reducers – If your reducer grows to a point where it has a lot of potential state operations, it can become a fairly large method. There are answers to this like extracting methods out for complex transformations (which I recommend).
Additionally, depending on the flavor of reducer framework you’re using, you can have multiple reducers or nested reducers. This makes things a bit more complex, but also keeps methods small and manageable.
Closing Thoughts
Overall, I am pleased with the shift towards reducers in recent years. Their construction and design makes it hard for bugs to hide.
The main drawbacks I’m seeing are around the initial mild learning curve, a rise in boiler-plate code associated with reducer-based frameworks, and added complexity.
We continue to innovate as a programming community – particularly around JavaScript frameworks. I have a high degree of confidence that over the next 5 years we will see frameworks innovate solutions around more of these common problems, reducing the drawbacks of reducers even further.
Stay tuned as I talk about the reducer’s role in Model View Update (MVU) frameworks in future articles.
If you’d like to play more with reducers in the meantime, a good starting point for JavaScript developers would be checking out Redux.
The post Rise of the Reducer Pattern appeared first on Kill All Defects.
Top comments (5)
Just want to mention that this comes from the concept of a fold/reduce in functional programming. For example, in F#, you could process a stream of messages using
fold
:Love it. I think you can put
fs
after the triple apostrophes to get F# syntax highlighting there.And yes, I'm doing a ton of F# programming recently, which inspired the post on reducers at large.
Thanks. I actually had to use “fsharp” to trigger syntax highlighting.
Nice article! I’ve used both redux and ngrx extensively and must say that while I appreciate the effort of the ngrx team it feels so unintuitive compared to the pure redux implementation.
A colleague once told me to only use redux for state that needs to be accessible elsewhere or fetches data and keep all the rest as local state. While not necessary I really like how this simplifies debugging and keeps the reducers from growing out of control.
That's a good tip, though it does get rid of some of the centralized aspects.
There are some lighter weight Angular state management libraries out there that may be more of a happy medium as well, but I've not looked into them yet.