View Design Patterns

View Design Patterns


There is a lot of confusion and misunderstanding about View Design Patterns. Model View Controller or Model View Presenter are tossed around to mean anything that has a route rendering a view. But things are a little bit more intricate than that. In this post, I am going to try to clear some of the confusion and give a clearer account of what each View Design Pattern means.

Unlike Software Architectural styles, View Design Patterns define how the presentation (or the view) is structured and layered, and how it interacts with the business logic. For example, if we consider the simplicity of Ports and Adapters, a typical UI pattern can elaborate on the outer (adapter) layers without heavily considering the inner business layers.

To visualise the outer layer (of UIs) below:

UI View is the "Outer" layer, also referred ot as Driving Adapter

In short, View Design Patterns affect how the user interface interacts with some core system, not the system itself. They typically end at the boundary border of the core layer but before the layer of the domain, and go no further. On the other hand, in your core layers, you can apply Clean Architecture, Ports and Adapters, or Vertical Slice Architecture, and they will work with most of the View Patterns described below. This is because each of the Software Architecture patterns mentioned has a place for outer layers where the View Patterns reside.

We will review major View Design Patterns in order of their appearance. Let's talk about the Model View Controller (MVC) first.

Model View Controller

First implemented in the Smalltalk programming language in the 80s, MVC is the first major View Design Pattern. Many people consider different things to be Model View Controller patterns. At its core, MVC consists of three things: a Model, which serves as access to the database and guards domain rules, a Controller that enables the Model to do its business logic, and a View that displays that manipulation to the user. 

The first and most important difference between the MVC and other view patterns is that the View is updated directly with the Model. A second characteristic of the MVC is that it is behaviour-driven. A user interacts with the Controller first, which results in outputting the correct View. The Controller decides what to do with user input which results from the user’s interaction. Next up, Model updates the View directly or via a subscription. As a perfect analogy, just imagine yourself playing the Xbox, you first interact with the controller and then await what will happen on the screen (the View). As the MVC diagram above implies, the Controller should not directly communicate with the View, but rather, through the Model.

To reiterate, in MVC it is not so that when you edit a form you first edit the View. Quite the contrary. First, the Model needs to be updated with the input provided by the Controller, and only then the View reflects the change. Each element (or component) on the screen can have its controller pair. Each widget can observe the Model and react to its changes, updating its View. Therefore, MVC is credited with being the pattern that popularised the Observer pattern. In the Observer pattern, the update is triggered and published to subscribers once a change is detected.

You might be surprised that there are multiple controllers on a single page. Yes, this is the traditional definition of the pattern. Martin Fowler on MVC pattern:

I should stress that there's not just one view and controller, you have a view-controller pair for each element of the screen, each of the controls and the screen as a whole. So the first part of reacting to the user's input is the various controllers collaborating to see who got edited. In this case that's the actuals text field so that text field controller would now handle what happens next.

