Clean Architecture in .NET
Implement Clean Architecture in .NET — separating domain logic from infrastructure concerns for maintainable, testable enterprise applications.
Clean Architecture Layers
Clean Architecture (popularized by Robert C. Martin) organizes code into concentric layers where dependencies point inward. The innermost layer (Domain/Entities) has zero dependencies on outer layers. In .NET projects, this translates to: Domain Layer (entities, value objects, domain events, interfaces — NO framework references), Application Layer (use cases, CQRS handlers, DTOs, validation — depends only on Domain), Infrastructure Layer (database access via EF Core, external APIs, file system, email — implements Domain interfaces), and Presentation Layer (ASP.NET Core API controllers, Razor Pages, Blazor — calls Application layer). The key principle: business logic in Domain and Application layers NEVER depends on frameworks, databases, or UI. This means you can swap your database from SQL Server to PostgreSQL without changing a single line of business logic.
Project Structure
- MyApp.Domain: Entities, Value Objects, Domain Events, Enums, Exceptions, Interfaces (IRepository, IUnitOfWork). Zero external dependencies
- MyApp.Application: CQRS Commands/Queries, Handlers, DTOs, Validators (FluentValidation), Mapping profiles (AutoMapper), Application services. Depends on Domain only
- MyApp.Infrastructure: EF Core DbContext, Repository implementations, External API clients, Identity/Authentication, Email/SMS services. Implements Domain interfaces
- MyApp.WebAPI: Controllers, Middleware, Filters, API models, Swagger config, Program.cs DI registration. Depends on Application + Infrastructure
- MyApp.Tests: Unit tests (Domain + Application), Integration tests (Infrastructure), End-to-end tests (WebAPI). Mirror the main project structure
- Shared Kernel: Common utilities shared across bounded contexts — Result pattern, pagination, base entities. Published as internal NuGet package
CQRS with MediatR
- CQRS (Command Query Responsibility Segregation): Separate read models (optimized for queries) from write models (optimized for commands). Each can scale independently
- Commands: Operations that change state — CreateOrderCommand, UpdateUserCommand. Validated with FluentValidation before execution
- Queries: Operations that read data — GetOrderByIdQuery, GetUserListQuery. Can use optimized read models or raw SQL for performance
- MediatR: In-process mediator library — handlers are decoupled from controllers. Send(command) dispatches to the right handler automatically
- Pipeline Behaviors: MediatR pipelines for cross-cutting concerns — logging, validation, caching, performance monitoring. Applied to all requests automatically
- Event-Driven: Raise domain events (OrderCreatedEvent) and handle side effects (send confirmation email, update inventory) in separate handlers