Entropic Thoughts

The MVC Mistake

The MVC Mistake

Creating abstractions should not be left to beginners. This is what Richard Gabriel says:1 Patterns of Software: Tales from the Software Community; Gabriel; Oxford University Press; 1996.:

Abstractions must be carefully and expertly designed, especially when reuse or compression is intended. However, because abstractions are designed in a particular context and for a particular purpose, it is hard to design them while anticipating all purposes and forgetting all purposes, which is the hallmark of the well-designed abstractions.

This is one of my favourite quotes on abstraction, because “anticipating all purposes and forgetting all purposes” so nicely summarises what one needs to do to make a good abstraction. Thus, I agree strongly with the first sentence of issue 34 of Frontend at Scale, where it is phrased as “how to care about anything without caring about everything”.

The issue of Frontend at Scale unfortunately derails quickly after that.

Let’s take a simple layered architecture for example:

  • Presentation layer
  • Business layer
  • Persistence layer
  • Database layer

Each one of these layers represents a different logical concern.

No, they do not. This is the mvc mistake all over again. Those layers represent boundaries between technologies. If we want to add a feature to this system2 Or remove one, for that matter., we have to go into every single layer and make the corresponding change. That is the polar opposite of separation of concerns. When concerns are appropriately separated, changes to requirements have a small blast radius. With an mvc type architecture, there’s a 100 % blast radius to adding a feature: it goes into every single layer of the application.

So when the issue says

By separating concerns, I’m not talking about putting barriers between the different layers, though. After all, they all depend on each other to some degree, and the easier and quicker we can move across them, the better.

the author unknowingly admits this problem. Only when we have failed at subdividing our architecture do we need to move quickly across layers – because changes require us to modify components across layers in the architecture.

Business domain concepts trump technical boundaries

David Parnas is the forefather of modularisation and software architecture. When Parnas wrote some of his excellent papers on the subject in the 1970s, it was common to architect software along technical boundaries, just as mvc suggests today. Parnas realised two things:

  1. Business domain concepts tend to change less than technical concepts. In other words, our caching strategy, data storage, algorithm choice, etc. change relatively often, compared to, say, what defines a loan, a stamped sheet of metal, or a sale.
  2. Features come and go more often than technical concepts. Extension and contraction3 Roughly speaking “adding and removing features”. happens most often at the business logic level. Maybe yesterday we made rifle barrels, and today we also make bicycle frames, but the systems we use to aid manufacturing stay roughly the same.

The two points above might sound contradictory, but they are not: business concepts/features are added and removed often, but for as long as they are in there, they tend not to change much as technical solutions and infrastructure concerns.

This means that modules drawn along technical boundaries – as suggested by mvc and Frontend at Scale – are more coupled, less cohesive, and make extension and contraction harder. Those module boundaries don’t align with the boundaries between things that are added and removed together.

Example: software for a grant making organisation

As an example, imagine we’re an organisation giving out grants for research in some area. We have structured our early-stage software platform in the mvc manner, and are at this point in our development:

mvc-mistake-01.png

Now we want to add the ability for users to write comments on yet-to-be-approved grants. We will have to extend all three layers and add more code for that feature to them, because that’s what happens when one draws module boundaries along technical lines. It’s clear why Frontend at Scale suggested we needed to “move quickly across layers” when this is their idea of how to structure code.

I have a suggestion for how to do it instead, where “moving quickly across layers” becomes unnecessary: build it on core business fundamentals4 Note that I’ve thought about this specific case for all of a grand three minutes, so there might be an even better way to do it. I’m not trying to prove my architectural skills here. I’m only illustrating a point.:

mvc-mistake-02.png

If we want to add comments to this, we will probably need a business logic column for that, but we would not have to do anything about what’s below the horizontal dotted line. Business concepts rarely change when features change. We’ll plug in our commenting flow using existing functionality among the business concepts.

To emphasise how we don’t have to “move quickly across layers” in this codebase, we could implement domain concepts as their own libraries. We could even open-source those libraries, should we dare, and maintain them on a separate schedule. We don’t need to move fast with business fundamentals, because business fundamentals don’t move fast.

As programmers, we already know this. We can construct good libraries. Open up your npms, PyPIs, Mavens, NuGets, Packagists, RubyGems, etc. Full of great libraries. That is the type of abstraction that works to minimise maintenance over time. We just forget how to do it once we start developing an application.

Following in the footsteps of Dijkstra

The Frontend at Scale issue quoted Dijkstra for support. But they didn’t do what Dijkstra suggested elsewhere. He had another great idea: in a layered architecture, each layer should act as a virtual machine on which the next layer is implemented. The bottom layer holds definitions that practically never change, barely over the level of abstraction provided by the programming language itself. At the top layer is where features emerge by piecing together functionality from the layer immediately below.5 Dijkstra emphasises that a layer should never reach down multiple layers – it always interacts with the layer immediately below itself. This way, expressive power is removed and the application is refined as we go up the layers.

Even if requirements change drastically, we may still be able to reuse the bottom few layers – as long as we’re still in the same business. This is in large contrast to an mvc type application which will need major rewrites when requirements change drastically.

For good, helpful modularity, we need to do is the opposite of what mvc suggests. We knew this 20 years before mvc was created. We’ve known this for 50 years now. Yet mvc and derivatives are still used, and used as examples of good modularity.

Locality of behaviour in an onion architecture

Parnas suggested the phrase ease of extension and contraction to refer to good modularity, but this phrase never caught on. The vm-based approach was called a layered architecture once, but that term has since been bastardised, and “vm-based” is a little confusing too. It’s also been called inside-out design but that is very easy to confuse with bottom-up which is something different.6 Inside-out can mean either bottom-up or top-down depending on whether the bottom or the top is the inside … We need better words.

Carson Gross attempted to capture the idea of extension and contraction with the term locality of behaviour which sounds nice. I’ve also heard the term onion architecture for an inside-out design.

Please help these ideas from the 1970s catch on. Encourage locality of behaviour. Adopt an onion architecture. It is about time.