How I like to Structure My .NET Backend Project - 10 More Days Left of 100 Days Challange
After 40 days of MAUI, I have started to work on a project that I always intended to build with .NET. While working on that, I came up with the project organization strategy which I think will be well suited for API-based backends. This structural documentation is made with keeping 2 things in mind, simplicity and scalability. As much as we need rock solid structure of source code for enterprise application, in this era, speed is one of the most important thing we cannot deny.
In this article, I will walk through the design philosophy and organizational principles behind the source code of my backend project. This will help developers understand where to place their code and how to navigate through different parts of the system efficiently.
Purpose of the Structure
The primary goal of this source code structure is to:
Let’s explore how the codebase is logically divided.
?? Project Hierarchies
The source code is divided into hierarchies, which are top-level directories representing the logical layers of the system. Each hierarchy can contain folders based on taxonomies (explained later), and can support multiple application types (like Admin, User, etc.).
Note: All hierarchy folder names must be singular.
Here’s a breakdown of each hierarchy:
1. Module
Represents individual chunks of business logic. Think of them as feature-focused units. Examples: Auth, Advertise, File, Campaign
2. Provider
Contains the implementation of external service providers like databases, email services, schedulers, and caching systems. The business logic for using these providers will reside in the relevant module or service.
3. Settings
Holds application-level configuration data. Examples: Database connection strings, email credentials, private keys, etc.
4. Migration
Contains all the database migration scripts necessary for setting up and evolving the database schema.
5. HTTP
Home for utilities related to HTTP requests and responses, such as route prefixing and response standardization.
6. Common
Stores shared definitions like types, enums, and other constants used across the application.
7. Modifier
Includes elements like middlewares, filters, and attributes—components that modify the behavior of classes and requests.
8. Utils
A container for utility functions and ad-hoc tools that support the rest of the codebase.
?? Taxonomies
Taxonomies define the technological or functional role of a file. Each file in the project should represent only one taxonomy and should be named accordingly.
Here are the supported taxonomies:
?? Application Types
The backend is designed to support multiple application types. Initially, the following two applications are defined:
Different application types may use the same modules or services but from different perspectives, enabling flexible scalability.
Naming Convention for Maintainance
A consistent naming convention is essential to support a well-structured codebase, as it reinforces the logical hierarchy and taxonomy outlined in the project’s architecture. It helps developers quickly understand the purpose and context of files, functions, and components, making the code easier to read, maintain, and scale across different modules and application types.
This naming convention system provides a consistent and structured approach to naming files, functions, and components across the project, ensuring clarity and maintainability. All files are expected to use PascalCase, with the exception of enums, which should follow UPPER_CASE_WITH_UNDERSCORES. File names are composed using the pattern {Module/Provider}{Taxonomy}.cs (e.g., AuthController.cs, FileRepository.cs) to reflect both the context and the function of the file. For context-specific providers, the format {ContextName}{Provider}.cs is followed (e.g., EmailProvider.cs).
Function names across services, controllers, and repositories are also standardized using PascalCase. Service functions begin with a verb and, if asynchronous, include the suffix Async (e.g., FetchAvailableProductsAsync). Controller functions mirror HTTP actions (e.g., GetUser, CreateCampaign), while repository methods follow a {Action}{Entity}By{Filter} format with optional With{ForeignField} or WithAll suffixes for joins. Projections use Dto for base models and a structured naming format for joined projections (e.g., UserProjection_PasswordDto). Enums are defined using PascalCase for the type and UPPER_CASE_WITH_UNDERSCORES for the values. This consistent naming strategy enhances readability, reduces confusion, and makes the codebase easier to scale and navigate.
?? Final Thoughts
This source code structure is designed with flexibility and clarity in mind. By dividing code into logical hierarchies and technological taxonomies, it becomes much easier to manage, scale, and onboard new developers.
Whether we're developing a new module, introducing a new provider, or simply trying to find where a particular type of logic resides, this structure provides clear guidance and consistency across the codebase.
Understanding and adhering to this structure is essential for maintaining high-quality backend code and achieving long-term project success.
Full-Stack Laravel Developer | API & Web Service | Server Deployment & Maintenance
4 天前you better create a website and start uploading these, then add the google Adsense ??
Finalist @ICPC Asia West Continent | Software Engineer
4 天前Good stuff. Keep sharing.
Software Engineer
4 天前Useful tips Nice insight and a sweet getaway from the usual updates! Great stuff overall! Keep it up