You know the feeling. You open a file to fix a minor bug, and suddenly you’re staring down a 500-line Controller. It’s handling HTTP routing, validating input, querying the database, firing off an email, and formatting a JSON response. You change one button’s behavior, and suddenly, twelve brittle tests break.
We were promised clean architecture. We were promised separation of concerns. Instead, we got a procedural dumping ground disguised as an enterprise design pattern.
Controllers don’t separate concerns; they just hide the mess.
The Model-View-Controller pattern made sense in the 1970s for desktop GUIs. But somewhere along the way, frameworks like Ruby on Rails, .NET MVC, and Spring Boot decided that the Controller was the perfect place to orchestrate modern, distributed web applications. It was a catastrophic misstep.
Think about your test suite. To test a single controller method, you have to mock the database, mock the email service, mock the HTTP context, and pray you haven’t missed a dependency. Why? Because the controller forces you to entangle business logic with infrastructure concerns. It turns a simple feature into a fragile web of implicit dependencies.
When your tests break because a button changed, your architecture isn’t decoupled—it’s just disguised.
Tony Hoare famously called his invention of the null reference his ‘billion-dollar mistake.’ It’s time we recognize the Controller as the other one. It doesn’t just create boilerplate; it actively encourages scattered logic. By trying to be the middleman between everything, it becomes the bottleneck for everything.
Developers treat controllers as a necessary evil. But the real mistake isn’t how we write them—it’s our uncritical acceptance of the pattern itself. We assume that because frameworks ship with them, they must be right. They aren’t. They ossify codebases and turn maintainable software into a refactoring nightmare.
We accepted boilerplate as the price of maturity, but really, it’s just design cowardice.
If you write or maintain software, you need to stop treating controllers as the inevitable center of your universe. Move your business logic into isolated, independent modules or use-case interactors. Let the HTTP layer do what it’s supposed to do: transport data. Stop worshipping at the altar of a pattern that is actively working against you.
FAQ
Q: Isn't MVC an industry standard? Why move away from it now?
A: MVC was great for 1970s desktop GUIs. In today's distributed, stateless web applications, controllers just create a bottleneck that entangles HTTP concerns with business logic.
Q: If I stop using controllers, what do I replace them with?
A: Move your logic into isolated use-case interactors or domain-driven modules. Let the HTTP layer simply transport data, keeping your actual business rules pure and testable.
Q: Aren't controllers fine if we write them correctly?
A: No. If a pattern requires perfect, superhuman discipline just to avoid becoming a 500-line garbage dump, the pattern itself is fundamentally flawed.