Analysis / Contributed / Technology /

What Developers Need to Know about MVI (Model-View-Intent)

5 Apr 2016 11:58am, by

Luca Mezzalira
Luca Mezzalira is a passionate Italian solutions architect with more than 10 years of experience, a Google Developer Expert and manager of London Javascript meetup. Mezzalira is involved in cutting edge projects for mobile (iOS, Android, Blackberry), desktop, web, TVs, set-top boxes and embedded devices. He's spent a lot of time studying and researching on topics like object-oriented programming, functional programming and functional reactive programming. He regularly contributes to several technical magazines and websites, and a technical reviewer for Packt Publishing, Pragmatic Bookshelf, and O'Reilly. Last, but not least, he is a speaker with over 70 talks in the past 7 years.

There is a lot of interest in reactive programming nowadays, with many new frameworks appearing that embed this new paradigm to use asynchronous data flow and decouple the communication between objects.

If you’ve never heard of “reactive programming,” I can give you a brief explanation of what reactive programming means. Let’s start to say that reactive programming is not related at all to React.js; reactive programming is a paradigm that is trying to solve the problem of handling asynchronous data flow inside a program.

This paradigm could be sound very new for many of us but digging a little bit more deeply about it, we will find that reactive programming is based on two very familiar patterns like observer and iterator pattern.

Intent is a component whose sole responsibility is to translate user input events into model-friendly events. It should interpret what the user is trying to do in terms of model updates, and export these ‘user intentions’ as events.–Andre Medeiros

I genuinely think the main obstacle when we start to approach this paradigm is the new vocabulary we need to learn to understand what we can do with it. Words like hot and cold observables, back-pressure, streams, marbles diagram and so on could raise a barrier in front of us but with few hours of practice, we’ll see that a lot of these new words are based on very familiar concepts.

One of the most famous library currently available is Reactive Extension, available for several programming languages like Swift, Javascript, Java, Scala, C#, Ruby and many others.

When you search online for examples and information regarding reactive programming, you can start to find quite a few resources from online books, tutorials, guides and so on. But the missing part, or better the area not much discussed, for now, is how to structure an application in a reactive way!

I spent quite a few months trying to understand if and how it could be possible to structure an application end to end with observables, and I found few answers behind the MVI (Model View Intent) architectural pattern.

For those well-schooled in MVC (model-view-controller) application design, it would be redundent explaining what a model and a view are but probably you are interested in understanding what “intent” is.

I’d like to get the original definition from one of the first (if not the first) blog post regarding this architecture, in the post “Reactive MVC and the virtual DOM” Futurice blog, by Andre Medeiros: “Intent is a component whose sole responsibility is to translate user input events into model-friendly events. It should interpret what the user is trying to do in terms of model updates, and export these ‘user intentions’ as events. It translates ‘View idioms’ to ‘Model idioms.’ Intent itself doesn’t change anything else, as any other typical Reactive component doesn’t, by definition.”

One of the key parts in this architecture is understanding how these different objects communicate between them to structure an application in a reactive way.

WhatDevsNeedToKnowAboutMVI-1

The first thing to notice is regarding the communication, as you can see from the schema above the communication is unidirectional.

In my opinion, one of the best patterns Facebook shared recently with the JavaScript community was the Flux architectural pattern.

Flux is not only the name of a library that integrates with the React framework. It can create a nice framework to handle our applications, but it’s also the name of the pattern related to the unidirectional data flow nature that allows easily to structure and debug any application without following the complexity of a traditional event-driven architecture, where events can bubble from different objects without a consistent logic.

Let’s try now to look more deeply on the MVI mechanic; in MVI, the view is exposing an observable for the intent to capture all the user interactions from the view and passing the data through an observable to the intent. The intent is preparing the data received for the model, and these data are passed to the model via an observable again. Last but not least the model save these data and update the view exposing an observable to the view.

As you can see from the schema above, the view is not the rendered DOM elements but, in this case, is the virtual DOM mechanism that prepares via a library (using libraries like React or similar libraries for instance) the data to render inside the DOM.

This architecture inherits the benefit of Flux adding a more powerful way to communicate between objects via observables.

If you are not familiar with observables you need to know how flexible and powerful they can be. For instance, you can broadcast data via an observable mainly to 1 or more observers merging different data sources, reducing the values, manipulating them and so on. Let’s pick a basic MVI example from Staltz GitHub repository and let’s try to understand how MVI is implemented.

I’d suggest first to take a look at the example to understand better the code below.

To focus on is how all the different entities are wired together, let’s take a look to binder.js:

module.exports = function (model, view, intent) {
if (view) { view.observe(model); }
if (intent) { intent.observe(view); }
if (model) { model.observe(intent); }
};

This is where we are creating the monodirectional flow observing the model in the view, the view in the intent and the intent in the model. Now let’s take a look to the views, so check how is the anatomy of a view:

function observe(ItemsModel) {
replicate(ItemsModel.items$, modelItems$);
}
var vtree$ = modelItems$
.map(function (itemsData) {
return h('div.everything', {}, [
vrenderTopButtons(),
itemsData.map(vrenderItem)
]);
});
module.exports = {
observe: observe,
vtree$: vtree$,
removeClicks$: removeClicks$,
addOneClicks$: addOneClicks$,
addManyClicks$: addManyClicks$,
itemColorChanged$: itemColorChanged$,
itemWidthChanged$: itemWidthChanged$
};

