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.
The architecture is composed of the following layers:
- 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
, andRequests
orResponses
(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.
- 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 Logic
layer 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).
- Content: Contains the core application logic, business rules, use cases, and orchestration. It consists of
Interfaces
defining its services and the services it requires from the infrastructure, andImplementations
containing the actual logic. - Interaction: Implements the interfaces consumed by the Application Layer. It interacts strictly with the
Infrastructure
layer 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).
- Content: Defines and contains both the
Interfaces
andImplementations
specifically 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 Logic
layer when it needs access to persistence or other external capabilities. TheImplementations
act 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
.
- Content: Simple, stateless
Helper
methods or common utilities. - Purpose: Provides reusable functionality specifically needed by both the
Business Logic
andInfrastructure
layers, avoiding code duplication. - Dependencies: Should have minimal dependencies, likely only on the base .NET libraries.
- Content: Databases (
DB
), Caching systems (Cache
), third-party services, message queues, etc. - Purpose: Resources external to the application itself, accessed solely via the
Infrastructure
layer.
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
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.
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.