In this post I assume that you are familiar with concepts of Flux architecture like dispatcher, actions and stores. If not, please check these articles to get an idea, then come back & continue

Architecture

The main concept at the core of all Flux implementations is that the data must always flow in one direction. This is a worthy cause and it brings a lot of benefits during development and maintenance of software projects. With such architecture, project state becomes predictable, easier to reason about and debug.

Event bus vs explicit calls

Flux decouples logic by implementing event bus with the Dispatcher being responsible for dispatching Action generated events to all Stores. As with everything, events come with a trade-off too. They enforce decoupling of application logic by their very nature. The cost of that is that it’s usually much harder to mentally track execution of event-heavy code.

Yes, you can store complete event history to see what happened in your application but you lose ability to easily comprehend scope of all the logic that will be executed as a result of producing single event. Another problem is assuring that the dependent operations happen in correct order (solved by waitsFor from original Facebook’s Flux example).

Even if it might be a matter of subjective preference, nothing beats explicit calls when you need to orchestrate complex business flows through various domains.

Let’s implement one way data flow with plain old services and explicit calls!

UI

User interface is implemented using React components which belong to one of the two categories; container or simple, based on their responsibilities. The separation was strongly inspired by this article.

Container components

Template of container components consist purely of other React components which means they implement no own layout and because of that they don’t execute any business logic by themselves. Their responsibilities are:

hold current (rendered) application state

register model change listeners to get notified on model data change

retrieve actual data on model change

pass state to children components through their props

implement calls to domain & application services (and pass them to children if needed)

Simplified implementation of container component

As you can see, all parts are coming together inside container components. Imported model is used to register data change listeners & retrieve data. Service is used to explicitly call business logic which mutates the application state and lastly, container itself holds actual UI data and passes it to all interested child components together with relevant functionality.

Simple components

Simple components receive all their data and functionality through props (from parent component). They can implement their own local state and logic for handling of internal UI interactions but all mutations to the application state stored in models can only be achieved by executing functions received from parent through props (which are in turn calling domain and application services). As a guideline this means that they don’t import services & models by themselves.

Business logic & domain separation

As applications grow larger it is usually a good idea to split functionality into multiple packages (folders) by their respective concern. In the perfect world these concerns would be completely orthogonal so we didn’t have to implement any cross domain orchestration or cross-cutting concerns. Unfortunately, that’s rarely the case and we usually have to deal with cross-domain coordination when implementing our business flows.

In the next part, I will point to the related Flux concepts with ~ character. ~Actions then means like Flux’s Actions…

Application services

Application services (~Actions) are used to implement cross-domain orchestration. They are the only type of service which is allowed to import services from other domain packages. They only execute functionality of imported domain services and contain no business logic on their own.

(Domain) Services

Services (~Actions / Stores) are used to implement domain specific business and infrastructure logic. Services of particular domain can import only other services belonging to that domain (folder). All inter-domain orchestration must be implemented using application services. Logic can be separated into multiple services based on concern (eg: business logic, persistence, …).

Models

Models (~Stores) are responsible for holding app state during it’s runtime. They implement observer design pattern so that all interested container components can use model’s addListener method to register callback to be notified when the model data change. This implementation enables one way data flow. Model has full control of the (possibly custom) notification behaviour, while component knows what kind of data it needs to retrieve from model after being notified.

All “get” data functions of the model create copy of the retrieved data (vanilla immutability) so that they prevent direct mutation of model’s data through shared reference. Also, while it is theoretically possible for component to directly call other model’s methods, as guideline only listener and get methods are allowed.

Conclusion

In my opinion, it’s always very important to cut straight through the noise and get to the core idea. It was the same with Flux and I still remember that weird feeling like when you discover something new which looks too complicated for what it really is, with all the people spiting some new specific terms like an arcane mantras. This was the reason why I ended up implementing “Vanilla Flux” architecture example & writing this blog post…

To summarise, most important property of Flux architecture is the one way data flow and you can easily implement that just by using React with vanilla Javascript services & models (Typescript in case of example repository). That being said, always feel free to use any of the available libraries which suit your use case the best!

Ok, we’re done for today

Don’t forget to recommend this article if you found it interesting and check out other cool front-end and Angular JS related posts like Proper testing of Angular JS applications with ES6 modules, Guide to parametrized Webpack builds or Continuous deployment with GitHub Pages & Travis CI

Follow me on Twitter to get notified about the newest blog posts and useful front-end stuff.