In this case, I’ve grabbed only the interesting part, so the view is delegating the rendering to a library similar to React.js. So the view is not changing directly, but it’s just preparing the virtual tree to render and then it will be another object that will effectively render the DOM.

Bear in mind that one peculiarity of an MVI architecture is that an object shouldn’t manipulate or directly call any method of another object; the only communication allowed are through observables!

The dollar sign ($) at the end of the variable indicates the presence of observables assigned to that variable.

Now we are going to take a look to the intent:

function observe(ItemsView) {
replicate(ItemsView.addOneClicks$, inputAddOneClicks$);
replicate(ItemsView.addManyClicks$, inputAddManyClicks$);
replicate(ItemsView.removeClicks$, inputRemoveClicks$);
replicate(ItemsView.itemColorChanged$, inputItemColorChanged$);
replicate(ItemsView.itemWidthChanged$, inputItemWidthChanged$);
}
var colorChanged$ = inputItemColorChanged$
.map(function (inputEvent) {
return {
id: Number(inputEvent.currentTarget.attributes['data-item-id'].value),
color: inputEvent.currentTarget.value
};
});
module.exports = {
observe: observe,
addItem$: addItem$,
removeItem$: removeItem$,
colorChanged$: colorChanged$,
widthChanged$: widthChanged$
};

As you can see each single observables from the view is observed by an observer in the intent (replicate is an util method to handle the subscription to the observable).

If we check the colorChanged observables in the intent, we can see that every time the user is changing the color, the intent is preparing the data for the model and distribute these data via an observable that will be listened to by the model.

Last part is the model:

function observe(ItemsIntent) {
replicate(ItemsIntent.addItem$, intentAddItem$);
replicate(ItemsIntent.removeItem$, intentRemoveItem$);
replicate(ItemsIntent.widthChanged$, intentWidthChanged$);
replicate(ItemsIntent.colorChanged$, intentColorChanged$);
}
var colorChangedMod$ = intentColorChanged$.map(function(x) {
return function(listItems) {
listItems[x.id].color = x.color;
return listItems;
};
});
var items$ = itemModifications.startWith(
[{id: 0, color: 'red', width: 300}]
).scan(function(listItems, modification) {
return modification(listItems);
});
module.exports = {
observe: observe,
items$: items$
};

Again, we subscribe to the observables from the intent in the observe method, in colorChangeMod observables, we are assigning the color to the listItem object that will be the collection of components that we can create dynamically in the demo.

And we expose all the properties in the items observables ready to be picked up by the view that will render via the renderer library the changes.

A very simple unidirectional flow, with few basic concepts, shared and replicated across the entire architecture.

I can’t say that is straightforward working with reactive programming and MVI but after understanding the basic concepts, I can assure you that everything will have way more sense than the first time!

I guess you are thinking if any frameworks could help you to be immediately productive using MVI for your web apps. Therefore, I want to give you the answer you were looking for: YES there are!

Cycle.js

WhatDevsNeedToKnowAboutMVI-2

Cycle is a quite new framework created from one of the most active members of the reactive community, André Staltz.

The definition from the official website is: “Cycle’s core abstraction is your application as a pure function main() where inputs are read effects (sources) from the external world and outputs (sinks) are write effects to affect the external world. These side effects in the external world are managed by drivers: plugins that handle DOM effects, HTTP effects, etc.”

Cycle.js was created with “modularity” in mind, in fact, is very easy to create and reuse components across different applications.
To do that, Staltz implements MVI as a main architectural pattern to generate applications with Cycle.js.

I’d like to emphasize few interesting approaches implemented in Cycle.js:

  • The only communication between different architectural entities is via observables injected inside the function.
  • It’s a really lightweight library with a smart libraries system that allows composition in a nice and easy way via drivers.
  • The views are rendered creating a virtual tree, by default Cycle.js is suggesting to use virtual-hyperscript, a lightweight implementation of a virtual tree with a powerful diffing system like the React virtual dom.

Potentially you can also decide to use React instead of hyperscript with Cycle.js because all the view states are stored in the model and shared with the view via an observable.

I’d also suggest taking a look at the GitHub example repository to see how Cycle.js works.

Wrap up

Is MVI the “silver bullet” architecture for reactive applications? It’s hard to say right now, but it’s definitely a good implementation with a nice separation of concerns, encapsulation and with the right amount of inversion of control applied.

But, it’s not the only reactive architecture currently available on the Javascript world, keep an eye on Calm^2 (read “calm squared”) and Tsers, they could be the next “big thing”!

It’s really an amazing moment for the reactive programming ecosystem and the front end industry; we are just at the beginning, and a lot should be discovered and tested before finding the “perfect” way to follow, but that’s where the fun begins right? And we are in the middle of this huge storm, and we can really be part of that!


A digest of the week’s most important stories & analyses.

View / Add Comments

Please stay on topic and be respectful of others. Review our Terms of Use.