Skip to main content

Fat Controller

A Fat Controller crams business logic, validation, and data access into web handlers, making them untestable, framework-coupled, and impossible to reuse. Keep controllers thin and move logic into a service layer and rich domain model, refactoring one action at a time.

A Fat Controller is an MVC controller (or web/API request handler) that has taken on responsibilities far beyond coordinating a request and response. Instead of delegating to a domain or service layer, it contains validation, business rules, data-access queries, and even formatting logic. The controller swells into a catch-all, mirroring the God Object problem but specifically at the web boundary. The corollary advice — "skinny controllers, fat models" — names the cure.

Why It Happens

The controller is the first place a request lands, so it is the most convenient place to add logic. Framework tutorials often show database queries and rules directly in controller actions for brevity, and that style sticks. Under deadline pressure, adding to an existing action is faster than designing a service. As features grow, each action accumulates more steps until the controller carries the application's behavior. Absence of a clear service or domain layer leaves nowhere else for the logic to go, so it stays in the controller by default.

Why It Hurts

Business logic embedded in controllers can only be exercised by simulating HTTP requests, making tests slow, brittle, and dependent on the web framework. The same rules cannot be reused by other entry points — background jobs, CLI tools, other endpoints — so they get duplicated and drift apart. Controllers become tightly coupled to the framework, the database, and the domain all at once, so any change ripples widely. Action methods grow to hundreds of lines, hard to read and reason about. Cross-cutting concerns get copy-pasted across actions. The web layer, which should be thin and replaceable, becomes the heaviest and most fragile part of the system.

Warning Signs

  • Controller actions contain validation, calculations, and database queries.
  • Action methods are very long and handle many responsibilities.
  • Business rules are duplicated across controllers and other entry points.
  • You cannot test core logic without spinning up the HTTP stack.
  • Changing a business rule means editing controllers.

Better Alternatives

Keep thin controllers that only parse input, invoke a use case, and shape the response. Introduce a service layer (or application/use-case layer) that holds orchestration and is callable from any entry point. Put core rules in a rich domain model per Domain-Driven Design, so controllers merely call into well-tested business logic. The controller's job is translation between HTTP and the application, nothing more.

How to Refactor Out of It

Take one fat action at a time. Extract its business logic into a service or use-case object with no HTTP dependencies, leaving the controller to gather inputs, call the service, and return a response. Move data-access concerns into repositories the service uses. Add unit tests against the extracted service, now testable without the web framework. Where the same logic appeared in multiple controllers or jobs, route them all through the shared service and delete duplicates. Repeat until controllers are short and uniform. The end state is a thin, replaceable web layer over a tested, reusable core.