Simplifying Redux Architecture

THE PROBLEM

Tucker Connelly
5 min readAug 3, 2016

Hey, so you’ve got a sweg Redux app. You’re using redux-api-middleware to connect to the backend, and normalizr to normalize the results. Great, let’s add the concept of a user.

Can’t even bring myself to write these, the point is, lots of files ;)

src/actions/user.js

src/constants/actionTypes/user.js

src/reducers/user.js

src/schemas/user.js

src/components/User.js

test/actions/user.js

test/reducers/user.js

test/components/User.js

That’s a lot of modules for one concept! Oh, what’s that? We need to add the concept of a Comment now? Okay…

src/actions/comment.js

src/constants/actionTypes/comment.js

src/reducers/comment.js

src/components/Comment.js

test/actions/comment.js

test/reducers/comment.js

test/components/Comment.js

Whew! Okay, work done…finally I can clean up my tabs and close all these files.

…Oh we need a list of comments. OKAY. GREAT. LET’S DO THAT. I LOVE THIS.

Opening…

src/actions/comment.js

src/constants/actionTypes/comment.js

src/reducers/comment.js

src/components/Comment.js

test/actions/comment.js

test/reducers/comment.js

test/components/Comment.js

What’s going on here?

Let’s get theoretical, shall we? What even is a module? If you look at the generated source from webpack:

webpack lists out all of your modules and replaces `import` with `__webpack_require`, which looks like this:

It basically puts each module into an anonymous closure function, puts that function into a cache, and uses the cache when the module is re-required.

What does this mean?!

It means that, modules are singletons!

Yep, singletons. Are you wincing yet? Classes and collections of functions probably are a good use-case for singletons, but if you ever create a local variable in a module, you’re essentially creating global state, which is a massive stinking code smell.

With that in mind, let’s discuss one of the fundamental problems with this vogue lust for functional programming in JS.

Primitives Obsession in Javascript

Another one of the code smells Martin Fowler outlined in Refactoring was primitives obsession, an over-reliance on primitives. This obviates any sort of useful abstract concept and keeps you tied to, well, concrete primitives. Instead of programming at the problem domain, you’re always kind of programming in the algorithmic domain.

When you create a separate file (sorry, global singleton) for action creators as well as another for the reducers, you’re using algorithms and data structures instead of encapsulating those very algorithms and data structures into some sort of more useful higher-level concept. You’re never working with a user, you’re working with a user action-creator. Ew.

To put this in terms a sweg functional JS programmer can understand, you’re programming imperatively instead of declaratively.

I have a problem with redux itself, too.

Redux is global state, and global state is a code smell.

You can doll it up with immutability (*cough* pass-by-value), say, nah it’s functional programming bruh. Nah bruh, it’s context, have you heard of React’s context feature?

At the end of the day, redux is global state, and global anything is a code smell¹.

So yeah, you’re basically using singletons to modify global state. Martin Fowler just had a heart attack.

There are a few valid use-cases for global state in modern apps, that I can count on one hand:

  • Site-wide notifications
  • Opening some sort of modal/overlay
  • And, of course, storing local data from a persistance layer

The biggest one is the third one, and loads of tools have come in to support redux in this (normalizr, redux-api-middleware, yada yada). However, Relay does this better because it encapsulates all the imperative, hard work of maintaining a local store, so you can just declaratively say I want this data, figure it out!

BUT, we live in a redux world currently with legacy REST apis. How will we ever ease our suffering?

Just admit you’re using singletons

“Hey, so we’ve got the methods for this concept in one module, and the data they operate on in another module.”

The obvious solution to me is to colocate related concepts. If you put everything related to a user in a single file (reducer, action creators, AND constants), it will reduce maintenance costs. And it has, for me, in practice.

This is essentially a class, with getUser as the interface.

Now every time I’m dealing with a user, I don’t have to open up 8 different files to get anything done, I only have to open up this one file. If you think of the action creators as the interface to this module, you can deal with the concept of the user at a higher level.

Another theoretical concept this hits on, to nestle back into pedantry, is the idea of span and live time.

span is how close individual variable references are to one another. If you declare variable a on line 1, and use it again on lines 3 and 5, it has an average span of 2 lines (5 - 3 = 2, 3 - 1 = 2).

live time is the total number of lines a variable is alive for. The live-time of variable a is 5 lines if it’s never used again after line 5.

To my knowledge, there haven’t been any empirical studies on the effects of span and live time on maintainability, but it’s a nice way of thinking about the complexity of a file. What even is the span and live-time if you separate out reducers and action-creators into separate modules? The world may never know.

ANYWAYS, let’s use this “store” module in an actual component:

This is pretty darn close to Relay’s declarative nirvana². The only difference is you have to imperatively fetch the data in `componentWillMount`.

Alright let’s wrap this up. To recap: any time you create a module, you’re essentially creating a singleton, which is one step removed from global state. Redux is global state, and it’s very easy to misuse. The footprint of redux in your app should be really small, and it should feel like an app that uses redux, instead of you building a redux app. Strewing action-creators and reducers about entangles the app in redux, and makes it difficult to transition to something else (like Relay) in the future. So my proposed solution is to co-locate reducers, action creators, and constants in a single file, which has, in practice, reduced maintenance costs for me.

Aight, thanks ya’ll!

¹ To be fair, redux is essentially a variation on the Chain-of-Responsibility pattern, reducers are a kind of Memento, and action-creators are a kind of Command, which makes the whole thing a little better, but it’s still global state.

² Note: This is tongue-in-cheek. I have lots of problems with Relay, the biggest of which is it violates the Single Responsibility Principle when used with the thing it’s coupled with, GraphQL. You have to declare mutations, fat queries, etc. TWICE. But that’s another topic for another day.

Follow me on Twitter @TuckerConnelly

Unlisted

--

--