As you can see, when people talk about MVC they notoriously mean something other than its traditional form. They either refer to the Model View Presenter pattern (MVP), or the evolution of MVC called Web MVC (we'll discuss it later with the example of ASP.NET MVC). MVC in its original form lost much of its importance with the advent of modern input implementations. For example, let's look at the new HTML specification. These days, an HTML View can do a lot more input logic on its own without the involvement of the Controller. This was not the case 30 years ago. Back in the day, every input change had to be consulted with the Controller first. This meant that every input field had to have an associated Controller.

Lastly, MVC is at its face value, a pattern that is subject to several problems on its own. For instance, in the modern evolutions of the pattern, such as Web MVC, complex behaviour causes the issue of a “fat controller”. Controllers tend to grow disproportionately big if they are responsible for managing many Models that render Views. The second problem is the high coupling of Models and the View. In MVC, a View is a visual representation of the Model and it directly emits events to update the View. Needless to say, tightly coupling your domain logic to your view is not that great as MVC can end up polluting business models with UI concerns such as colours, fonts and other stylistic pieces of UI. Several evolutionary MVC frameworks mitigate the above issues in one way or the other. If you’ve been in this game long enough, you’ve heard about a couple of highly popular MVC UI Frameworks that somewhat evolved. Let’s consider these through the lens of impact and history.

Highlight: ASP.NET MVC

Developed by Microsoft as an alternative to WebForms, ASP.NET MVC is probably the most popular framework that includes MVC in its name. ASP.NET MVC is an evolved MVC pattern.

It has a clear separation of concerns, an approach to testing, and pretty solid developer experience. It paved the way for component-based designs that were later adopted by the JavaScript frameworks.

Here's Model:

The corresponding Controller:

And the View:

As we can see, there is a degree of coupling between the Model and the View in the representation, and the business Model directly touches parts of the View.

A few important distinctions are here in place. ASP.NET MVC is an example of a thin client, where all parts (Model, View and Controller) reside predominantly on the server. As mentioned earlier, it is also an example of an evolved MVC pattern, which is often called Web MVC. Web MVC is a common approach these days. Because of the stateless nature of the web in the context of server-side interaction, the View does not directly observe events from the Model, and Controllers do not exist on the level of individual page components. The thin client nature means that the messages which arrive over the web are considered the user input to the Controller. The Model is then updated with the input, and the View is outputted from the server-side rendered template. The interactive View aspects do not belong to the server and have no direct observability. (It doesn't mean that they can't. In some modern frameworks, this is possible. I'll discuss these at the end of the MVC section.)

So, in the ASP.NET MVC example, the view does not directly observe events. However, we’ll see an example of that in the next section, in the context of a single-page application (an SPA). We are going to have a look at an old JavaScript framework, Backbone.

Highlight: Backbone

Backbone appeared sometime in 2010. It served as a lightweight library written by Jeremy Ashkenas, author of a popular library lodash. It attempts to give some MVC flavour to the current web development.

At the time, it was a moderately popular option, second to AngularJs only, but was eventually pushed aside by React in the JavaScript framework “wars”.

The below example illustrates the event-driven nature of MVC, where changes in the Model can directly update the View.

The Model in Backbone:

And the View:

As we can see in the example above, it is more of a traditional Model to View subscription, which is akin to the MVC style as popularised by Smalltalk. Nevertheless, there is a key major difference. The difference is that Backbone does not have a clear demarcation of a Controller, which they mention specifically in their documentation:

in Backbone, the View class can also be thought of as a kind of controller, dispatching events that originate from the UI, with the HTML template serving as the true view. We call it a View because it represents a logical chunk of UI, responsible for the contents of a single DOM element.

Can it then be considered an MVC? The author does consider it an MVC pattern, but not in a strict sense. In my opinion, it is.

I will here briefly mention another MVC implementation, which is worth mentioning, but was not as popular as Backbone. The framework in question is Mithril, namely its first implementation, which was MVC-heavy. (In the latest version, the team walked away from the concepts of MVC).

Here's the Mithril component that encompasses the MVC spirit:

As you can see above, each component in Mithril had its controller, which is truer to the original concept of the MVC.

The overall conclusion is that the MVC pattern is often the most misrepresented and misunderstood pattern of all. It is very hard to find an MVC pattern that matches the original intent (also described by Martin Fowler in his book Patterns of Enterprise Application Architecture). The majority of frameworks don't apply MVC the traditional way, but rather, they take a pragmatic approach. This exactly often leads to the problem of "fat controllers" mentioned before. If MVC was to be applied rigorously, it might not work well with the modern and rich UIs. Because of this, the industry quickly found that we needed something different, and Model View Presenter was born.

(Note that the part about MVC not working with rich UIs has to be taken with a grain of salt. With technologies such as Hotwire / Turbo Streams or HTMX, we can easily achieve granular Controllers which only update individual components of the page, and also listen to changes on the Model)

Model View Presenter

Model View Presenter (MVP) was developed by Taligent (a joint venture of Apple, IBM and HP). It appeared to address some of the pitfalls with Smalltalk’s MVC. As you can see, it isn’t always the most viable solution to have a model which updates the view directly. The high coupling of the model with the view is an inherent problem of the MVC pattern. This might result in the model not only having to store business logic but also addressing view-specific issues.

Therefore, in MVP, the view becomes more passive than in MVC. It no longer has the responsibility of directly observing the Model for changes. Rather, Model manipulation happens entirely facilitated by the Presenter layer. What is returned is a view data object that has little to do with the model itself. It is decoupled. The advantage is that we now have a clear separation between business and presentation logic, which is taken up by the Presenter layer. Presenters can manipulate fonts, colours and other UI-centric things, but those don't exist on the Model anymore. Changes in the presentation layer don’t impact Models, and vice versa. View passes all control to the Presenter and Model does not directly inherit the relationship with the View, nor does it manage it.

The View utilises a dispatch mechanism where any interaction is delegated to presenters. For example, on form submission, we can delegate action to the presenter’s handleSubmit handler. 

Here’s a Vanilla JavaScript example:

An extremely simplified presenter:

This minimalistic example is almost too simple; however, it is enough to exemplify the concept.

(In our example, we don't have any Model. A business model could, but ultimately shouldn’t exist on the pure client-side application. In the case of an SPA application, the solution would involve a presenter making an API call to the business logic layer.)

By going with the MVP pattern, we effectively decouple the View from business logic, achieving higher scalability and better testability. Both are important architectural concerns. Today, the MVP pattern is likely one of the most popular view patterns. A lot of frameworks, such as React, are perfect to implement it by design.

We’ve seen some MVP in action, but it’s worth noting one additional distinction inherent in the pattern. There are two flavours of MVP, Passive view and Supervising controller. Let's discuss both.

Passive view

The Passive view, as shown on the graph above, is close to our simple HTML/JavaScript example. View directly passes control to interaction to the Presenter handler. The View is "dumb" so to speak. Passive view offers the advantage of improving testability since View becomes a purely rendering piece.

Highlight: React (Passive view)

Chances are that you know React if you haven’t been living under the rock for the last ten years. React was developed by the Facebook team. On its own, it started an entire revolution in the UI paradigms.

Back in 2013, when React came to life, the most popular framework was AngularJS. Every developer was digging into two-way data binding, and the MVVM structure of the application. 

The problem that AngularJS tried to address was the complete lack of structure of most of the legacy jQuery apps, which oftentimes resulted in a problem of “spaghetti code”. Of course, you could structure your apps using jQuery, but it requires upfront effort. Rather than reinventing the wheel, AngularJS provided some basic building blocks for dependency management and separation of concerns. AngularJS used common software patterns like dependency injection, directives, controllers, and providers. However, this wasn’t all pristine. First, AngularJS had a quite steep learning curve. It set a very high entry bar for beginners. This frustration was addressed by minimalistic frameworks/libraries such as React or Vue, which had a much smaller learning curve and were less opinionated.

Second, people realised that we needed something more performant when working with extremely dynamic UIs. The two-way data binding paradigm as implemented by AngularJs simply wasn’t fit for purpose in those cases. Thus, rather than two-way data binding, React introduces a simple one-way binding mechanism. This mechanism was inspired by Von Neumann’s architecture. In React, we have a simple system in which data flows top-down (input to output), but not both ways like this was the case in AngularJS.

As much as React tried to solve many problems by being minimalistic and unopinionated, it created just as many in their place. Striking off years of good trends in UI development and patterns (see some great books by Addy Osmani) turned out to be a steep move for the industry. The state of UI Architecture in general (or lack thereof) is partly the result of that tendency. From one extreme to the other.

For now, it's important to understand that for all the reasons above, React fits perfectly to a Passive view pattern of MVP. Since the flow of change goes strictly top-down and there is no bi-directional data binding. Here's an MVP example in React.

Presenter hook:

View:

(Note that there is no Model per se in the above example, at best, we are setting in state some view data in a purely front-end application. At best, we could call that a View Model as it's tailored specifically for presentation. In reality, the business-centric Model should not exist on the client, but on the server-side part. In the React ecosystem, it could exist in a Next.js backend page, but this entails a Server-Side-Rendered (SSR) React environment. Recall the first diagram about Ports and Adapters where we identified UIs to be the periphery of the business logic.)

We discussed the delegation of the dispatch as a typical characteristic of the MVP. The code above uses, for example, handleSubmit in this fashion. A view delegates a call to the presenter, and then the presenter, in turn, calls back the view (to render the tree).

Supervising controller

Supervising Controller is a bit of a twist on the original MVP pattern. It enriches it by adding data binding. In this pattern, the View takes a more active part than the Passive view.

You might get confused at this point, if both Presenter and data binding exist, what is it that updates the models?

Well, it is both the Presenter and the View, but they have different responsibilities. Simple input changes can be bound to the Model straight. However, more complex changes that require complex business logic are taken up by the Presenter.

Let's see a concrete code example in Svelte.

Highlight: Svelte (Supervising controller)

Created by Rich Harris in 2016, Svelte is a wonderful web framework which has its compiler toolchain. It takes the pain of bundling from the developer and sprinkles a few bits on top of it.

Svelte incorporates a couple of ideas which we have seen in previous frameworks and trends but gives them their twist. Firstly, contrary to React, which opted out of the idea of two-way binding, Svelte embraces it. This influence is coming from KnockoutJs, as Rich Harris once stated. Secondly, Svelte adds a minimal degree of Reactive Programming by introducing simple streams (Svelte uses a special $ for it). Even though it doesn’t implement an entire reactive toolkit, it makes reactivity easier. For instance, Svelte allows us to implement a data store as a simple stream of events, to which components subscribe. Third, because of its compiler advantage, Svelte creates highly performant (zero overhead) imperative code which updates the DOM directly. All of that without the need to use any virtualisation like JSX.

To no one’s surprise, as a result of all the small goodies, Svelte took the UI world by storm, becoming the fastest-growing framework in recent years.

But enough chatter, let’s examine a simple example where the presenter layer binds the model to the View:

As you can see, the subtle difference is in bind:value. The View can be updated both by the presenter but also reflected by data-binding from the View.

Next, we'll discuss the Model View ViewModel pattern (MVVM).

Model View View Model

With the simplicity of Model View Presenter, the presentation logic can get quite complex and presenters can grow out of hand. In other words, it is conceivable that the presenter can become a complicated module that deals with just too much. Here’s where the introduction of a View Model might help. This is, consequently, called the Model View View Model pattern.

A key thing to understand in the MVVM pattern is that a View Model is an intermediary between a View and a (business) Model. View interacts with the View Model asking if there is any business-centric change. The relationship is bidirectional. The View Model is free to update the Model and return updated view data. On the Model’s update, the View Model can emit change events back into the UI whenever depending on the technology chosen.

A big difference between the MVP and MVVM is that, in the latter, there is no presenter. Instead of a Presenter, there exists a binder. Through the binder, View binds directly to the View Model. Additionally, in MVP a View typically relates to the Presenter in a one-to-one fashion, whereas in MVVM a single view model can exist in multiple views.

As a highlight and an example to illustrate we are going to use a known MVVM framework: Knockout.

Highlight: Knockout

Knockout.js is a JavaScript library that was first released back in 2010 by Steve Sanderson, a developer working for Microsoft at the time. It gained popularity for its simplicity and the use of MVVM in the web setting. 

The main features of Knockout include declarative bindings and automatic UI updates. Declarative bindings allow HTML tags to be linked to View Model’s values using an attribute syntax on the HTML tags themselves. The selling point is the automatic HTML updates, where a developer doesn’t have to write explicit update code. This makes it easier to manage dynamic interfaces where changes in the model need to be reflected in real time.

Knockout historically featured dependency tracking. It can automatically detect when (and which) UI elements need to be updated based on changes in their dependent model. Moreover, what was new back then, and it's still relatively hot these days, is implementing the concept of Observables. Observables are special entities that notify subscribers when changes occur. We won't talk about Observables here much, but they are a part of the Reactive programming paradigm in general. The Observable pattern can be thought of as the combination of the Observer and the Iterator pattern.

Let’s see a quick example similar to our earlier examples.

An HTML View:

And a Knockout (Observable) Model:

A note on Knockout. Even though it is a largely forgotten framework these days, patterns coming from Knockout are coming back with a vengeance. We can see some of those tendencies, patterns and influences in newer frameworks such as Svelte.

Highlight: Svelte

Before, we had Svelte served to us as a Supervising Controller pattern within the MVP pattern, but we can also make it work with the MVVM pattern. It is extremely versatile in adopting patterns.

View Model in Svelte:

The View itself:

Voila! An implementation of MVVM in Svelte. What remains is the application of Model synchronising View Model with something server-side like SvelteKit (an equivalent to React's Next.js framework).

Conclusion

Those are the key and most influential approaches to structuring UI apps. This is by no means an exhaustive list of View Patterns, but it is enough to demarcate their importance, both from a historical lens, and how much they bring to the table.

At least, I hope we now understand better how View design patterns have come a long way since MVC first appeared in Smalltalk. They introduced a structured way to separate concerns of the apps. From one shortcoming to another shortcoming, they lead to the evolution of patterns. Each of these evolved to address specific pain points, whether it’s MVC’s tight Model-View coupling, MVP’s fat presenter problem, or else.

At the end of the day, no single pattern is a silver bullet. The best approach depends on the complexity of your UIs. Understand how much separation and testability you need, and how tightly you want to couple your view to your business logic. Frameworks continue to blur the lines between these patterns, but understanding their core ideas helps make better architectural decisions.

Subscribe to Trust Me I'm an Architect

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe