The term "Clean Architecture" gets used loosely enough that it has lost some precision. I want to be specific about what I mean by it, why it matters for .NET systems specifically, and what it actually looks like in a real project directory — not just in a diagram.
The Problem It Solves
Most .NET applications I inherit have one critical structural flaw: business logic depends on infrastructure. Controllers directly query the database via Entity Framework. Domain objects inherit from DbContext. Business rules are mixed into the same file as HTTP request handling.
This creates a system that is difficult to test, difficult to change, and tightly coupled to its current technology choices. Switching the database, changing the ORM, or moving from REST to gRPC becomes a multi-week project rather than a configuration change.
Clean Architecture inverts these dependencies.
The Four Layers
Domain — The innermost layer. Contains business entities, value objects, and domain logic. Has zero dependencies on any framework, database, or external library. Pure C# classes. This is where the real business rules live.
Domain/
Entities/
Order.cs
Payment.cs
ValueObjects/
Money.cs
CustomerId.cs
Exceptions/
InsufficientInventoryException.cs
Application — Orchestrates the domain. Contains use cases (I call them commands and queries, following CQRS convention), interfaces for external services, and application-specific business rules. Depends on the Domain layer only.
Application/
Orders/
Commands/
PlaceOrderCommand.cs
PlaceOrderHandler.cs
Queries/
GetOrderByIdQuery.cs
GetOrderByIdHandler.cs
Interfaces/
IOrderRepository.cs
IPaymentGateway.cs
IEmailService.cs
The interfaces here — IOrderRepository, IPaymentGateway — are declared in the Application layer but implemented in the Infrastructure layer. This is the dependency inversion that makes the whole structure work.
Infrastructure — Implements the interfaces declared in Application. Contains Entity Framework configurations, database migrations, third-party API clients, email senders, and anything else that touches the outside world.
Infrastructure/
Persistence/
AppDbContext.cs
Repositories/
OrderRepository.cs
Configurations/
OrderConfiguration.cs
ExternalServices/
StripePaymentGateway.cs
SendGridEmailService.cs
Presentation — The entry point. Controllers, Razor Pages, gRPC endpoints, background workers. Depends on Application via MediatR (or direct handler invocation). Knows nothing about the database.
Presentation/
Controllers/
OrdersController.cs
Pages/
Checkout/
Index.cshtml
The Dependency Rule
Dependencies flow inward only. Domain knows nothing. Application knows Domain. Infrastructure knows Application and Domain. Presentation knows Application.
This is enforced at the project level in .NET — by creating separate .csproj files for each layer and using explicit project references. If someone tries to reference Infrastructure from Domain, it is a compile error, not a code review finding.
MediatR and CQRS in Practice
I use MediatR to implement the command/query pattern in the Application layer. Each use case is a handler that receives a command or query object and returns a result. The controller dispatches to MediatR; MediatR routes to the appropriate handler.
This keeps controllers thin (they validate input, dispatch, and return results) and keeps business logic in handlers where it is testable in isolation — no mocking of HttpContext or controller machinery required.
What This Buys You
Testability. Application handlers can be unit-tested with mock repositories. No spinning up a database, no HTTP context, no integration test overhead for pure business logic.
Framework independence. Replacing Entity Framework with Dapper requires changing only the Infrastructure project. The Domain and Application layers are untouched. I have done this migration on live systems without changing a single business rule.
Parallel development. Frontend developers can work against mock implementations of the interfaces while backend developers build the real infrastructure. The contracts (interfaces) are defined early; the implementations come later.
Onboarding clarity. New developers know immediately where to put things. Business rules go in Domain. Orchestration goes in Application. Plumbing goes in Infrastructure. There is no ambiguity about where a new feature belongs.
The Honest Trade-Off
Clean Architecture introduces indirection. For a simple CRUD application with two developers and a six-month lifespan, it is over-engineering. The ceremony of mapping between layers, defining interfaces for everything, and wiring up MediatR adds real overhead that is not always justified.
I apply it on systems that are expected to live for years, be maintained by multiple developers, or need the ability to change their infrastructure layer without rebuilding the business logic. For the B2B portals and enterprise systems I typically work on, it pays for itself within the first major feature addition.
If you are evaluating the architecture of an existing .NET system or designing a new one, get in touch. I am happy to walk through what structure makes sense for your specific context.