Sitemap
CodeX

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Follow publication

User-Interfacing Software Design Patterns, Revisited

13 min readApr 19, 2021

--

When developing software that is facing the general public, it is first and foremost important to get the interaction with the users right. Nobody will care that there are fancy design patterns behind the scenes, if, for them, navigating your application is an absolute nightmare.

That being said, however, it is also important to remember that there are two sets of people that are somehow using your software: the users, and the developers. While the success of a piece of software depends on the interaction with the users, the success of a software development team depends on how it builds software. At this stage, design patterns become important.

Let’s then, take a look at design patterns that extend all the way to user interaction. We will look at MVC (model-view-controller), MVVM (model-view-viewmodel), SAM (state-action-model) and CQRS (command-query responsibility segregation), with the BFF (backend for frontend) “anti”-pattern.

One thing to note in this story is that these patterns are examined from a web application perspective, and might need slight adjustment when applying to anything else.

MVC

MVC stands for Model-View-Controller and is possibly the oldest in the list, being invented somewhere in the 1970s.

The MVC pattern, as used with a query operation, and as used with a command operation
MVC pattern: query (top) and command (bottom)

The main entry point for a user is the controller, which exposes actions. When users decides to interact with the software, they do so by invoking an action on the controller. The controller then does what it’s supposed to do, and creates a model, which is essentially a structured data package, and applies a view on top of it, which transforms that data into a format that can be sent back to the client (HTML in the case of web applications). The client then displays the data to the user and the whole system pauses to wait for further action from the user.

Users, if they decide to interact with what is presented before them, can bring alterations to the model, and can submit it back to the controller via an action (which can be the same, but doesn’t have to). The controller will take that modified model and, after validation (which, depending on the framework and business logic, can take place in multiple places and in multiple ways) will start doing what the action is supposed to do. When done, it generates another model and applies a view on top of it, sending it back to the user.

A few key things to take out:

  • MVC is merely reacting to the user’s requests (be them manual or automatic), and by definition does nothing absent a user; certain actions can be invoked automatically by the browser, but they will appear as user-invoked actions to the controller, as it (in principle) cannot tell that a piece of JavaScript code is doing the request
  • This pattern is controller-centric and action-centric — controllers and the actions they expose are the only components that actually need to exist in order for this pattern to work; a controller might not create or accept a model, or it might not need it at all, and there might not actually be a view per se (unless you consider output of raw data into a HTTP-compliant format as some sort of a default view)
  • It is relatively easy to change in terms of how data is presented to the user — including extended support for browsers, various viewports, etc., without affecting the business logic of the application; even if you do introduce a bug, if MVC is done right, and the model is well-validated (which is usually a difficult task), it should be a rather easy to spot and correct it in most cases
  • both CQRS and separation of concerns are rather easy to achieve, with a good MVC-based design
  • It is relatively easy to conceptualize tests, but it is relatively difficult to actually write tests, as there is seriously tight coupling within the controller, and especially with the underlying framework (ASP.NET MVC is a good example of this, as calls to the underlying framework cannot be abstracted away in most cases, and such calls are necessary for a lot of functionality)
  • A lot of developers assume that the model is somehow coupled with the persistence model (for instance, the database model) — this is not true, as the MVC pattern makes no such requirement — whether or not the model matches the database in any way is solely the decision of the implementer, and the database model can be subject to a composed or related MVC pattern, as outlined below:
Composition of multiple MVC patterns, one for serving the user, and another for the database
Composition in MVC

MVC is a pattern that is easy to understand, and easy to implement, but, also, easy to misunderstand, and seriously easy to misuse. Amongst the biggest sins of developers using this pattern are:

  • not figuring out that they are using composition when they shouldn’t
  • not seeing all the links to the underlying framework, and the tight coupling that comes with it
  • mixing UI code in business logic (especially in frameworks that don’t encourage tight separation, such as Blazor)
  • poor model/view design, resulting in a hazy border between business data, operational data and UI data
  • poorly designed middleware that interferes with normal operation when it shouldn’t, or doesn’t interfere with it when it should
  • fragile request state due to poor execution path design, especially with underlying frameworks that practically demand asynchronous operation

MVC is a tried-and-tested design pattern that has stood the test of time, but is becoming a thing of the past, as more and more developers manage to wrap their heads around advanced concepts of control flow, security, CQRS, scaling, massive parallelism, etc.

Further reading:

MVVM

