You may have heard the expression “fat model, skinny controller” when talking about MVC frameworks. I’m going to try and convince you that why that’s a misleading and potentially dangerous rule to follow.
Problems working with MVC
MVC frameworks provide a pretty good way of separating the various concerns of your application. Presentation goes in the view, business logic goes in the model and the controller stitches everything together, right?
The main problem that I come across is that developers think that all of their code has to go into a model, view or controller. Some frameworks provide view helpers (but these are evil) and maybe a place for custom plugins or components, so that’s maybe 5 or 6 types of class.
As developers improve in their knowledge and experience of MVC-based frameworks they will undoubtedly learn the mantra of “fat model, skinny controller”. I do believe that it’s better to have a fat model than a fat controller, as controllers are notoriously difficult to test and even more difficult to DRY up (“don’t repeat yourself”). But the problem with this approach is that, as your application grows, it will end up with god objects – huge, monolithic models with thousands of lines that are very difficult to maintain.
Expand your mind
MVC is a design pattern, but it’s a very high-level, architectural pattern. That gives you room to introduce lower-level design patterns in your code. If you shift your thinking from limiting yourself to three main class types, you suddenly re-open the whole world of software design.
Let’s take the simple example of exporting a model instance to a serializable format, such as JSON. Here are the options:
- Send the record down to the view, then loop over the attributes and output a JSON – bad: not reusable
- Do the same, but in the controller – bad: not reusable
- Put a method on the model that converts it to a JSON – bad: mixing concerns (business data and presentation)
- Create an exporter class that accepts the model record and spits out a JSON – good: reusable, separate from the core model, easily testable
That final example is just that – an example. Since we’re no longer limited to keeping everything within MVC you can choose whichever pattern best fits your specific case.
Models represent data and (perhaps worryingly) persistence. This is more than enough responsibility for a single class. Questioning the scope and responsibility of each class is a good way of helping you design your code well. If a class knows too much about other classes or unrelated parts of the application then that coupling is just waiting to bite you in the backside.
Another example is callbacks. Rails’ ActiveRecord allows you to write methods that get triggered after certain database events, such as after_save, after_find, before_create, etc. This fills your model with code that’s could even be collaborating with totally unrelated classes, such as an email sender. Not only that, but you have a problem when unit testing – example.com is about to get spammed whenever your test suite runs
This is a classic observer pattern – why not implement the observer pattern and notify a pool of observer objects about these events? Each observer performs the glue between your model and another unrelated class, and your model is kept in the dark. Testing is also easier – just detach the observer, or don’t attach it in the first place.
No class should be fat. Ever. There is no need for your controllers, models or views to be fat; this shows laziness, as there’s been no thought given to the application’s design outside of deciding to use a particular framework.
A web application with a large codebase can easily end up dwarfing the framework that it uses. If your application is 10% framework and 90% custom, you need to ensure that you give serious thought to the design, otherwise you’ll find yourself in unrecoverable technical debt. Would you ignore design for any other software project of the same size?
All I’m asking is this: think outside of your framework. It’s there to help you, but not to dictate the way you write every line of code. You can get away with this with small projects, but as they grow they become unmaintainable and a drag to work on. Read up on object-oriented design patterns, and use appropriate ones wherever possible.
“Question everything generally thought to be obvious.”