Friday, April 29, 2016

Should we use MVC for modern web applications?

MVC is one of the most commonly used design patterns in web applications. It can be used both with server and client side rendering. Frameworks like ASP.NET MVC and Angular adopted the pattern and made the development extremely easy and straight-forward.

Despite its popularity, I claim that the MVC pattern is no longer the best solution for creating rich and modern web applications.

 

MVC – the good parts

Single Page Applications rule the web with different client side rendering frameworks like Angular, Backbone, React and many more. These frameworks made JavaScript development easier-and by doing so made the client “smarter” and the server “dumber” by mostly being responsible for communicating with the client and data persistence. Most of the applications’ logic moved to the client side.

In order to support this transition the MVC pattern has moved to the client by creating controllers that manipulate data (the model) that is presented in the views. This makes a lot of sense, as opposed to using just jQuery for some simple DOM manipulation and handling all the heavy lifting in the server.

So instead of rendering the view in the server for each request, now it’s extremely easy to render it in the client and give the users a faster and cleaner experience.

“Magic tricks” like two-way binding allow applications to respond quickly to different events and change the appearance of the app accordingly.

Let’s have a look at the Angular docs website. We can easily navigate through the different sections in the website while avoiding a trip to the server for rendering the next page.

That is a classic MVC case. It works great for the “simple” apps where most of the page content is static and the user’s changes don’t affect different parts of the page.

 

However, this is not what rich modern applications look like.

Let’s look at this example. What happens when we increase a product’s quantity? 

 

Four different parts of the page change (the header counter, the notification, the quantity of the product and the cart title).

 

Let’s try to implement it using Angular

Obviously I won’t build the entire app in a single view or use a single controller. That’s the worst practice, since it will break the SOLID principles of development by creating a giant controller that is responsible for EVERYTHING. This will become difficult to maintain over time.

I want my code to be highly reusable and simple to use, which I can achieve with components. Angular provides me with directives and, since Angular 1.5, it also provides components. I will create a controller, a directive and a template (view) for each of the following elements: the header, the notification and the quantity counter. In order to notify the other components that a product was added to cart, I can do 2 things:

  1. Use $rootScope and raise an event on it while subscribing to it on the other components.
  2. Use a shared service to pass the data between the components.

 

MVC doesn’t provide us with an out of the box solution for cross components communication. We need to extend it and create workarounds.

Here we saw just a single use case for cross components communication. For an entire application we’ll have many different events that will be managed using shared services or the $rootScope.

 

Does MVC help?

MVC forces us to separate the logic that is in the controller from the view that is just a template. It sounds like good practice, but if you think about it (and don’t kill me right away) maybe that’s actually not so good.

We are creating components, self-existing components that, like it or not, have their logic coupled to their view. You can’t really change the controller without affecting the view, and vice versa.

That’s alright! The components can be “smart”, they can have logic, handle their state and respond to different events and properties in a different way. So if that’s the case, why do we try to extract the logic and make the view dumb?

Why not create components that define their functions, handle state and create the view in a single class – a real reusable component. This component will use actions to trigger events that will be dispatched through the application and notify other components that something has happened, and allow them to respond if it’s necessary. This pattern is called Flux and it’s used widely with the promising new framework React.js.

clip_image002

Flux introduces three new features that create the necessary infrastructure:

  1. Action – A simple class that defines the events that components may trigger.
  2. Dispatcher – A class that dispatches the events from the Action to the different Stores.
  3. Store – A store catches the events, processes them and then fires a completion event that components may subscribe to.

 

Let’s implement the previous example with React and Flux

image

Here’s a basic Flux file structure. You can find all the parts we discussed earlier here.

The + button will call an action, which will trigger “INCREASE” event.

 

The CartStore will respond to the event, calling to the server and actually modifying the product’s quantity.

 

Then the store will fire a “PRODUCT_QUANTITY_WAS_CHANGED” event to notify who ever listens.

Then each component that subscribed to the event will be notified and will act accordingly.

 

What’s the difference?

Nothing mind-blowing really. However, we don’t need to create a controller and separate it from the view and then subscribe and fire events in order to communicate. We build components that are responsible for themselves. They constantly pass events from and to each other, allowing us to easily change existing behavior, add new components and integrate them with the existing events.

The classic Model View Controller separation seems a little bit redundant, hence it must be completely changed for creating rich and modern web applications.

 

Agree? Disagree?

Share your thoughts.

 

* I want to thank Yonatan Mevorach for reviewing this post.

8 comments:

  1. Great job as usual :)
    I've been working with react for less than a month after years with angular and I can already say it's a much better approach for building SPA.
    The biggest advantage in react by far is the performance. It's uncomparable as long as you know what you're doing.
    About the MVC part - it really depends on your needs. If you want a simple website then MVC can do the job really well, but if you need to create an app (for smartphones or web based) I'll usually choose the facebook way for the job.
    And if you're using flux (the library) I'd suggest looking into redux which simplifies things.

    ReplyDelete
    Replies
    1. Thanks Adam!
      Couldn't agree more. I also use Redux. For the purpose of this post Flux is easier to understand.

      Delete
  2. Hi, great post. Thanks for writing this !
    I am an Angular guy.
    For the case study above, I find myself actually create a big controller, with the purpose as a communicating channel between 2 directives (through data binding). Your solution with shared service and pub/sub pattern is beautiful and common as well.
    I was thinking, how about , we have directive A which emits event "abc", and directive B listens to that event ? In this case, no controller at all.

    Also, do you have any experience with EmberJS 2.x ? Cheers !

    ReplyDelete
  3. Hey Steven,
    Using two directives might work as long as you your directives don't have any controller logic in them. Eigther way that will be almost the same as using components with React withount any MVC parts.
    I currently focus on React, haven't tried Ember yet. Do you?

    ReplyDelete
  4. Yoni Davidson11/29/16, 5:16 PM

    Agree
    I love working with react/redux.
    The flux architecture makes a component logic very predictable.
    Predictable leads to testable.
    Testable leads to the "light side" of the force...

    ReplyDelete
  5. What you call event's in React, are they not more like callbacks?

    ReplyDelete
    Replies
    1. Well yes. Events trigger predefined callbacks.

      Delete