Bidirectional vs Unidirectional data flow architecture

MVC (Model, View and Controller) architecture has been in the industry for quite sometime. This pattern has served web applications well by decoupling their various components. The following diagram gives a high level idea of how interaction between different components work.

mvc.png

Controller is a broker between View and Model. The flow of data happens between model-controller and controller-view. Since view and model don’t have a direct communication hence they are unaware of each other. This allows us to change them independent of each other.

With the advent of complex frontend heavy applications (eg. gmail), this pattern found its way in javascript as well. The above diagram can be mapped to javascript applications as follows:

mvc4.png

On front end, data flows from the view to server due to user interactions (for eg. button click). Similarly data flows back to the view from the server in case of server updates.

For applications which have thin front end (i.e. no business logic in JavaScript), the role of controller is that of a data binder between model and view. For instance,


//Send form data to server on click of button
$('#clickMeBtn').click(function(){ form.submit();})
//Update view with data recieved from server
$.get('/get/employee', function(employee){
$("#employeeName").text(employee.name);
})

The above code does the basic functionality of relaying data back and forth between view and server.

However, for an application with good amount of user interaction adding such code can become repetitive and combursome.  In such scenarios, we can leverage AngularJS’s two-way data binding. This allows us to declaratively bind the view with model thereby relieving controller of this duty. For such thin applications, custom controllers don’t play a significant role and are just a support function to AngularJS.

mvcAngularJS.png

Hence, in a thin javascript application with considerable amount of user interaction we can have a bidirectional data flow directly between the view and server.

Things start becoming interesting and complex for applications which have thick front end. Such applications have a good amount of business logic on the client side as well. One case of adding business logic on client side is to make the application seem highly nifty and responsive. This leads to higher user engagement and retention. For eg. social networking sites like Facebook and Twitter have thick front ends as they want to give a user instant feedback and keep them engaged.

In such applications, controllers start to play a significant role. As a result, they come out of the shadow of AngularJS and start showing their influence on View and Model.

mvcAngularJSController.png

The above diagram shows that data can flow between view and model from either the controller or AngularJS. This kind of architecture gives us low confidence on how the view and model interact. We can never tell with surety if the view got updated logically or as a side effect by AngularJS.

In an application, view is a function of the data provided to it by the server. Mathematically speaking,

viewAsFn.png

Mathematical functions are pure functions, which means that a given input maps to the same output all the time.

In the current architecture, we cannot say with a high degree of confidence that given the same data from server will our application show the same view. This is due the fact that some parts of our view change which are not in our control (or not easy to reason about).

In such thick frontend applications, we have to introduce the concept of unidirectional data flow.

dispatcher.png

Here, view talks to a component called Dispatcher through actions. Actions are generated through user-generated events like click or keypress. Dispatcher figures out what data this action needs to update and sends it to the Application Store for further processing. Application Store contains all the state information of the application. It can also communicate with the server for updates. Updates to the store are listened by the View which in turn updates itself.

Since the flow of data is unidirectional, there is only one way through which view can be updated. As a result this gives us a high degree of confidence that our view is a function of data and hence is deterministic.

The above architecture is as simple as it looks. We can find variations of this architecture in the form of flux, redux and elm.

The implementation of this architecture can become a bit more involved as compared to an out of the box bidirectional data flow architecture. However, this gives us more control over how data flows in our application.

Conclusion

For thin front end applications with minimum business logic in JavaScript, we can deliver quickly with bidirectional data flow in the form of two-way data binding provided by AngularJS. However, for thick frontend applications we should rely on unidirectional data flow to get more confidence regarding how the view will be displayed based on the state of the application.

Further reading

The case for unidirectional data flow

Unidirectional User Interface Architectures

Reactive programming vs Passive programming

Reactive programming has been quite a buzz word for quite sometime. It is often confused with programming in React.js. In this post I will share my understanding of what is reactive programming and when to use it.

