DEV Community

Cover image for Why Developers Should Stop Putting Business Logic Inside Controllers
Build for Search
Build for Search

Posted on

Why Developers Should Stop Putting Business Logic Inside Controllers

A controller often starts small. It receives a request, checks a few values, calls a database method, and returns a response.

Then the application grows.

A new condition appears. Another permission check gets added. Email notifications become part of the request. Soon, a single controller method handles validation, business rules, database operations, and error handling.

The code still works, but changing one feature becomes harder than it should be.

This is where separating business logic from controllers makes a real difference.

The Problem With Fat Controllers

Consider a simple order creation endpoint in a PHP or Node.js application.

The controller receives the cart, checks the user's account, calculates the total, applies a discount, verifies stock, creates the order, reduces inventory, and sends a confirmation email.

At first, putting everything in one method might seem convenient. There are fewer files to manage, and the complete process is visible in one place.

The difficulty appears when the same rules are needed elsewhere.

A mobile application might create orders through a different endpoint. An administrator might create an order manually. A scheduled task might process pending purchases.

If each entry point contains its own version of the order logic, the application can develop inconsistent behavior. One path may validate stock correctly while another forgets a check.

The problem is not the number of lines in a controller. It is the number of responsibilities the controller owns.

Give Each Layer a Clear Responsibility

A controller should primarily coordinate the HTTP request.

It can validate request structure, identify the authenticated user, call the appropriate application service, and translate the result into an HTTP response.

A service or use-case layer can handle the actual business operation. It decides whether the order can be placed, applies relevant business rules, and coordinates the necessary operations.

Repositories or data-access components can handle persistence when that abstraction is useful for the application.

These layers do not need to become an elaborate framework. Their purpose is to make responsibilities clear.

For example, an order controller might call a method such as placeOrder(userId, cartItems). The service then handles the order workflow, while the controller focuses on returning an appropriate response.

The method name describes the business action instead of exposing every implementation detail.

For background on structuring application responsibilities, see Martin Fowler's Service Layer pattern.

Business Rules Should Be Testable Without HTTP

Imagine a rule that prevents an order when a product has insufficient stock.

If the rule lives inside a controller, testing it may require creating an HTTP request, setting up authentication, and checking the response.

If the rule is handled by a dedicated service, you can test the decision more directly by providing products, quantities, and available stock.

This makes it easier to test scenarios such as insufficient inventory, invalid discounts, unavailable products, and successful orders.

You can still write integration tests for the complete HTTP flow. The advantage is that business rules no longer depend entirely on the web layer to be tested.

The Martin Fowler article on Domain Model explains an approach for organizing business logic around the concepts and rules of a domain.

Avoid Turning Every Service Into a Giant Class

Separating business logic does not mean moving an entire controller into a single service file.

A service that handles orders, payments, emails, inventory, user permissions, and reporting can become just as difficult to maintain.

Organize code around meaningful responsibilities. An order service should coordinate order-related rules, while payment processing can have its own component when the complexity justifies it.

Also, avoid adding interfaces and abstractions without a practical reason. A small application does not need a complicated architecture for every database operation.

The right structure is the simplest one that keeps important rules understandable and reusable.

Watch Out for Transaction Boundaries

Order processing also illustrates why clean architecture alone is not enough.

Suppose an application creates an order successfully but fails while reducing inventory. The database may end up with inconsistent records if related operations are not handled correctly.

When several database changes must succeed together, a transaction can help preserve consistency. External actions, such as sending an email, need separate consideration because they cannot normally be rolled back by a database transaction.

The architecture should make these boundaries explicit rather than hiding every operation inside a controller method.

A Practical Refactoring Strategy

You do not need to rewrite the entire application.

Start with one controller method that contains several business rules. Identify the decisions that belong to the business domain, move them into a focused service, and leave request handling in the controller.

Then add tests around the extracted logic and compare the behavior with the original implementation.

Repeat this process only where it improves clarity, testability, or reuse.

Final Thoughts

Controllers are useful entry points, but they should not become the place where every business decision lives.

Keeping business rules in focused components makes applications easier to test, change, and maintain. More importantly, it allows different parts of an application to follow the same rules without duplicating them.

Good architecture is not about creating more folders. It is about making it clear where a decision belongs and ensuring that the decision remains consistent as the application grows.

For more insights on maintainable web applications and search-friendly development practices, explore Web Development SEO.

AI disclosure: This article was prepared with assistance from an AI writing tool and should be reviewed for technical accuracy before publication.

Top comments (0)