Pothibo

Thoughts on JS MV*

Shopify’s announcement last week made a lot of people react on Twitter. It’s not frequent that you see a company as big as Shopify publicly announce that they are dropping a whole architectural pattern in favor of something else.

Over the past few years, the JavaScript community has come to a conclusion that Model-View-Controller should be used to organize the frontend codebase.

This realization didn't come out of nowhere. Just before 2010, it became very hard for anyone to build a complex frontend infrastructure. At that time, popular libraries were solving a much more pressing problem: cross-platform support (jQuery, prototype.js, etc). And since JavaScript was still something that some enterprise were turning off for security reasons, it was hard for anyone who wanted wide adoption to be very serious about it.

But then the mobile era kicked in and we all saw JS as a bridge between native mobile and desktop. The JavaScript's age of Enlightenment, if you will.

From that point on, JavaScript's codebase exploded and spaghetti code started creeping up in everyone's project. For small project, the spaghetti could have been negligible, but a solution was needed nonetheless. We needed to organize our codebase better.

Some people started to look around for solution, and they found one: the Model-View-Controller from the backend.

So people went on to implement a structure they were familiar with. JSON and XML would be to glue between the two entities. I believe the MV* pattern has helped a lot of people structure their frontend application. The architecture is well-known and easy to reason with.

But it doesn't come free.

Doubling down on everything

It's no secret to anyone that by building two independents MV* architecture, we are doubling a lot of effort. Many decisions that are made on the backend needs to be rechecked when the data reaches the frontend:

# Backend
class Controller
  def index
    if @user.friend?
      json.add friend_info
    end
  end
# Frontend
if (json['friend'] != undefined) {
  showFriendBadge()
}

This double check means that every time a decision is made to change something in the backend, there's a chance that you will have to modify the logic in the frontend as well.

And that data doesn't always reflect the internal structure of your models. Maybe it did in the past, but at some point, your model will evolve and the JSON you send won't really match.

Overtime, this will have a knock-on effect on the whole codebase.

APIs are easy, maintaining and implementing them is very hard

Building an API is easy. I believe this is the reason why it's so popular. It's also possible that generating an API is a sort of assessment of progress.

API for [resource] is completed? Great. I'm done here.

But the web is a moving creature. You start somewhere and you end at a complete different place. Every time it's moving, all the pieces needs to adjust itself. And the frontend is always the last piece to move, like the tail of a snake.

Ironically, it's usually the frontend that dictates the changes. For every changes that impacts both the frontend and backend. Two set of MV* need to be updated. Everything is decoupled.

Two sets of states and codebase that require testing and tweaking. And eventually, the API you use will be used by the public — that's what an API is for, right? — which will mean that your development is going to have another set of restriction: API versioning.

Maintaining the API isn't easy. If your frontend requires a specific JSON attribute and your model changes internally, you will need to have JSON generate the attribute to satisfy the frontend thus bringing a separation between your data and your JSON.

Loose coupling

Many people encourage frontend/backend MV* pattern because system should be loosely coupled. The problem with this assumption is that JSON data structure is not an abstract interface. Not in the original sense of the word anyway. The data sent as JSON needs to be precise and even if the object is not typed, the content of the objects cannot be interchanged.

This may sound pedantic, but it isn't.

If you need a very strict dataset to render your templates, why do you go through the hassle of serializing the data just to deserialize it on the other end and inject it in a template that was first sent by the backend?

To me this sounds like I would deflate a balloon, send it with the rack it goes in, then let the receiver re-inflate the balloon and put it in the rack while you could have sent the rack with the balloon inflated in.

Crippled backend

Backend have plenty of tools already to build great application. By using a MV* JS architecture you are effectively removing yourself from using anything that powers view generations through the backend. If you are, like me, using Ruby on Rails, that means you have decided to not use ActionView. This is a fourth of the whole framework you have decided to ignore.

The problematic here is not really that you are ignoring ActionView per se. It's that you are replacing it with myriad of different little things spread between the backend and the frontend to get to the same thing. Whether it's on the frontend or backend, dynamic HTML will be built. And dynamic HTML means you will have to evaluate states. Why would you want to make those evaluations after you send it through the wire?

I've tried backbone.js and angular.js in my past. There are many smart people who works behind those frameworks to make it easier for you to build web applications. But I got to wonder if all this effort is not made in vain. I wonder if at some point, we'll look back at this era of dual MV* frameworks and say: "Why did we go through all this trouble?"

Get more ideas like this to your inbox

You will never receive spam, ever.