avatarNicklas Millard

Summary

The webpage advocates for refactoring controllers to eliminate business logic and data access, promoting very thin controllers for improved maintainability and flexibility in software design.

Abstract

The article emphasizes the importance of not allowing business logic or data access within controllers, a common practice that can lead to duplicated logic and entangled responsibilities. It argues that a controller's sole purpose is to handle HTTP requests and responses, with everything in between being outside its scope. The author suggests that modern web applications, which are primarily APIs, should treat controllers as a presentation layer, detached from data retrieval and business logic concerns. To achieve this, the author proposes a refactoring process that involves creating a user repository class for data access, adopting a simplified version of the Command Query Separation (CQS) principle to encapsulate business logic, and injecting specialized query classes into the controller. This results in a cleaner, more maintainable codebase where each class has a narrow responsibility, adhering to the SOLID principles and the separation of concerns.

Opinions

  • The author believes that the common practice of including business logic and data access in controllers is a bad habit perpetuated by some tutorials and even documentation from reputable sources like Microsoft.
  • The article suggests that the Model-View-Controller (MVC) pattern is outdated for modern web applications, which are more focused on exposing data through APIs.
  • It is stated that placing business logic and data retrieval in controllers is analogous to placing it in views in the context of the MVC pattern, which is not recommended.
  • The author posits that the complexity of a system does not necessarily increase with the number of classes if each class has a single, well-defined responsibility, making the system more enjoyable to work with.
  • The author provides resources for further reading on design patterns, the Command pattern, and arguments against the use of MVC frameworks.

BETTER SOFTWARE DESIGN

Your Controllers Are Doing Too Much — This Is How to Simplify Them

No business logic or data access allowed. Refactor to very thin controllers for better maintainability and flexibility

I’m sure you’ve at least once before accessed a database directly from inside a controller. Perhaps you’ve also snuck some business logic in as well.

Chances are you’ve learned this practice from the many tutorials you have watched and all the documentation you’ve skimmed thru. Even Microsoft is guilty of this.

This practice doesn’t seem too bad at first as it gets you out of the gates almost immediately. However, it has some massive drawbacks such as encouraging duplicate logic and entangled responsibilities.

This is the kind of bad practice we need to eliminate.

Let me tell you this, controllers shouldn’t do anything remotely related to business logic, and directly access data stores.

The controller’s only purpose is to receive a request and return a response. Everything that goes in between is not its responsibility.

So, with regards to the statement above, you should now be aware that code like the snippet below is a complete no-go.

Controller accessing a data store

Accessing a data store, or even a repository, from a controller, is bound to introduce trouble down the road.

This controller is so far an incredibly simple one. It first starts to get exponentially crazier when you’re also inserting data into your database from the controller.

Your controllers should be super thin

In modern days, we’ve left the Model-View-Controller (MVC) pattern on the shelf. It’s no longer a pattern any new web application uses. Now, we’re mostly building APIs that’ll expose data to the world. We can think of our controllers as the modern-day (data-)presentation layer.

It’s simply well outside the responsibility of the controller to know how data is retrieved — such as remembering to include the user’s list of articles.

Placing data retrieval and business logic in controllers nowadays is the equivalent of placing it inside old-school views, in the context of MVC.

I’m sure you wouldn’t want to do that… so why should you do it in modern controllers?

Let’s refactor that poorly designed controller

Show me the code — git repo

Refactoring the messy controller requires a tad more leg work from us. I propose three simple refactoring steps.

  1. Create a user repository class that’ll deal with data access
  2. Use a poor man’s version of CQS to encapsulate business logic
  3. Inject a specialized query class into the controller

We’ll end up with a controller looking like this snippet below. Neat, clean, and easy to understand.

Now, the controller has no idea of how data is accessed. All it knows is it must use this GetUsers query class.

I’ve drawn an overall solution structure to the absolute best of my abilities… I hope this will help you to understand the different parts and code snippets I’m going to show you.

So, let me give you a brief guided tour thru our system.

1 First off, some user makes a GET request to our web client’s controller at api/users that invokes the controller action Get(). The controller takes a dependency on aGetUsers` class which resides in a different module — or project in .NET terms.

2 Our GetUsers class is a special query class that has a dependency on a repository interface defined inside the same module. At runtime, we’re getting a concrete implementation of the IUserRepository` thru a dependency container.

Simple query class to fetch users

3 The UserRepository` implements the required interface used by GetUsers. To retrieve the actual user objects, we need access to a specialized data store class. In the case of .NET, we’ll be using EntityFramework Core for this. It comes with a DbContext class that much like an in-memory database. I’m sure your framework of choice has something similar to this.

Notice how the UserRepository` now has all the knowledge required to query the database for the full user object graph — i.e. including every users’ articles.

We’ve completed pull out this knowledge from our controller, which now, only deals with accepting a request and replying with a response.

“It’s a lot more complex now”, you might say

Well, we increased the number of classes.

Some equal this to increasing complexity. However, each class now has a very narrow responsibility. Whenever we’re about to go beyond a class’ responsibility, we make a new one that’ll care only about one single functionality.

Following the separation of concerns principle, you’ll quickly realize how enjoyable it’ll be to work with your solution.

Resources for the curios
-------------------------
C# Design Patterns: Command by Filip Ekberg
Command in C# by refactoring.guru
Why I No Longer Use MVC Frameworks by Jean-Jacques Dubray
Is Model-View-Controller Dead On The Front-End by Alex Moldovan

Nicklas Millard is a software development engineer in one of the fastest-growing banks, building mission-critical financial services infrastructure.

Previously, he was a Big4 Senior Tech Consultant developing software for commercial clients and government institutions.

New YouTube Channel (@Nicklas Millard)

Connect on LinkedIn

Technology
Programming
Software Development
Software Engineering
Web Development
Recommended from ReadMedium