Is MVC the best we can do on the web?

Recently I’ve been thrust back into web development after a 5 year hiatus.  Sadly I’ve discovered that not much has changed.  We’re still forcing an inappropriate pattern onto web development.

Why not MVC?

The pattern we all know and love has been great for many years, and certainly in a thick client environment, it and its brethren seem appropriate. In these cases, the UI’s are often being developed by the same folks that are developing the M and the C. With web development, this is often not the case.

On my first significant web project back in 1999, the workflow was a designer delivering HTML files, and then a developer hacking that into a JSP/Servlet combo. This results in your controller and views being tightly coupled together. What happens if the designer needs to deliver completely new screens and workflows? My guess is you’d have to throw out the views and the controllers and start again. How diligent is your team at keeping the controllers thin and free of business logic? Most of us know that’s a good practice, but how often have you seen that violated?

Introducing a new TLA: SVF

Service, View, Flow. The last one is Workflow, but W just has too many syllables. Let’s partition a new way.

Service

A service takes data in, and returns data out. A service might fetch a customer or a customer’s orders, or it might create a new order for a customer. This has been common in software development for some time now, and it continues to make perfect sense in web development. Nice, simple, reusable, beautiful.

View

A view should only require some data, and be able to render that data. Imagine a view template that specifies the data it needs. A Customer. A list of orders for that customer. The template specifies it needs that info be specifying the services needed. An incoming web request/get doesn’t go to a controller, instead it goes straight for the view. The view will specify what it needs. It will work to render itself.

What about performing operations? Let the form or the link specify a target service and bind data to be passed. Now the designer can compose the views however they want, without impacting code much, if at all.

Flow

Flow, or workflow if you like, is the traffic cop. The designer can specify much of the workflow through the views, but occasionally some more complex logic may be necessary. In this case, your workflow can be a simple codified state machine that can determine the next view based on some logic.

What does it mean?

Developers can focus on building small simple services, designers can not only build the views, but actively maintain them over time (as opposed to painful markup merges or outright rewrites). We are minimizing the development of “throwaway” code, and in this case I’m referring to throwaway as code developed for a single purpose of gluing/coupling components together. In the realm of MVC, Controllers are essentially throwaway. To some extent the Views are too, since they are so tightly coupled to their controllers.

To visualize, imagine web services, with HTML documents that interact with those web services directly. All we need is a framework to tie them together.

An example

It seems that Twitter style micro-blogging is the Hello World of web apps, so let’s see what this might look like. We’ll take a look at a user’s home page, which will provide a form for a new micropost, as well as a list of our own posts. Such a page will need two primary services, one for the User, and one for the user’s Posts.

The View

@Fulfill User = User.Current
@Fulfill Posts = Posts.ByUser(User.Id)
<html>
  <head>
    <title>Hello Microblogger World</title>
  </head>
  <body>

Here, we see a couple template markups in our invented template language that are binding variables to services. First, the service identified by User.Current is bound to the variable User. Likewise, the service Posts.ByUser is passed a parameter (the current users ID), and the results are bound to the Posts variable. These are pre-requisites for displaying this view.

Next, we’ll have a nav bar that includes links to various other views (not controllers, not actions, but the actual views).

<nav class="round" >
  <ul>
    <li><a href="@View()" >Home</a></li>
    <li><a href="@View('Users')" >Users</a></li>
    <li><a href="@View('Users', User.Id)" >Profile</a></li>
    <li><a href="@View('Users', User.Id, 'Edit')" >Settings</a></li>
    <li><a href="@View('Help')" >Help</a></li>
    <li><a href="@Service('User.Logout', 'Login')">Sign out</a></li>
  </ul>
</nav>

Most of our links are pointing straight at other views. We are maintaining RESTy-ness with the links that would be generated by @View. The final link for signing out actually calls a User.Logout service, and specifies Login as the followup view. By doing this, the designer can maintain control of workflow instead of the developer coding this into a controller.

Now let’s see how we would handle posting a new microblog message.

<h1>Tell me what you're doing:</h1>
@ServiceForm('Posts.Create') {
  <textarea cols="40" id="newpost" name="@Bind('message')" rows="20"/>
  <input id="post_submit" name="commit" type="submit" value="Post" />
@}

We’re going to bind the HTML form to a service called Posts.Create. The textarea element is bound to the input parameter on the service, “message.”