Software programming is about implementing a business use cases in a language which computers can understand. High level languages have made programming easier and accessible for everyone. This has allowed us to tackle complex business problems such as e-commerce, health insurance. However, it is important to distinguish complexity into essential and accidental.

Accidental Complexity

Accidental complexity is the complexity introduced in a system by the architecture and programming paradigms. This is generally independent from the business problem.

Consider the following code snippet in Java to read a file:

InputStream fileInputStream = new FileInputStream(FILE_PATH);
BufferedReader bufferedReader = new BufferedReader(
        new InputStreamReader(fileInputStream));
String line="";
while((line = bufferedReader.readLine()) != null){
    System.out.println(line);
}

Following is the code in Python:

with open(FILE_PATH, "r") as file:
    for line in file:
        print(line)

Python achieves the same functionality with lot fewer lines of code. This complexity is not due to any business requirement. Solving such complexity is generally straight forward. In the above case, we can use class composition to reduce the lines of code.

Essential complexity

Some systems are inherently complex and take time to understand. Consider the following system:

delhi-metro-rail-map copy

The above image is a route map of Delhi Metro in India. Looking at the above map, it is difficult to find the exact route from station A to station B. There can be multiple alternative routes and some of them might not be valid. However, a color coded image can remove majority of confusion:

delhi-metro-rail-map

Now we can get a better clarity of how many different lines of trains ply in Delhi Metro. It binds stations on the same line much more clearly. Hence, solving an essential complexity involves finding creative solutions which make the complexity easier to understand. This is where the understanding of reactive and passive programming can help us.

A software is divided into modules with each performing a set of cohesive functionality. I will take an example of e-commerce checkout flow system. Consider two modules, Cart and Invoice such that if an item is added in Cart, Invoice should be update.

CartItem.png

One way to achieve this is to add dependency of Invoice on Cart, such that when Cart updates itself, it updates Invoice as well. Something like the following pseudo code in Cart:

addItemInCart(Item item){
    Invoice.update(item.price)
}

CartInvoice1

Another way to achieve this is to add dependency of Cart on Invoice such that Invoice updates itself when an item is added in Cart. Something like the following code in Item:

Cart.onAddItem(function(Item item){
    self.update(item.price);
});

The above code uses a callback function  which gets invoked when item is added in cart.

CartInvoice2.png

Notice the difference between the arrows in the above two figures. In case when Invoice is passive, Cart is dependent on Invoice. Hence the arrow is near Cart. In case when Invoice is reactive, Invoice is dependent on Cart. Hence the arrow is near Invoice. This is the major difference between reactive and passive programming. In reactive programming, a module is responsible to update itself as compared to passive programming, where other modules are responsible to update the module.

In a fairly complex system, managing dependencies is a critical aspect. Systems with clearly defined dependencies are easy to refactor and understand. For a module, dependencies can be described in two areas:

  1. How the module works?: A module will be dependent on other modules to complete its functionality. In our example, Invoice depends on Cart to update itself.
  2. What does it affect?: A module might affect modules which are dependent on it. In our example, Cart affects Invoice.

Hence a comparison of reactive vs passive programming in these two dimensions results in the following table:

Passive Reactive
How the module works? Find usages Look inside
What does it affect? Look inside Find usages

In case of reactive programming, we need to look inside the code of module to find out how it works as the module is responsible for itself. Similarly in passive programming, we need to find usage of a module across other modules as other modules are responsible to update it.

Dependency.png

Scanning through other modules is a difficult job. Incase the module is accessible publicly, it might not be possible to find out its reference. Hence a benchmark of when to use passive vs reactive programming is the ease of how vs what.

In general, software systems become complex when we don’t know how our modules work. Invoice can be updated by Cart but in future might be updated by  Discounts and Taxation modules. Reactive programming serves us well in such scenarios.

Further reading:

  1. Reactive Programming: Why It Matters
  2. The Reactive Manifesto