MVVM stands for Model-View-ViewModel, and is, at first glance, an antithesis of the MVC pattern, from the perspective of user interaction design.

The MVVM pattern
The MVVM pattern

The entry point for MVVM is the view, and all interaction with the user is done through it. The view dictates what data is presented to the user, and how. The view has a life of its own, and, in practice, any view from a collection of views which make up an application may or may not have any of the rest of the components, while still correctly implementing the pattern as a whole.

Views in MVVM are usually considered to be declarative, meaning that they’re supposed to be a declaration of data and how everything binds, and commanding, meaning that various actions that are made available to the users are declared through binding to commands (or through a very similar overridable system for default actions or internal workings). In the case of the .NET environment, this is achieved via the extensible application markup language (XAML).

The model is the domain model, or, really, any model of data that we wish to operate on (it may or may not be a data access model, or a database model, or a REST/SOAP data model, etc.), with little limitation.

The view-model is basically a UI-useful abstraction of the model, and is used for connecting things together, and holding all the intermediate state between the model and the view. The view-model, as an abstract concept, does not need to be functional in any way, however, in practice, the logic that supports the UI, as well as the logic that supports changes on the model, as well as the internal state of the view, are being held in the view-model.

An accessory, as stated in the image above, is the binder, which has the purpose of linking parts of the view with parts of the view-model, and is usually the heart of the binding system of MVVM, implemented specifically and usually differently in any framework.

A few key things to take out:

  • MVVM is view-centric, meaning that user interaction is at the centre of its philosophy, and clearly separates the model, meaning that the only component that is tightly-coupled and will always be affected by non-cosmetic changes is the view-model (note: the view-model is only conceptually tightly-coupled with the view — in practice, since they rely on entirely different writing styles, most compilers and IDEs will not notice a lot of possible errors, especially if bindings are not type-safe, which will only surface at runtime; an especially-annoying implementation is WPF, the binding system of which depends on the visual tree, which is not always what the developer expects)
  • MVVM offers disconnect between model and view, but the view-model is tightly coupled to both
  • Unless someone has seriously messed up their design, UI-supporting code should be completely separate from the model
  • Composition of MVVM can be done at any level, and every view can be broken down into subcomponents, themselves implementing the MVVM pattern either to a model of their own, through a view-model of their own, the same model as their parent, the same view-model of their parent, or any combination of the above, in any number (it is possible to have components that use both their parents view-model and a view-model of their own, and the same goes for models)
  • Unit testing and mocking is rather easy
  • The learning curve of the MVVM model is steep by comparison

Further reading:

SAM

SAM stands for State-Action-Model, and is a departure from the previously-describe architectures, in the sense that it makes both a clear separation between the domain-driven model and the application state, and also gives clear option to the domain-driven model as to how to update itself according to its own rules.

The state-action-model pattern, with components and subcomponents
The SAM pattern

The user is presented with a view, which is a pure function of the state of the application. The view works pretty much as one would expect the MVC view.

The user then gets the chance to perform an action. An action is, in fact, a proposal for the model to change in a certain way, proposal which goes through its own set of validators (in practice, these validate whether the proposed change is formally valid), moving on to the model.

The model, through acceptors (which validate whether the proposed change is valid as per business rules, considering the current state of the model), chooses whether to accept the proposed change, or to discard it.

Should the model accept the proposed change, it will incorporate the change and make its own internal state valid as per that change, then either react to it, updating the application state (through reactors) or by getting the state to learn of a mutation (through learners), which state, in return, may, according to its own rules, propose a new action, and/or update the view presented to its user.

The key things to take out:

  • The SAM pattern gives the model a life of its own, emphasizing the domain rules regarding it, while decoupling it completely from the view
  • The pattern focuses on mutations of the model as the source of state change
  • The view can be mathematically described as a pure function of the state, at any moment, and is supposed to be recomputed as the state mutates
  • SAM shuns the idea of managing subscriptions to events, and, instead, relies on the user’s interaction pushing suggestions for state mutation
  • SAM has its roots in TLA+, a software modelling language created by prof. Leslie Lamport as part of Microsoft Research

Further reading:

CQRS

Standing for command-query responsibility separation, CQRS proposes that any command/action/event handler should be one-directional, in the sense that it should either query data, or mutate data. It is not an alternative to the previous patterns, but an addition to them.

The command-query responsibility segregation pattern.
The CQRS pattern

