Flux architecture was meant to make the complexities of front end development more manageable. While Flux (and its variants like Redux) seems to have done a great job in eliminating the pitfalls of two-way data binding, I cannot help but wonder if it hasn’t actually made things worse: many developers spend more time trying to figure out glue code (actions, action creators, dispatcher, stores, reducers) rather than writing the code that actually does the work.
Here I propose an alternative to both Flux and MVC. In a way, you may think of it as a hybrid of the two. Consider an architecture with the following three elements:
- A completely UI-agnostic “application engine” that encapsulates all business logic and state, wrapped up in an API
- A thin, logic-less “shell” or interface that exposes an API to control the UI (graphical or otherwise)
- A “transmission” layer between the engine and the shell that connects the two by mapping shell events to engine API calls, and engine events to shell API calls
If you fail to see the difference between MVC and TX, the sample code below might help.
Here I will implement only the “create todo” part of a simple todo app. And in order to demonstrate that the engine has to be completely UI-agnostic, the UI in this example will be a command line interface.
First, let’s look at the TodoEngine. It implements an API as simple functions that complete their jobs and emit events.
The TodoShell (or UI) reacts to user interactions by simply emitting events (the shell itself does nothing). Something else is expected to listen to these events, make the decisions and tell the shell what to do next using its API (e.g. navigate to a different screen, show an error, add an item to a list etc.)
Note that in this example, the API function names are the same as the engine for simplicity— it doesn’t have to be that way. The transmission can map these two APIs any way you want.
Here’s how a transmission layer will connect the engine and the shell. It only contains wiring — no logic, state, or view concerns at all. onShellEvent() listens to shell events and calls engine API in response. onEngineEvent() listens to engine events and calls shell API in response.
Here’s how you’d run the application:
For the full source code:
git clone email@example.com:hliyan/epc-test.git
npm install -g jest
npm test # run unit tests
npm start # run the command line app
todo> add hello # how to add a new todo item from the app prompt
- Prevent any direct references to logic functions in the UI code, so that the UI can be developed and tested independently, just like the logic module (the engine). UI testing will not even require Selenium (because you have an API to drive it).
- An architecture with minimal semantics — no actions, stores, reducers, middleware etc. Only API functions and events.
- A relatively short, flat, uniform looking paths through code. We’re choosing event emitting in favor of promises or callbacks for asynchronous operations; use Flux-like switch-case statements.
- Composability: for systems with complex UIs and business logic, we can setup multiple Shell-TX-Engine chains in parallel.
Using with React.js
I haven’t an example yet, but a React based shell is fairly trivial: simply fire events on onClick, onKey* events on interface elements (e.g. shell.events.TODO_TEXT_ENTER_KEY), and expose an API that ultimately changes component props (e.g. addTodoToList(todo), filterList(‘completed’)). I hope to create a TodoMVC example soon.
Real world applications
I’ve already had seven developers use a more rudimentary version of this architecture on three different front end applications. Each one was more successful (easier and faster to code, less error prone and easier to debug) than previous Redux implementations. For my next project, I plan to use Transmission architecture fully. I will of course report back with observations and more learnings.
Credits: thanks to Pasindu Rumal Perera (@udnisap) for double-checking my work and suggesting improvements.