Model-View-Controller is a pretty common decomposition for a lot of software, especially the ever-present CRUD app. But I think this design doesn’t often get explained well. In particular, there are an awful lot of articles explaining “how to do MVC” but many of these seem to be oblivious to the fact that other articles will directly contradict it. Since these differences aren’t acknowledged, they aren’t justified. Once again we have trouble understanding the “why.”
Also muddying the waters, I think MVC actually has some difficulty adapting to the stateless web application, so to even call web applications “MVC” is not quite a perfect fit. There’s also been a host of different ideas that all get labeled MVC, despite their differences, and many very related designs that get names other than “MVC,” with as little justification.
I’ll assume you’re already familiar with some form of MVC. I want to start by rolling back the clock to where MVC originated as an organizational approach for developing stateful application user interfaces.
Smalltalk and the 80s
The original paper describing MVC for Smalltalk is actually quite readable. (It’s a light 30 pages!) I’m going to briefly describe a slightly updated version of this approach. There are several key ideas that can be found right from the start here:
- We’re working with a stateful representation of the model and UI.
- This is specifically an object-oriented design.
- The key rule is that the Model is unaware (and thus de-coupled from) Views and Controllers.
- The key concern is reusability in this design. This concern manifests itself in two different ways. First, the model should be reusable by a lot of other code (multiple views or controllers, or even other modules of the application). Second, that views should be reusable by other views, essentially making views composable.
- In this original design, view and controller classes are often somewhat coupled to each other.
- We see the origination of something like the “Observer” pattern, allowing the model to send change notifications to objects is has no static knowledge of.
- The model was intended to represent some aspect of the application’s state independently from any UI concerns.
- The view was intended to handle all drawing-related concerns, potentially with some internal state of it’s own.
- The controller was intended to do all event handling and all updating of the model.
There were a few problems that eventually arose with this original design:
- Although there was probably supposed to be some “right way” to minimally couple Views and Controllers, they often ended up tied to each other in a 1-1 way.
- Programmers often experienced immense confusion between views and controllers. What was responsible for what? It didn’t help that these were often so coupled together that you could get away with mistakenly placing code in one or the other module.
- Newly developed UI toolkits actually further exacerbated this confusion, as their designs often tightly coupled events with drawing. (e.g. A “text entry” widget was an object that both drew to the screen and received events.)
An evolved stateful MVC
These drawbacks lead to a few improvements and clarifications.
- Controllers came to be more associated with “business logic” than with event handling.
- The view became the originator of events, and allowed handlers to be registered.
- The controller and view were more de-coupled from each other.
In terms of dependencies, Controllers and Views still know about Models, but the only other direct dependency is that Controllers sometimes know about Views, but not vice-versa. The controller is now unknown to the view, and only becomes responsible for handling events because the application dynamically registers handlers (implemented as part of the controller) with the view. Similarly, the model remains ignorant of any view, but views continue to be notified of changes by registering to receive observer broadcasts, just as before.
This approach meshes better with the object-oriented UI widget toolkit designs that were becoming popular. The view corresponds with a pile of widgets, and exposes somewhat higher-level event hooks that typically will call into the controller. The application is responsible for initializing the view by registering these event handlers.
And the model? Well, that part was good. No changes really called for.
The goals of all this were relatively simple:
- The model represented the application domain data without any dependencies on the UI at all. The benefits of this should be fairly obvious: we can use that model independently of the UI, without UI at all, or with multiple different UI components.
- The views became independent reusable (and composable) components, not coupled to a particular use-case.
- Programmer confusion over where to put code was eliminated, mostly.
There were still a few more minor problems left, each of which deserves some added explanation.
Controllers often too coupled to UI
Even with the transition to the controllers largely being about business logic, they were still too often directly coupled to the UI because they got implemented as event handlers. In the architectural diagram above, I show a dependency from controller to view, but this dependency could in principle be eliminated.
The trouble here is just one of convenience in many object-oriented languages and many object-oriented UI toolkits. The “ideal” approach would be for controllers to be ignorant of any UI concerns, to be without that dependency. But event handlers have reasons to know about their view.
To eliminate this dependency, the functions in the controller would have to be solely about the application domain (or business logic), and not actually themselves be the event handlers. The most likely solution here is to have the application register simple (probably anonymous) functions as event handlers that extract what’s necessary from the view and then call the controller without any view-specific code.
The drawback of this approach is that it leaves us with a sharp divide between two different kinds of “event handlers:”
- Higher-level “business logic” event handlers that live in the controller. (Meaningful changes to the model, for instance.)
- More local “view-only” event handlers that live internally to the view. (Scrolling, for instance.)
It might be difficult to divide events cleanly into these two categories. One might need to both do something meaningful with the view and model as part of handling an event. (Consider both changing the model and attempting to “focus” on a UI element that needs attention.)
Also awkward is the choice to separate “model as stateful data representation” and “controller as manipulator of that state.” Many choose instead to place business logic, not in the controller, but in the model. After all, once your business logic is sufficiently free of UI concerns… why not?
And indeed, why not. This design approach often leaves a degenerate controller module that largely acts like those anonymous adapter event handlers I suggested as a tool to de-couple controller from view. That’s basically all the controller becomes in the fat-model approach.
Fat models still come with some minor drawbacks:
- Putting too much application logic in the model might actually hinder attempts to re-use that model. To make a change, what modules need changing, and who do those changes concern? With a fat model, the model always needs changing, and all users of the model are concerned. That may or may not be a problem. If the model is getting re-used a lot, you may want to see model changes only with schema changes about the representation of the underlying data.
- With most logic in the model, programmers can again become confused about why “view” and “controller” are separate at all. (In stateful MVC, that is. Their separation is more clear in stateless MVC, as we’ll see next week.)
Observers lack meaningful data
Every time a change happens to the model, the view is supposed to update correspondingly. But the observer notification is usually just “something happened.”
What happened? Where? What needs updating? These are questions the view usually ends up having to answer on its own. Sometimes the view has a cache of the data involved in what’s currently on screen, and a quick comparison is made to try to narrow down how much of the view needs updating.
It might be nice if there were some provision here for being smarter about updates. But MVC requires no such thing.
Finally, this is all stateful
One of the most commonplace uses of MVC today are in web application back-ends. And yet, HTTP web applications are inherently stateless designs. There is no observer-like notifications of views, not anything like what MVC uses, anyway.
So web applications have a whole different notion of MVC… one they also just call MVC. And that’s going to be a topic for next week’s post.