Finally, we’ll iterate the user’s own messages

@ForEach('Posts') {
<tr>
  <td class="gravatar">
    <a href="@View('Users', User.Id)" >
      <img class="gravatar" src="...."/></a>
  </td>
  <td class="post" >
    <span class="content" >@this.Message</span>
    <span class="timestamp" >@this.Timestamp</span>
  </td>
</tr>
@}

We build a link much like before, and we iterate over the posts with the @ForEach tag.

The Services

We can cleanly build our services now. In this case, we need Users.Current (which would handle the authenticated user), User.Logout, Posts.ByUser, and Posts.Create. These services are very general, and once built the views can be endlessly changed and redesigned and reused without further code necessary.

class PostsService < Service
  def by_user
    user_id = request[:user_id]
    ... fetch return posts for user_id
  end

  def create
    user_id = context[:user].user_id
    message = request[:message]
    ... create new post for user
  end
end

It’s interesting to note that these services are practically web services if the inputs and outputs were transformed from/to JSON or XML.

Getting with the Flow

For the sake of discussion, we’ll say that our Help views can be initiated from anywhere in the application. The help contains numerous individual views to assist the user, but when the user clicks a Done button, they should return to the screen they were on when they first clicked Help.

class HelpFlow < Flow
  attr_accessor :view_stack

  on_viewing(:help) do
    view_stack.push(request[:source_view])
  end

  on_viewing(:done) do
    redirect(view_stack.pop)
  end
end

Changing roles

We are changing the roles and responsibilities now. Developers are focusing on developing code to process and return data. Designers are focusing on building the user experience. While offloading some responsibilities from developers, we are adding some for the designers. Communication will be necessary to work out what services will be needed, what data needs to go in and what data is coming out. In other words, the developers and designers will be coming up with an API between them.

Just today the value of this became apparent. The team lead and the designer were trying to figure out how to refactor the partitioning of the views. It turns out that partial views and layouts had been pulled out by some developer along the way to suit his purposes. Unfortunately, how he had partitioned them was damaging and resulted in considerable duplication. If the designer had been wholly responsible for views, they could have properly partitioned the different headers, footers, and so on in a logical manner that would minimize duplication.

Advertisements

9 thoughts on “Is MVC the best we can do on the web?

  1. wmsoczynski

    You made a very good point. Apart from some implementation details ( I would not expose the current user to the view – instead made service methods to return data for the CURRENT user) I think that your idea fits very well to the land of web development. This style of developing applications is often used in pure ajax or flesh/flex apps, where one writes the whole view in Javascript or in Flash/Flex and the whole business logic / model part lies on the server exposed as web services.

    Reply
  2. Gregory Pierce

    I think this is something that most bigger shops that aren’t simply “painting by numbers” are already doing. On a project that I’m currently managing we have a team that is building services that are connected together by flows (activiti and drools flows in particular). We still have a controller tier because there needs to be a clean separation between the views and the services less your services become polluted with things like security concerns which really belong to the controller tier. In general the pieces start to look more like 4 tiers:

    View->Controller->Flow->Service

    This approach has allowed for a lot of flexibility as well as providing a number of encapsulations that web applications sometimes lack.

    Reply
  3. Kenneth McCall (@ellisgl)

    I’m scratching my head here.

    Service sounds like how a model should be used – fetching/pitching data back and forth from a data source.

    Flow sounds pretty much like a controller. In most MVC’s I’ve used, there’s a front controller (aka a router) that send the request to the controller which then delegates task models and takes the out come, shoves in a view and returns the compiled view to the user.

    Is there something I’m missing?

    Reply
    1. feedmyconstraint Post author

      Why restrict it to models? Services could be models, query services, or even CQRS services.

      Flows take on the responsibilities of navigation, or workflows. Controllers in typical web frameworks take on a lot of responsibilities, including reading from the view (the request), interacting with the model, and instructing where to navigate next. That isn’t how original MVC worked, but it isn’t something that maps to the web very well.

      Reply
      1. Scott Brickey

        I came to say the same… sounds the same as MVC… that said, ensuring common terminology IS a big problem across any project (especially on the business object side… is a customer an account, etc).

        The biggest issue I see, is that a lot of projects aren’t developed according to good practices… this can be said of ANY dev platform… webforms, mvc, winforms, etc… separation of code is a good thing in concept, but rarely executed well in practice.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s