A practical, layered .NET architecture suitable for a wide range of projects

ModularNet is a layered, modular architecture designed for building robust, maintainable, and testable .NET applications. It emphasizes a strong separation of concerns, leveraging Dependency Injection and interface-based communication between layers.
The core idea is to decouple the application’s core business logic from both the user interface/application entry points and the infrastructure concerns (like databases, external APIs, etc.). This is achieved through distinct layers with well-defined responsibilities and controlled dependencies, primarily flowing inwards towards a central, simple Domain layer.
This architectural pattern is highly versatile and can be adapted for various types of .NET solutions, including Web APIs, Console Applications, Worker Services, Lambdas, and more, providing a solid foundation for growth and change.
It is an almost-ready-to-use template to manage the registration and login of a user with Google firebase, email sending and secrets management with azure, authentication and authorization in APIs, logs, etc.
Architecture Layers
The architecture is composed of the following layers:
1. Domain Layer
- Content: Contains simple Plain Old CLR Objects (POCOs) representing the data models (
Entities). These classes are purely data containers with properties and no constructors or business logic within them. It also holds sharedEnums(used instead of static classes for constant values), definitions forCustom Exceptions, andRequestsorResponses(likely simple data transfer objects for input/output). - Purpose: Acts as a shared kernel defining the shape of data used across the application. Because it contains only passive data structures and definitions without logic or complex dependencies, it can be safely referenced by any other layer (
Business Logic,Infrastructure,Application) that needs to understand these data shapes.
2. Application Layer (Runtime Applications)
- Content: Entry points like API Controllers, Console application main logic, UI-related components (e.g., Blazor components, MVC Controllers), etc.
- Interaction: Communicates strictly with the
Business Logiclayer via interfaces. It receives instances of business logic services through Dependency Injection. It’s responsible for initiating actions in the business layer based on user input or system events and presenting the results. - Dependencies: Depends on
Business Logic(interfaces) andDomain(for models/DTOs used in requests/responses).
3. Business Logic Layer
- Content: Contains the core application logic, business rules, use cases, and orchestration. It consists of
Interfacesdefining its services and the services it requires from the infrastructure, andImplementationscontaining the actual logic. - Interaction: Implements the interfaces consumed by the Application Layer. It interacts strictly with the
Infrastructurelayer via interfaces. It receives instances of infrastructure services (repositories, external service clients, etc.) through Dependency Injection. - Dependencies: Depends on
Domain(uses the POCOs, Enums, Exceptions),Shared(for helpers), andInfrastructure(interfaces).
4. Infrastructure Layer
- Content: Defines and contains both the
InterfacesandImplementationsspecifically for interacting with external systems (e.g., database access contracts, caching provider contracts, external API client contracts). - Purpose: Provides the concrete mechanisms for communicating with external resources (
DB,Cache, etc.). Its interfaces define how these external systems can be accessed, and these interfaces are consumed by theBusiness Logiclayer when it needs access to persistence or other external capabilities. TheImplementationsact as bridges or consumers, containing minimal logic beyond the technical details of the interaction (e.g., using an ORM, making HTTP calls, interacting with cache clients). - Dependencies: Depends on
Domain(to understand data shapes it handles),Shared(for helpers), and interacts withExternal Systems. Its interfaces are consumed byBusiness Logic.
5. Shared Layer
- Content: Simple, stateless
Helpermethods or common utilities. - Purpose: Provides reusable functionality specifically needed by both the
Business LogicandInfrastructurelayers, avoiding code duplication. - Dependencies: Should have minimal dependencies, likely only on the base .NET libraries.
6. External Systems
- Content: Databases (
DB), Caching systems (Cache), third-party services, message queues, etc. - Purpose: Resources external to the application itself, accessed solely via the
Infrastructurelayer.
Dependencies and Infrastructure Details
This implementation of the ModularNet architecture utilizes or is configured for the following specific technologies and services, primarily managed within the Infrastructure layer or related configuration:
- Authentication: Firebase Authentication
- Authorization: Microsoft.Identity platform (handling token validation and enabling features like
[Authorize]attributes with scope/role checks in API controllers) - Emailing: An Azure-based email service (e.g., Azure Communication Services Email)
- Secrets Management: Azure Key Vault
- Logging: Serilog
- Caching: Ready to work with Redis (specifically targeting Azure Cache for Redis)
- Database: Ready to work with Microsoft MySQL
Advantages of ModularNet Architecture
Adopting this architecture brings several key benefits:
- High Testability: Business logic can be easily unit-tested in isolation by mocking the infrastructure dependencies (repositories, external services) using their interfaces. Application layer components can similarly be tested by mocking the business logic interfaces.
- Modularity: Layers are distinct and can often be implemented as separate projects within a .NET solution. This promotes independent development, deployment (in some scenarios), and easier management of code.
- Maintainability & Scalability: Changes within one layer (e.g., switching database technology in Infrastructure) have minimal impact on other layers, provided the interfaces remain consistent. Adding new features often involves localized changes within specific layers, making the codebase easier to evolve.
- Clean Code & Best Practices: Encourages adherence to SOLID principles (especially Dependency Injection and Single Responsibility) and promotes a clear separation of concerns, leading to a cleaner, more understandable codebase.
- Flexibility: The reliance on interfaces allows for different implementations of infrastructure concerns to be swapped out relatively easily (e.g., moving from SQL Server to PostgreSQL, or changing a caching provider).
- Clear Structure: Provides a well-defined structure that is easier for development teams to understand, navigate, and contribute to effectively.
Differences from Onion Architecture
ModularNet architecture is inspired by and very similar to Onion Architecture (and Clean Architecture), but has some differences:
- In classic Onion, Business Logic defines the abstractions (
IXxxRepository), and Infrastructure implements them. In ModularNet, the Infrastructure layer definesIXxxRepository, and Business Logic consumes it. - ModularNet does not follow DDD, and the domain contains just POCOs with no constructor or logic. This is the Anemic Domain Model, which I consider cleaner and with less “hidden magic.”
- ModularNet includes a Shared layer, which Onion Architecture doesn’t define.
- All DTOs or application entities are always placed in the Domain layer. In Onion, these application-specific data transfer objects would reside in the Business Logic layer.
For more details and in-depth explanations about the solution, please visit the Wiki section.
I welcome contributions and feedback! If you have ideas, suggestions, or improvements, feel free to open an issue, submit a pull request, or write in the Discussions section. Your input helps make this project better for everyone.


Leave a comment