MVC Tutorials Are Broken
Tags: python, perl, programming
Published: 2021-03-15 10:38:47

> Note: The Model, View, Controller pattern, or “MVC”, actually comes in many flavors, but for the purposes of this article, I’m referring to MVC for the Web. Feel free to comment below, but keep in mind that we have a particular viewpoint here.

The Problem: getting MVC wrong is a common—and expensive—error.

When I start working with a new client, I often find software with common sets of mistakes. One egregious mistake is the misunderstanding of the Model-View-Controller (“MVC”) pattern. MVC—[which has gotten an unfair bad rap](https://www.google.com/search?q=why+mvc+is+bad)—is thoroughly misunderstood and much of this can be blamed on rubbish examples and tutorials.

Here’s a perfect example of [bad MVC using Python and Flask](https://realpython.com/the-model-view-controller-mvc-paradigm-summarized-with-legos/):

```python
@app.route('/')
def main_page():
    """Searches the database for entries, then displays them."""
    db = get_db()
    cur = db.execute('select * from entries order by id desc')
    entries = cur.fetchall()
    return render_template('index.html', entries=entries)
```

In this example, we have a page which finds “entries” and displays them on a Web page. It’s entirely possible that you see no problem with this, but it perfectly demonstrates what’s wrong with people’s understanding of MVC: you’ve pushed knowledge of your database, your business logic, and your HTML into the controller.

MVC examples and tutorials are teaching developers how to build unmaintainable software. As a result, when I work with a new client using “MVC”, I often see this:

```python
def some_controller_method():
    """begin hundreds of lines of code"""
```

How do I hate thee? Let me count the ways. (with apologies to [Elizabeth Barrett Browning](https://poets.org/poem/how-do-i-love-thee-sonnet-43)). The following comes from years of experience seeing this over and over again.

* The database or ORM is exposed at the controller level
* You can’t reuse that logic outside of the controller
* You can’t test that logic outside of a Web context
* Tests are much slower because of the Web context
* Test coverage suffers because it’s hard to test conditional logic in the controller
* The logic often gets duplicated in other controllers
* Different behaviors become tightly coupled, making it hard to develop
* Performance suffers because you can’t cleanly target a layer for optimization
* Integrating new technologies is harder because the layers are tightly coupled
* ... and so on

If the above list seems huge, that's because this is a terrible problem that costs companies a lot of money. It's one of the worst problems in modern web application development. But what does this mean in practice? Let’s consider a real example I encountered a few years ago.

One company I worked with had all of the above problems. In particular, they had a **3,000 line “method”** for their search engine. It conflated many things, including searching for products and filtering the search results. Thus, even though I was tasked with working on it, every time I touched search, it would break filtering. Every time I touched filtering, it would break search. It was slow and tedious to get anything done. Further, that method generated HTML that was injected into a Web page!

It took me some time to separate search, filtering, and moving HTML generation into the view. When I was done, I had a surprise visit from one of the developers of the mobile side. It seems they wanted a proper search engine for ages but never added it because they couldn’t because their needs were so different from the Web context. After this work, it only took them fifteen minutes to implement a search engine.

Let me repeat that because it’s important: the mobile app had never had a proper search engine because the Web app had pushed all of the logic into a _Web_ controller. Fixing that allowed the mobile team to get a search engine running in fifteen minutes.

Not getting this right **flushes money down the toilet**. Often we hear that a new feature will take a lot of time to implement “because the application wasn’t designed for this use case.” What was actually meant is “because we didn’t understand [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) and this simple feature will take months to develop.”

Note: [you can fix this mess](https://ovid.github.io/articles/a-simple-way-to-fix-legacy-code.html), but it takes time and you have to understand what MVC should look like.

# A Proper Controller Method

To contrast this, let’s look at a the controller for an extremely complex bit of logic from the free MMORPG, [Tau Station](https://taustation.space/), using Perl and [the Catalyst MVC framework](www.catalystframework.org):

```perl
sub station ( $self, $c, $station ) : Local Args(1) {
    $c->character->travel_to_station($station);
}
```

Without going into detail about what `travel_to_station` does (trust me, it’s complex), all we see is a request for a complex action to be performed. In the controller we can see:

* Knowledge of the route to this controller method (the `Local` attribute)
* But no knowledge of how the model is constructed
* And no knowledge of how the data will be used

Having the route information hard-coded is a bit unfortunate, but that’s easy to work around. However, this controller method merely connects the model to the view, and that’s all. That’s what you’re looking for in MVC. MVC, when appropriate, takes an application with a UI (user interface), and decouples the responsibilities into reasonable layers.

It’s now easy to test the logic (you’re testing the model, not the controller layer and all of its Web context). It’s easy to reuse this code. And, if the model is properly built, you have a great separation of concerns that makes it easy to maintain and extend the application.

* Model—The application.
* View—What the consumer (user) sees. Often HTML, JSON, or XML.
* Controller—A thin layer connecting the View and the Model.

And that’s it! There’s nothing fancy, but it often requires explanation. The controller, as shown above, should be as thin as possible. The view should only have display logic and the model is ... what, exactly?

# The Model

So let’s dig into the model. Almost every MVC tutorial that I see confuses the data model with the Model. Think of the model as “the business logic”, not the data. This is what we get (assume all following examples are pseudo-code):

```python
@app.route('/neighbors')
def neighbors():
    location = self.character.location
    neighbors = Character.query.filter_by(location_id=location.id).all()
    return render_template('people.html', people=neighbors)
```

Now that looks reasonable. In fact, it seems so sensible that I used to write my MVC code like that because that's what tutorials taught me. In the case of Tau Station, the above was deeply flawed. The above query tightly couples a controller method to the underlying structure of the database. If you need to change that structure, oops! But it’s just one method call, so factoring it out is silly, right?

Wrong. As it turns out, you didn’t want to show any characters who hadn’t been active in the past day.

Unless they were NPCs.

Or the other characters are not visible.

Any time we discover a new case of when characters are or are not visible to a given viewer, we can revisit this logic in every controller method which needs to know this or we can abstract it out into a single method:

So let’s try it again:

```python
@app.route('/neighbors')
def neighbors():
    characters = self.get_orm().model.table('Characters')
    neighbors = characters.visible_to( self.character )
    return render_template('people.html', people=neighbors)
```

Ah, that’s better. Except we have exposed our ORM (and thus, our database). We have many data sources which have data in Redis, or configuration files, or a custom cache, or from all sorts of locations that the controller should never, ever know about. Some of my clients with this anti-pattern have worked around this by trying to implant those other data sources directly in their ORM. This simply shuffles the coupling around. The controller should never care about the source of data. The controller should only be asking for the data. Instead:

```python
@app.route('/neighbors')
def neighbors():
    characters = self.model('Characters')
    neighbors = characters.visible_to(self.character)
    return self.template('people', people=neighbors)
```

Where do the “visible” characters come from? You don’t care. By hiding all of these details from the controller, the developers are free to implement the fetching of this data any way they want, so long as they respect the contract originally specified when this method was created.

And we return a template via a name, but we don’t claim it’s HTML. The instance can figure out its calling context and render accordingly.

The model is your _business logic_, not your data model. And I’ll go a step further and say a good design choice is to make the model provide services the client needs, with a [headless application](https://en.wikipedia.org/wiki/Headless_software) underneath. By creating a this, you can layer a controller and HTML view on it. If you’re careful, you can have your controller target multiple views and build a native mobile application on top of it, only requiring a new view. You could create a native client/server desktop application, too. If the controllers are too inflexible, that’s OK. They’re tiny and easy to skip. Write thin controllers specifically for your mobile and desktop apps, secure in the knowledge that the application logic is all in your headless application.

MVC controllers should be like air traffic controllers: they don’t fly the plane, they just tell them when to takeoff and land. When you write controllers without logic, that are only able to connect the view to the model, you’re writing better software.