Use of the Model View Controller (MVC) design pattern is generally accepted as a best practice in modern web applications. Like all design patterns, MVC is a reusable solution to a common problem. The MVC pattern is intended to address the following concerns:
- Support for multiple types of clients
- Reduce duplicate code when supporting multiple types of clients
- Isolating domain logic from the user interface
Note that items 2 and 3 are both dependent on item 1. Support for multiple types of clients is the single driving force behind the MVC design pattern. Following are some examples of client types:
- Web Browsers
- API Clients
- Admin Processes (e.g. CLI, cron, daemon)
- Unit Tests
- Native GUI
Realistically, how many of these different client types do you need to support? If you’re taking a RESTful approach, then Web Browsers and API Clients can be combined into one type of client, which we can just call User Agents. Likely you’re not building a Native GUI. This leaves you with the following three types of clients to support:
- User Agents
- Admin Processes
- Unit Tests
Do you really need to use the MVC design pattern in order to support User Agents, Admin Processes, and Unit Tests? Likely not. That’s not to say that there aren’t benefits to isolating domain logic from the user interface. However, if you don’t need to support multiple types of clients you should really question your use of the MVC design pattern. There are many approaches one can take to separating the domain logic and the user interface, of which MVC is just one.
This bring me to The MVC Paradox. Modern MVC web frameworks inadvertently encourage the coupling of domain logic and user interface. If you don’t need to support multiple types of clients, then decoupling the domain logic from the user interface is the only reason left to use the MVC design pattern.
Modern MVC web frameworks often involve a lot of boilerplate code just to support the primary client type of User Agents. This boilerplate code typically does little to help with supporting the other client types of Admin Processes and Unit Tests. As a result of the overhead introduced by this extra boilerplate code, developers often find themselves creating Fat Controllers (a side-effect of The MVC Paradox). Controllers take on too many responsibilities, both vertically and horizontally. Vertically, Controllers start to handle domain logic that should be pushed down to the Model layer. Horizontally, multiple concerns get stuffed into a handful of Controllers. These different horizontal concerns should be separated out into multiple Controllers (the Single Responsibility Principle). The overhead introduced by modern MVC web frameworks leads directly to these problems.
In contrast, take a look at the Slim PHP 5 micro framework. While not a completely RESTful framework, it does a pretty good job of addressing the concerns I’ve laid out in this post. Here is the “Hello world” example from Slim’s homepage:
<?php
require 'Slim/Slim.php';
$app = new Slim();
$app->get('/hello/:name', function ($name) {
echo "Hello, $name!";
});
$app->run();
?>
There is very little boilerplate code involved in handling a User Agent’s request. Minting and handling new routes is incredibly simple. Placing your entire front-end application layer in one file may seem absurd at first. However, it has the nice side-effect of making it painfully obvious if you start to handle domain logic outside of your Model layer or if one route is doing too many things. If your front-end application is large enough, then you can organize your routes into separate files.
This post is not intended to discourage you from using an MVC web framework. There are certainly times when such a framework is useful. As with any technology decision, carefully consider what problem you’re trying to solve and what technology will best address your problem set. Sometimes your technology choice can undermine the very purpose of using that technology in the first place, as is the case with The MVC Paradox.
Update (12/10/2012): This blog post has been translated into Serbo-Croatian by Jovana Milutinovich of Webhostinggeeks.com.
11 Comments
While you’re right on about MVC being overkill for single client apps, I’ve found frameworks like Slim and Silex to be a complete freaking nightmare to maintain and debug.
@lucifurious: Out of curiosity, can you tell me a bit more about the maintenance and debugging issues that you’ve run into?
Interesting point. I mostly aggree with you, but I think that MVCis not important only because of these reasons. If you follow this pattern, your code is going to be easier to maintain, easier to understand.
On the other hand, I am not familiar with Slim, I will give it a try.
I very much agree! I’ve written before about how MVC doesn’t even apply on the web, no matter how much Rails folks and Sun Microsystems like to pretend it does: http://www.garfieldtech.com/blog/mvc-vs-pac
Slim looks an awful lot like Silex, which is sort of Symfony2 Junior. I haven’t built anything with it directly yet, but the main issue I see with it, and with Slim (from the one code sample above), is that it’s not going to scale to controllers that have to be complex (lots of form handling) and/or lots of controllers. Drupal (my main development system) has over 300 routes out of the box; it’s not unusual for a site to have nearly 1000 routes by the time you’re done, and yes they all have a purpose. Defining all routes and controllers inline at that point is completely unsustainable, especially from a performance perspective. You’re creating hundreds of closures, only one of which you’ll ever use.
Also, if all of your controllers are defined inline as closures, you cannot unit test them. For a project with a half dozen controllers you can probably get away with that. Anything bigger and you’re really short-changing yourself.
let’s say that most “simple” app do not need CLI intervention, what about automatic testing ?
It looks like your approach leaves only the blackbox acceptance testing option, while MVC enables you to deeper unit test parts of your app
I think frameworks like Slim and Silex “support” spaghetti code too much. 😉 IMHO, there is no clear separation of anything. Nothing you can’t do yourself, but then you might be better off with another framework.
On debugging – Silex is kind of annoying in this regard – especially phar’d. Then I’d argue it’s not at all micro if you happen to need something other than routing you step through two dozens of Symfony2 components. 😉
On Slim – they lost me with PHP 5.2 compat. PHP 5.4 is out.
I can’t see how MVC supposedly “Support for multiple types of clients” from accepted definition of MVC like the one from Wikipedia: “Model–View–Controller (MVC) is an architectural pattern that splits interactions between users and applications into three roles: the Model (business logic), the View (user interface), and the Controller (user input).[1][2] This separation of concerns facilitates the independent development, testing, and maintenance of each role.”
“This bring me to The MVC Paradox. Modern MVC web frameworks inadvertently encourage the coupling of domain logic and user interface. If you don’t need to support multiple types of clients, then decoupling the domain logic from the user interface is the only reason left to use the MVC design pattern.”
I dont understand this paragraph. The 3rd sentence does not provide any support for the claims of the 2nd sentence, leaving the 2nd sentence kind of without basis.
Supporting multiple clients is only one of many advantages. MVC’s main purpose is to separate concerns, hence the name. This enables you to grow your codebase while maintaining flexibility. It also makes your code easier to understand for other developers, because they can quickly recognize the pattern and guess where this and that process occurs. And the list goes on.
I find the full toolset of Asp.Net Mvc (asp.net environment, model binders, action filters, view engines, path resolvers, html helpers, routing) as the reason to get involved with this type of mvc framework. The rest are all well known issues for the community. But from my experience, things are changing fast so its better to ‘go standard’ and don’t get very theoretical if you know what i mean.
I’m using Slim + Redbean + (Twig)* to create simple services for flash apps. especially if i don’t have svn support and console access on the production server.
I’m used to develop php using zend + doctrine + (smarty|twig) and for sure are oversized for this kind of projects.
About testing, it depends on you practice. i’m used to it so i develop all the business logic into worker classes, leaving to the microframework just the routing and input validation.
That’s my experience!