In part one of this series I talk about Why MVC doesn’t fit the web from the point of view of writing web services, in the vein of Ruby on Rails and Express. This time I’m continuing that rant aimed at the modern GUI: The Browser.
MVC originated from the same systems research that gave rise to Smalltalk, which then had ideas imported into Ruby and Objective C that we use today. The first mention of an MVC pattern that I’m aware of was part of the original specifications for the Dynabook – a vision that has still not been realized in full, but that laid out a fairly complete vision for what personal computing could look like, a system that any user can modify and adjust. The software industry owes a great deal to some of this visionary work, and many concepts we take for granted today like object oriented programming came out of this research and proposal.
The biggest part of the organizational pattern is that the model is the ‘pure ideal’ of the thing at hand – one of the canonical examples is a CAD model for an engineering drawing: the model represents the part in terms of inches and parts and engineering terms, not pixels or voxels or more specific representations used for display
The View classes read that model and display it. Its major components are in terms of windows and displays and pixels or the actual primitives used to display the model. In that canonical CAD application, a view would be a rendered view, whether wire-frame or shaded or parts list data displayed from that model.
The way the two talk is usually that the model emits an event saying that it changed, and the view re-reads and re-displays. This lives on today in systems like React, where the pure model, the ‘state’, when it updates, triggers the view to redraw. It’s a very good pattern, and the directed flow from model to view really helps keep the design of the system from turning into a synchronization problem.
In a 1980’s CAD app, you might have a command-line that tells the model to add a part, or maybe a mouse operating some pretty limited widgets on screen, usually separate from the view window. Where there is interaction directly on the view, the controller might look up in the view what part of the model got clicked, but it’s very thin interface.
That’s classic MVC.
To sum up: separate the model logic that operates in terms of the business domain, the actual point of the system, and don’t tie it to the specifics of the view system. This leaves you with a flexible design where adding features later that interpret that information differently is less difficult – imagine adding printing or pen plotting to that CAD application if it were stored only as render buffers!
Last we come to controllers. Controllers are the trickiest part, because We Don’t Do That Anymore. There are vestigial bits of a pure controller in some web frameworks, and certainly inside the browser. Individual elements like an input or text area are most recognizable. The model is a simple string: the contents of the field. The view is the binding to the display, the render buffers and text rendering; the controller is the input binding – while the field has focus, any keyboard input can be directed through something that is written much like a classic controller, and updates the model at the position in the associated state. In systems dealing with detached, not-on-screen hardware input devices, there’s certainly a component that directs input into the system. We see this with game controllers, and even the virtual controllers on-screen on phones emulate this model, since the input is usually somewhat detached from the view.
In modern web frameworks, you’ll find a recognizable model in most if not all.
Backbone did this, giving a structured base class to work from, since it is
commonly mapped to a REST API in the form of its Backbone.Model
class.
Angular does this with the service layer, a pretty structured approach to
“model”. In a great many systems, the model is the ‘everything else’, the
actual system that you’re building a view on top of.
Views are usually templates, but often have binding code, read from the
model, format it, make some DOM elements (using the template) and substitute it
in, or do virtual DOM update tricks like React does. Backbone.View
is an
actual class that can render templates or do any other DOM munging to display
its model, and can bind to change events in a Backbone.Model
; React
components, too, are very much like the classic MVC View, in that they react to
model or state updates to propagate their display adaptation out to the
viewer.
The major difference from MVC comes in event handling. The DOM, in the large, is deeply unfriendly to the concept of a controller. We have a lot of systems that vaguely resemble one if you squint right: navigation control input and initial state from the URL in a router; key bindings often look a lot like a controller. To make a classic MVC Controller, though, input would have to be routed to a central component that then updates models and configures views; this split rarely exists cleanly in practice, and we end up with event handlers all directly modifying model properties, which reflect their state outward into views and templates.
We could wrap and layer things sufficiently to make such a system, but in the guise of ideological purity, we would have lost any simplicity our system had to begin with, and in the case of browsers and the web, we would be completely ivorced from native browser behavior, reinventing everything, and losing any ability to gracefully degrade without javascript.
We need – and have started to create – new patterns. Model-View-ViewModel, Flux, Redux, routers, and functional-reactive approaches are all great ways to consider structuring new applications. We’re deeply integrating interactivity, elements and controls are not just clickable and controllable with a keyboard, but with touch input, pen input, eye-tracking and gesture input. It’s time to keep a critical eye on the patterns we develop and continue to have the conversations about what patterns suit what applications.