Some of the frameworks apply a pattern that I call “fractal architecture”. This type of architecture should be the most popular today. To let you know what I mean by fractal architecture I will list some of its rules:
- Application is a tree of components of the same API.
- Each component is able to contain (use) other components.
- Top component does not differ from other components.
- Glue and application lay apart from each other.
Now I’m going to show how these rules apply to some popular frameworks.
Both React and Angular use the word “component” to name some of their entities. Components in React and Angular are organized into trees. They are composed together by APIs similar to XML. And what they produce at the end are DOM trees.
Both Angular and React provide a way to bootstrap an application. Bootstrap API is different from component API.
Elm in not a framework but language.
But there is The Elm Architecture.
This architecture is also fractal.
An Elm application consists of components (you can call them modules).
Each component exposes three functions:
Each component can consume these functions from other components.
Bootstrapping is done by passing root component to a special function
main with some preparations.
In Cycle each component is a “pure” function (meaning it does not have any side effects) and a complete application. It accepts some sort of observables and exposes another sort of observables. Each component can use other components within itself, almost how a function calls other functions inside its body.
Wiring of components is done separately from components declaration.
What’s wrong with Redux
There is nothing wrong with Redux itself.
But sometimes I see how Redux is being used wrong. It’s necessary to notice that Redux has been created with influence of Elm and Flux. Elm leads to fractal architecture. Some Redux applications not.
The case is that there is no concept of “Redux Application”. Redux itself is a library to manage data flow, nothing more.
Many people including myself do not pay much attention to application boundaries. They write an application with a thought that there will be only one instance of it, one instance of root component, that application won’t be used inside another application. This leads to non-scalable architecture.
How to fix that
Redux application scalability can be achieved by keeping it in mind while writing code. This is possible by following the rules written at the top of this post. With a context of Redux in mind, they can be interpreted as follows:
- Make an application reusable.
- Keep it apart from bootstrapping boilerplate.
How to make an application reusable? By making it composable. How to make it composable? By treating an application as a module. There are some requirements for module in redux application:
- Module boundaries and API are well defined.
- A module is not responsible for creating redux store or any other dependency that is meant to be a singleton.
- A module accesses its state from its API. It’s not responsible for knowing the full application state.
- A module exports its reducer. It’s not responsible for registering the reducer in the store.
Let’s apply these rules to a simple application that uses two counters.
First, let’s define a
Remember that each module can be used as an application.
As you see, our
Counter is a factory.
It has no dependencies, so it could be a simple object.
But now it’s a function that returns simple object.
The return value is our module’s API.
I decided that the module in this case should return its reducer and a view function.
Reducer is the same as always.
View function takes dispatch function from redux store and current state of a counter.
As you can see, this module does not store any state itself.
view are pure functions with their dependencies.
Also it does not create redux store. It only accesses store’s
dispatch function via arguments.
Now let’s define our
App module, that will use just defined
App module is also a factory that returns an object of the same shape as our
This means that both modules have the same API.
App module accepts
Counter via its arguments.
That’s how we inject
Counter module into an
App module exports two functions:
They’re both pure.
Let’s wire those components together.
As you can see, we’re finally creating redux store here and instantiating our app. All bootstrapping boilerplate comes apart from our modules. That allows us to use those modules again in another parts of our application or even in other applications.
Advantages of fractal architecture
Fractal architecture applies one simple pattern across all the application. If you know how one component works, it means you know how every of them work.
Another advantage is that each piece of an application is easily extractable into a separate application, like each subtree is a valid tree. That leads to easier refactoring, easier testing in isolation, and to code reusability.