Rediscovering MVC - A severely misunderstood "pattern"
14 min read
(Originally posted by me on github, now updated and moved to a better writing platform)
Poor MVC! It's probably the most misunderstood "pattern" in the history of computer science.
Why? Viewing MVC as simply separation of concerns leads to unnecessarily complicated solutions to simple problems. MVC is richer than a single pattern - it is a pattern language, created to align the computer and the programmer with the mental model of the end user.
So if you want to expand your view on a very useful and well researched pattern language, let's try to understand it again, beginning with a visit to the past.
A brief history of Patterns
When Trygve Reenskaug started his research back in the 70s, great ideas were going on in computer science. No doubt computers were here to stay, and people like Alan Kay and Doug Engelbart had visions of computers being an extension of the human mind. The computer was supposed to be composed of objects, a concept that humans reasoned about naturally, and those objects were supposed to communicate with each other, essentially making the objects a recursion on the idea of the computer itself. So Trygve set out to make a connection between computer data and stuff in the end user's head, trying to make the computer think what the user was thinking.
The result, conceived in 1978, was MVC, Model-View-Controller. It was based on how humans interpret and manipulate data and information, and it seemed possible for software to take a big step towards object-orientation and user-friendliness. The computer was in grasp of any person!
Then came the 80s, and the engineers were spending time with what they enjoyed, which wasn't end user mental models, dynamic interactions, user interfaces and other human-related things. No, it was structure. After some time, the actual architecture, the form of a system relevant for the users, was completely hidden by layers of abstraction called software design patterns, later known as the "GoF patterns". They were abstract, decoupled and reusable, like a house made entirely out of scaffolding. Ugly and not user friendly, but very organized, just like the engineers want it!
The GoF patterns became the norm, and in the 90s the humane ideas from the 70s were mostly forgotten. Nerds ruled the computer world, and nobody else had anything to say about software design. Who could even argue? Computers were considered very complicated, no wonder when the engineers had turned the rules around. The users were basically controlled by the computer and its programmers, and most systems turned very complex (big structure) or into a mess (bad structure).
In the 00s, after the dot-com bubble, the costs of this structural mess had become a real issue. This led to the idea that we shouldn't even consider architecture and planning anymore. Just be Agile! Make the structure so flexible that if any new requirements comes up, we can just add more with almost no cost. If this adding sums up to a mountain of scaffold, well, another layer of indirection/scaffold will surely solve it! Then introduce unit testing, a very wasteful testing method that will effectively double the code base (how Agile is that?), and not much have changed really.
But something happened around here, when the web started to grow rapidly. MVC was rediscovered as a useful pattern for websites. But alas, shoehorned into a design pattern. The idea of separation and modularity was more popular than ever, like the world was made of Lego and block modules was the best way to build a nice, lively house. Separation was enforced, and the View became an HTML container, the Controller not much more than a static class, and the model got the rest of the system, a substantial mix of data and functionality. And the debate started to rage... Fat models? Skinny models? View models? Fat Controllers? Services? Layers? Mediators? Where to put everything?
No wonder people eventually got tired of this and tried to find alternative patterns and solutions (MVVM, MVP, PAC, MVA, ADR...), but blaming MVC for any of this is not fair, as you hopefully understand now. So let's move on from this history lesson and see what we can do to restore MVC to its former glory!
Correcting some misconceptions
Let's start by forgetting most of what we know about MVC from thousands of web articles. Unfortunately it's necessary, most are based on the "design pattern" view of MVC, the nerd-centric approach. If we want a way out of the above mess, we have to reconnect to the end user.
A mental model is "an explanation of someone's thought process about how something works in the real world." This is what Reenskaug had in mind with MVC:
We have the User's mental model, where the View is a bridge between the Model and the User. The user observes the Model through the View and gives input through the View to the Model, experiencing an illusion of working directly with his or her information (the Model). This means that the Views appear to communicate directly with the Model. The illusion can only be upheld if all Views are synchronized to always show the current Model. How this is done is an implementation detail.
The fundamental idea for the programmer's mental model was to separate the computer representation of the user's mental information model (the Model) from the parts of the program that facilitate the user observing and editing selected aspects of it, the Views. Views are created and coordinated by a Controller. Any actual message flow will be acceptable as long as it upholds the user's mental model.
This is very welcome information that helps correct some misconceptions about MVC. What else can we find? In Reenskaug's human centered document MVC - Its Past and Present, it is shown how MVC works as a pattern language, and there we'll make some other discoveries:
1. Objects can be any combination of M, V and C!
In MVC - Its Past and Present on page 10-11, we find something ignored by countless programmers who have been enforcing separation at all costs:
- In simple cases, the Model, View and Controller roles may be played by the same object. Example: A scroll bar.
- The View and Controller roles may be played by the same object when they are very tightly coupled. Example: A Menu.
- In the general case, they can be played by three different objects. Example: An object-oriented design modeller.
Shocking to anyone familiar with many popular MVC web frameworks. What about reusability and modularity, the goal of any software project worth its name? We must be able to re-use as many classes and components as possible for the next project, right?
Well, in reality, how much of that is true? Are you reusing the Views? Hardly, your next web site will look quite different. The Controllers? Nope, the routes, database access, etc, are different too. The Models then? Not if you are creating them according to the knowledge and terminology of users and Domain experts, then they will be different between customers. So what is reusable really?
It seems that we're only reusing CSS and HTML frameworks (occasionally!), some general-purpose libraries, and the GoF patterns that we still hang onto, but maybe shouldn't. Those things are quite low-level. On a higher level, we want to create things (objects) relevant to the user, and they are very specific for each project. Also, striving for reusability before something is even used, smells of premature optimization.
All this means that we can couple MVC objects because they aren't on the actual level of being reusable. We should rather embrace the natural coupling to create small, efficient objects that are easy to understand, using terms based on the end user mental models. That's a step closer towards the Kay object-oriented vision for sure.
2. The View is an object!
This follows from the previous point, confirmed recently by Trygve, but it's still worth emphasizing because the "web view" idea is so dominating: A "View" in web apps is usually considered a template, a code snippet rendered to HTML and displayed in the browser. Quite simple to use, but not as dynamic as web requirements are today. In early sites the template had to be populated with data, but there was no natural place for the data since there were no objects in sight. The template engine and framework handled this with key-value mappings initially, but after a while the View Model was invented. An object containing the data required for a view template to display correctly.
This is an example of something being invented because people are more interested in innovation than building on already laid foundations. Older stuff isn't as trendy, but it is solid and useful, like a timeless piece of equipment surviving through the ages.
If we instead apply the idea that a MVC View is an object, we can pass the relevant Model directly to that object, in the constructor for example, and we don't have to invent a concept like a View Model, that doesn't make sense to the user and/or complicates the program. State not related to the Model can be put directly in the View.
That solves the data part of the View, but what about functionality? Web apps today are quite interactive, but it's contrived to model interaction in a template snippet. Again, objects are a much better fit.
If you agree that the View should be an object, finding a MVC web framework that allows us to create Views as simple objects isn't that easy. Mithril is a shining exception however. It lets one focus on the system architecture instead of structure. It uses View Models, but they aren't a requirement. Svelte is another example that embraces the natural coupling of MVC. Overall, the situation is improving with the "components" approach, which, in light of the above information, is really MVC in disguise.
Those were some urgent points. Now let's get into details, focusing on web development. The following list about the responsibilities of MVC objects is taken from the excellent book Lean Architecture by James Coplien, and I've added some comments.
- Updates its data at the request of commands from the Controller
This has been corrected since the book was published. Most user input comes in through the View, which will usually pass the input on to its Model, if the implementation allows. For modern web apps, objects are in memory in the browser, so it's no problem to let the View talk to the Model directly, since the View is coupled to the Model data anyway.
- Notifies appropriate Views when its state changes
- Can be asked to register/de-register Views
- Presents a particular representation of the data to the user
- Can ask a Model for the current value of data it presents to the user
This is quite simple now that we have a View object that either is passed a Model, or is the model itself. Just ask the Model for the data and display it appropriately.
Regarding the relationship between Models and Views, Reenskaug and Coplien agrees that "a given view canonically has a single Model." Reenskaug continues:
Other Views can have other Models. The subview structure could reflect the model structure, or it may merely keep things together that belong together in the user's tasks.
- Can handle its own input
In web applications, the View object is usually the point of user interaction, through DOM events. Quoting Coplien here:
A View can handle some of its own input (e.g., a text input field can handle all local text processing - backspaces, line kills, etc.) only to send the data to the Model when an ENTER key is pressed or a button is pushed - but that's kind of an optimization. The important is that the end users should feel as though they are interacting directly with the Model. That completes the link from the end user mind to the program and back.
Many modern frameworks uses DOM events to automatically redraw the screen, since most user input results in some visual change. Again very useful to avoid patterns like Observer.
- Together with the Controller, handles selection
This one is interesting. From MVC - Its Past and Present, page 13:
A user selects one or more objects in one of the Views. The selection should appear in all Views where the selected object is visible in order to maintain the object model illusion. [...] Solution: Let the selection be the responsibility of an object that knows all the relevant Views.
The object in question is the Controller. This means that the Controller will contain a list available to the Views it manages. When the list changes, so will the Views displaying the selected objects. The selection input is passed on from the Views to the Controller.
In the original MVC, a Controller is mainly a View manager, with the following responsibilities:
- Creates and manages Views
When Views are objects, a common issue of how to organize "parent-child" or "between component" communication is much simplified. The Controller will instantiate the required views and make sure the correct Models are passed to them. An illuminating note from Reenskaug:
Let a View play two roles: Let it be managed by its Controller in the usual manner and, at the same time, be (sub)Controller that manages a number of subviews.
- Handles selection across Views
See the last point in the View section above.
- Passes commands to the Model
This has also been corrected as of late. Since the Views talks directly to the Model when possible, it's not required to pass any commands through the Controller. It could be required if the Controller is the only way in the framework to send requests to the server, but that's also an implementation detail.
Commands are further discussed in The original MVC reports:
A view should never know about user input, such as mouse operations and keystrokes. It should always be possible to write a method in a controller that sends messages to views which exactly reproduce any sequence of user commands.
This has been confirmed by Reenskaug as implementation-specific, not a general rule. But even though the View can handle input in the browser, the last part is still a useful guideline when writing the public interface/API for the MVC objects. Do they reflect the way the user thinks about the system? And when someone wants to connect to your useful software with other software, will it be an intuitive process, or will it feel like a feature that was slapped on as an afterthought?
- Handles commands that applies to the entire window
Having an overarching Controller for the whole window already exists in many MVC frameworks. It's usually called a Router, and now when we allow it to be any combination of M, V and C, its usefulness increases.
I hope you can now see and better understand the composition of a system using MVC. Objects are playing the roles of M, V and C, containing other objects that exists and communicates according to the mental model of the User. To signify how important the last part is, Reenskaug and Coplien have improved the name of the pattern to MVC-U, finally including the User, the most important part of the system. It is, after all, not only for ourselves but mostly for others we code.
DCI - The MVC complement
If you want to dive even deeper in system architecture: In languages today there isn't a natural place for functionality that involves a network of communicating objects. This is a topic that leads us to DCI (Data, Context, Interaction), Reenskaug's next invention, co-authored by Coplien.
It is a new paradigm in system architecture, designed as a complement to MVC. Quoting from the introductory article The DCI Architecture:
The MVC framework makes it possible for the user to reason about what the system is: the thinking part of the user cognitive model. But there is little in object orientation, and really nothing in MVC, that helps the developer capture doing in the code. The developer doesn't have a place where he or she can look to reason about end user behavioral requirements.
As with any new paradigm, it cannot be understood with help of the previous one, so it takes some effort, but is really worth the time. It will make you a better programmer, guaranteed. The official DCI webpage at http://fulloo.info/ is simple but informative, has a thorough FAQ, and as I've spent 10 years on the DCI frontier, I'm also available for answering your questions. Enjoy!
A final note
Reenskaug writes in a mail:
It is important to realize that these reports reflect an idea and a particular implementation. They are not a final answer where every sentence can be taken as normative.
A humble acknowledgement that things will be different in various systems, and in the future. If we strive towards simplicity and focus on the end user, the above ideas should in any case get us closer to a better way to write useful software, I'm quite certain of that!
Now when you know more about the human side of MVC, I hope it will become a great help in your future system design. Thanks for reading, and please let me know what you think!