The query part represents a one-way data selection, conversion and arrangement, with the purpose of serving data to the user in a specific and desired format (either a view, or maybe exports, or reporting).

The command part represent a one-way data mutation.

In practice, however, many times, the command also has a query part, which retrieves the entity that it has mutated, in order to display its new state to the user. This is usually used by small pages which focus solely on one entity, which usually has an almost one-to-one mapping with a domain entity.

While this practical situation does not implement CQRS in its strictest definition, it is usually considered an accepted practice, since it is unlikely that an entity will change in a way which only affects the way it is written, and not the way it is read.

Implementing CQRS in MVC is rather easy, as actions in the controller can be designed specifically to query or to mutate. In the case of ASP.NET MVC, for instance, and most other frameworks involving HTTP, one could argue that the intention was to support a specialized implementation of this pattern right from the start.

With MVVM, things are not that simple, as there is not necessarily a clear distinction between mutation and the simple effects of data binding. Since (usually incorrect, or, at least, overeager) binding logic can mutate the underlying model beyond what is required from the perspective of UI logic, CQRS is best implemented strictly inside the view-model, in the sense that, even though the view-model mutates its internal state as a result of data binding, those mutations do not impact the underlying model, unless a command (such as a conceptual “save”) triggers this mutation.

SAM does not concern itself much with CQRS, as mutation is solely in the will of the model (thus the idea of “commands” from a CQRS perspective is not particularly relevant), and the view (as a conceptual query) is, basically, a function of the state. From the perspective of acceptors and reactors/learners, CQRS is implemented by definition.

A few things to take out:

  • CQRS aims to make flows of data one-directional, in order to simplify the application
  • By separating queries from commands, CQRS aims to isolate changes in the application, by lowering the number of sources of volatility; should an entity change its structure, but not in a way that impacts some queries, those queries need not be touched; similarly, should a query be changed by some business need, then the commands need not be touched, even if the query is expanded to include new entities
  • Complex domains benefit most from CQRS, especially when coupled with a single point-of-change policy in application design, although, in practice, this may not necessarily always be possible
  • Extreme care needs to be taken for resource access synchronization, as a poorly-implemented multithreaded application that follows strict CQRS will almost certainly be prone to many inconsistency issues and race conditions

Further reading:

BFF

Considered by many an anti-pattern, or a comical last-resort pattern, Sam Newman’s BFF stands for Backend-For-Frontend, and can be considered both as an able critique of general-purpose backend API architectures, as well as a useful pattern when there is simply no reason to have a general-purpose backend API architecture.

An example of general-purpose backend, and the backends-for-frontends pattern
General-purpose back-end (left) and BFF pattern (right)

The idea behind this pattern is that unique frontends (or category of frontends) have unique needs, and, therefore, should also have unique backends (or, at least, public-facing sets of APIs).

The idea is already widely-used, as the needs of an AJAX-based web application differ from the needs of an iOS application, for instance, in terms of session management, security, trust, accessibility, data flow, etc. So, the idea is that there is no reason to burden an application with has better API capabilities with a limited API.

Similarly, communication can be done in multiple ways (easiest example is a web assembly with web sockets technology, versus a classic AJAX-based application), which means that some facilities for APIs can dramatically improve performance, speed, and features, which would otherwise be very difficult with a general-purpose back-end.

A few key things to take out:

  • BFF is based on the inequality of frameworks, hardware and underlying system facilities between differing types of frontends and/or clients
  • considered by many purists an anti-pattern, the idea behind BFF is actually very useful in specific circumstances
  • BFF does not necessarily mean that other patterns cannot be implemented — indeed, each of the backends can correctly and completely implement other patterns
  • when implementing BFF, there is always the risk of code duplication — in which case, either back-ends can be made to have a common root, or, maybe, depending on the nature of the duplicated code, a new service can be implemented for that common part
  • BFF can also be implemented towards external service consumers, meaning that on the same set of services, different downstream services (federation, streaming, etc.) will connect to different “frontends”
  • BFF is meant as a way to parallelize development across teams, while offering autonomy
  • One of the key concerns with BFF is keeping features in sync, which means that product owners will have a harder job managing such an architectural choice

Further reading:

--

--

CodeX
CodeX

Published in CodeX

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Adrian Mos
Adrian Mos

Written by Adrian Mos

Software developer, architect and father, with a love of science and writing, and a desire for global social and economic development.

No responses yet