Showing posts with label Rails. Show all posts
Showing posts with label Rails. Show all posts

Sunday, February 24, 2013

Server-side view or client-side MV*?

Recently I came across a video in which David Heinemeier Hansson(a.k.a DHH, creator of Rails) talked about client-side MVC vs the traditional Rails way of developing web applications. In the video DHH claimed that the development experience of client-side MVC is inferior compared to traditional Rails development. He didn't give a lot more specific reasoning except:
  1. you can't write ruby at client-side.
  2. client-side MVC is "extra complexity" that is not worth the effort most of the time.
Personally I like CoffeeScript better, especially on the functional programming side, but I am not going to argue about it in this article. This article is about why the second reason he gave is wrong.
I'll first show some code comparison between the two paradigms to demonstrate the software design drive for client-side MV* and then I will talk about the business drive for client-side MVC(*) - interactivity.

Is it more complexity or more elegance?

DHH claimed that client-side MVC is more complex compared to server-side views, but is it? Or is it just more elegant? Let's look at some code. How about we write a simple todo list in both client-side MVC and traditional Rails way.
Rails way
erb view:
<ul>
  <% @todos.each do |todo| %>
    <li> <%= todo.title %> </li>
  <% end %>
</ul>
controller:
class TodosController < ApplicationController
  def index 
    @todos = Todo.all
  end
end  

Client-side MVC
service controller:
class TodosController < ApplicationController
  def index 
    render json: Todo.all
  end
end  
HTML view:
<ul>
  <li> {{title}} </li>
</ul>
client-side MVC coffeescript:
$ ->
  todos = new Collection(url: '/todos')
  new CollectionView(collection: todos, el: $('ul')) 
  todos.fetch()
  #The Collection and CollectionView is not standard Backbone class. We extended it a little bit in our internal Backbone library. 

The client-side MVC version does need some client-side MVC code, but whether it's resentful complexity or elegance with clearer view logic is arguable. Now let's add a dropdown filter so that user can filter the list by todos' status: 'completed' and 'overdue'.
Rails way
erb view:
<%= form_tag("/todos", :method => "get") do %>
  <%= select_field_tag('filter', 
     options_for_select(['completed','overdue'], @filter)) %>
<% end %>
You also need a little bit help from the client-side so that user doesn't need to click a submit button to submit the form.
$ -> $('select').on 'change', $('form').submit
Controller:
class TodosController < ApplicationController
  def index 
    @filter = params[:filter]
    @todos = @filter.present? ? Todo.where(status: @filter) :
                                Todo.all
  end
end  
Client-side MVC
HTML view
<select name='filter'>
  <option>completed</option>
  <option>overdue</option>
</select>
A new view is needed at the client-side MVC:
class FilterView extends Backbone.View
  events: 'change' : 'refresh'
  refresh: => @collection.fetch data: { filter: @$el.val()}
Add this view to the page load initialization
$ ->
  ...other initializations mentioned above...  
  new FilterView(collection: todos, el: $('select')) 
Controller:
class TodosController < ApplicationController
  def index 
    @todos = params[:filter] ? Todo.where(status: params[:filter]) :
                               Todo.all
  end
end  
This time the effort required in the client-side MVC solution is arguably at the same level. You need to write a new view class and initialize it but in exchange your HTML view is much cleaner than the erb template. The extra CoffeeScript in the Rails solution perfectly exposed its inelegance - why do you need a HTML form? Also, you might notice that the server-side controller is slightly cleaner when it doesn't have to remember the filter state at the client-side (I know you can access params directly from the view but that's frowned upon).

From a software design/architecture point of view, where to place the view logic is not a very tricky question. We don't need philosophical debate to sort it out. View logic needs both to access server-side data and to interact with client-side HTML DOM. Clearly it's more convenient/efficient/logical to place view logic close to where it has the most interaction. In the old days, the only user interaction is simple back and forth HTML form request and HTML page response, the view logic relied mostly on the server-side data. Nowadays web app UI has become a lot more dynamic and complex, there is way more dependency towards client-side DOM and states then server-side data. That's why placing view logic at the client-side is more efficient at this age.

It's the interactivity, stupid.

DHH admitted that certain level of user interactivity justifies such 'extra' effort for client MV*. He was close but did not get to the point. Interactivity is the essential for better UX. When your page is simple, yes, you don't need that much interactivity. But the market will demand more and more functionality on that page. The only way to keep the UX clean and intuitive is through creative interactivity.
DHH told the story that he chose Backbone to do a calendar because it's a highly interactive component and he used traditional server-side view for other "information" page. This hybrid approach is not that bad an idea when you can foresee how interactive each part of your app is. What DHH failed to see is that it is not the case for a website reasonably ambitious to be modern.

A good example is the todo list mentioned In DHH's story about his development of Basecame Next. He couldn't decide which way to go, client-side mvc or the Rails way. He couldn't tell if the amount of the interactivity in a todo list demands client-side MVC. So he wrote both a rails+ad-hoc javascript version and a Backbone version. He ended up with the rails+ad-hoc javascript version because it's less code and more 'elegant' and can also serve the interactivity needed for that todo list. But what if the market one day says we want more functionality in that todo list? The ad-hoc javascript and view logic in rails would become more and more messy until it would have to be rewritten in client-side MVC.

