How I Simplified My Backend Architecture for Business Applications
After more than a decade of building business applications, I noticed something interesting. Very few projects became difficult because the business rules were complicated. Most became difficult because of everything surrounding the business rules. Every new module seemed to require another controller, another service, another repository, another set of CRUD methods, another search endpoint,…
After spending over a decade crafting business applications, one striking observation emerged: the majority of projects encountered difficulty not because of the underlying business rules, but due to the surrounding infrastructure. Each new module tended to bring additional controllers, services, repositories, CRUD methods, search endpoints, authorization checks, and audit implementations.
This proliferation of infrastructure code across modules made me question whether it was possible to design infrastructure once, allowing developers to focus on writing business logic instead.
The shift in approach began with a deliberate separation of business behavior from infrastructure. Infrastructure encompasses CRUD operations, searching, pagination, auditing, multi-tenancy, authorization, notifications, and more. While essential, these components are often unique to individual modules rather than shared across the entire application. The focus shifted to ensuring business logic remained the core of the application, distinct from the infrastructure.
To simplify the architecture, unnecessary layers were removed, resulting in a streamlined structure: HTTP Request → Controller → Business Service → BaseService → IDataService → Database. Each layer assumed a single, clear responsibility. Controllers received requests, business services implemented business rules, the base service provided reusable application functionality, and the data service managed persistence.
This clear separation proved more maintainable than having each service repeatedly implement identical CRUD operations.
Another key change involved grouping services around business capabilities instead of individual models. Instead of creating services solely based on the existence of a model, related functionality was grouped into services like Product Service, Order Service, Inventory Service, and Customer Service. Supporting models only warranted their own service if they introduced unique business behavior. This approach minimized unwarranted abstractions and streamlined navigation within the solution.
When it came to searching capabilities, the solution was to avoid creating new endpoints for every filter requirement. Instead, clients were empowered to describe their search criteria, enabling the backend to validate these criteria and translate them into efficient database queries. This approach maintained a stable API surface while accommodating the evolving search needs of the application.
Making infrastructure automatic was another guiding principle. Instead of developers manually implementing concerns like tenant filtering, audit information, pagination, index creation, and timestamps, these tasks were handled automatically by the framework. By consistently managing these aspects, the risk of subtle bugs caused by inconsistent implementations was reduced, and the need for repetitive code was minimized.
Regarding the choice between GraphQL and gRPC, both technologies offer valuable features. However, the appropriate selection depends on the specific requirements of the business application. GraphQL shines when clients need fine-grained control over data retrieval, while gRPC excels at high-performance communication between services.
For most business applications, a well-designed REST architecture with reusable infrastructure often provides the necessary flexibility, including predictable APIs, advanced search capabilities, strong authorization, auditing, and maintainable business logic, while remaining straightforward to develop, test, and maintain.
The overarching goal was not to avoid newer technologies but to opt for the simplest architecture capable of effectively solving the problem at hand. The lesson learned was that complexity typically accumulates gradually, often through small, seemingly insignificant additions such as duplicated CRUD methods, additional search endpoints, or new authorization implementations.
Collectively, these elements result in high maintenance costs. The key to good architecture lies in eliminating unnecessary repetition, enabling developers to concentrate on solving business problems rather than grappling with technical debt.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — it may contain errors, so check the original before relying on it.