Why I implemented Flux in a UI-less software project
Spaghetti is a wonderful meal, but a terrible software design.
I am currently developing a React Native library at my workplace, that implements background behavior and communicates with our backend. I am unsure to what extent I am allowed to describe what this library actually does, so I will have to remain vague on the subject. All you need to know is this library has to manage a state, and changes to this state should trigger actions from various entities. It should also be able to save its current state to a persistent store, and retrieve it back later.
However, I also remembered trying to understand how to use Redux (or was it Flux?) and only achieving to realize that it was complicated. Also, it seemed to me, at the time, that Flux and Redux were designed for applications, aka software with a UI, which my library is not. (I do realize that “software with a UI” is a poor definition of what an application is, but my library still isn’t.)
Having very little time to devote to my state manager implementation, I had the feeling that trying to learn how to use Redux (or Flux, whatever) was risky, since I was not sure it was really suited for my project — especially for the part about being able to save the current state to a persistent store and retrieve it back later. But during a conversation with my aforementioned friend on the matter, he told me this:
You can implement Flux without a library.
So Flux must not be a framework, after all.
So, what is really Flux?
Flux is not a library or a framework, it is an architecture. Redux, however, is a software library that evolves the ideas of Flux.
The principle behind Flux is simple: it’s a flux. Data “moves” in an unidirectional pattern.
Flux is composed of a single dispatcher, and any number of stores. The current state of the application is stored in — wait for it — the stores.
The particularity is that no one can directly access or mutate the data that is stored in a store, excepted the store itself. Other software components can only trigger actions through the dispatcher. An action contains a payload, and when triggered, it is passed to all stores, and each one uses the received data to update its internal state. The new state is then passed to the store’s subscribers as a “change” event. Typically, those subscribers are UI views, and they use the new state to update their displayed data.
And what about Redux?
I don’t know a lot about Redux, but in my understanding, two important differences with Flux are that the state is stored in an atom, and stores are replaced by reducers. A reducer is a pure function that receives a current state and an action, and returns a new state.
The use of pure functions means that there is no side effect, so it improves testability, among other benefits. For a given state and action, a reducer always produces the same result. I personally think this is a great idea, and nothing prevents one to use it in their own Flux implementation even if they do not use Redux.
The benefits of using Flux in my project
Explanations of the Flux architecture usually describe how it helps keeping the UI views up-to-date. But state change notifications can really be sent to any software component.
In the case of my project, I have to manage event-driven behavior. When an event happens, an action is dispatched, and stores update their state accordingly. Then, the subscribed entities “reboot” by using the data contained in the new states as startup arguments, sparing me a lot of conditional blocks that would otherwise result in corner cases and potential bugs. There can however be a small performance drawback to this, since a subscriber has to “reboot” with the whole new state instead of doing something with only the part of the state that has changed — I find that a good way to deal with this is to have stores that individually keep as little data as possible, and handle very specific concerns.
Overall, using Flux guarantees me that I can retrieve the state from a persistent store at any time and have my software components behave exactly the same way as they would have if that state had occurred “naturally”. And I don’t really need to think about it while coding.