Actually, all the "information" pages he mentioned could be designed differently with more interactivity if the developer was not in the traditional Rails html form and view mindset. For example, in the comments page, how about we allow user to reply to any comment? The user clicks the reply button, a text input shows up below, the user types in the reply and it gets attached right after the original comment? True this maybe achieved by refreshing the whole comments container but what if he is also in the middle of replying to another comment? What if we want that new reply to show up in some sort of animation? (If you ask why animation, I suggest some with how animation can provide visual feedback and enhance UX)?

Embrace a new paradigm not because it's "shiny", but because it can free us from our old mindsets

One of the DHH's frustrations over the current client-side MVC trend was that he thought other people are adopting this new paradigm simply because it's new. I have to say that he misunderstood them. It's the trend not because it looks new, nor is it only because it naturally provides fluent UX. It is because it opens our eyes to rethink HTML and explore the many new possibilities on how to design a better web application (interactivity is one of the keys).

DHH showed us an good example of such confinements from one's old mindset when he talked about how awesome the form_for helper is. HTML form is simply a broken piece of HTML, it made user interaction rigid and boring and it couldn't even do the HTTP method correctly. And the form_for helper was partly to help server side keep client side state (so that a form submitted can be rendered again the way it is submitted, e.g. when server side validation failed), which shows how unnatural the server side view is - letting stateless server side maintain the state at the client side.

When World Wide Web started, the interaction between client and server was designed to be get/form request and HTML response. It worked fine for over a decade, but we have started (for years) the transition to a new highly interactive WWW. Views logic resides at the client-side powered by much stronger JS engine and communication between client and server is cut down to it's bare bones - simple json data. Both make perfect philosophical and practical sense. If our client-side MVC framework isn't perfect and introduces boilerplates, then we should improve it. What we should not do is to insist on keeping view logic at the server-side simply because we have been doing that for decades.

Tuesday, August 02, 2011

Interesting notes for a Rails 3.1 performance issue on an EC2 micro instance

For my web app, I recently switched to an Ajax intensive model where most of the html manipulation happens on client side with coffeescript. This significantly increases the number of http requests hitting the server, which caused really serious performance problem on my micro instance on EC2. The app reacts quickly for the first page but then would literally freeze for 1-2 minutes. It turned out to be a combination of the way how Ajax app, Passenger and EC2 micro instance works.

With default configuration, Passenger tends to keep a low number of instances in the pool and spawn when it needs to take care of higher volume of requests. It also recycles unused instance regularly (every 5 minutes by default). It works perfectly with the one request takes care everything model, but an Ajax app tends to fire more ajax calls and they usually come in spikes.
Passenger spawn more instance with the assumption that these requests are coming from multiple users and it needs to guarantee a reasonable response time for all of them. It's a waste for an Ajax model because the asynchronized nature of Ajax application allows them to function reasonably without a super fast server response on every request. Spawning extra instances becomes an overhead when serving a small number of users. Because the normal pattern for an Ajax application is that passenger sees a burst of requests and spawns a bunch of instances and then recycle most of them when user stops operating the app for a bit. Spawning apps is a CPU intensive operation.

On EC2 micro instance this became disastrous because these instances throttles your CPU after a cpu usage spike to prevent you from using 100% CPU all the time(they are shared). The consequence is that every time Passenger spawns 6 instance (default config value), EC2 'helps' by throttling you CPU to only 2.7% of normal computation power...

My solution to this is that I fixed the number of instances in the passenger instance pool at a lower level. This way, ajax calls might get queued a bit from time to time but 1) hardly felt at the client side because most of the ajax calls are some proactive data pre-fetching and 2) no CPU usage spikes.

The lesson I learned from this is that a lots of rethinking is needed in many aspects of web app when we switch to the Ajax driven model.

Tuesday, April 26, 2011

Introducing Collectr - my first flickr App

Alpha test: http://collectr.kailuowang.com/

What is Collectr?
Collectr is a web app that helps you, a flickr addicted, subscribe and explore flickr photos in a much more powerful and personalized way.
What's the vison of Collectr?
To achieve what flickr explore failed to achieve - an easy and free way to see more interesting pictures every day.
Why use Collectr?
  • Centralized slide show
  • New photos from multiple sources will be displayed at a single slide show
  • Personalized slide show
  • Collectr remembers your preference by recording your 'fave' action, so that when you have too many new photos from sources,
    it will display first the photos from the stream swhose photos you fave the most in the past.
  • Expendable Sources of Photos
  • With Collectr, not only can you subscribe to someone's upload stream, you can also subscribe to her favorites stream.
    This way you can discover flickr artists that are discovered by your favorite flickr artists.
    This is very important because it allows you to expand your list of sources of good photos.
  • Slide show with the best possible image quality
  • Unlike many of the third party flickr websites, when the collectr slide show display photos, it will try find the version of the photos whose reslution fits your screen the most. Also, this slide show is tablet friendly with navigation keys on the sides and links using larger font.
  • Easy share
  • You can share/backup your collections by exporting them into a backup file so that later you or other Collectr users can import it.

Saturday, March 19, 2011

Setup a Rails 3 app on an EC2 quick start linux AMI

Just setup a Rails 3, Ruby 1.9.2 on a free ec2 micro instance (http://aws.amazon.com/free)
Started from the quick Basic 32-bit Amazon Linux AMI 2011.02.1 Beta AMI, I setup the following stack in less than 2 hours.
git
ruby 1.9.2
rubygem
MySQL
Apache
Passenger
And my Rails 3 app.

The whole process is mostly smooth. All dependencies can be found on yum.
It looks like that the free (for a year) micro EC2 instance is quite a valid solution for personal ruby projects.