“Clean Architecture” has become a badge of honor in the .NET world. If you’re building a new API, there’s a good chance someone on your team insists on splitting the project into MyApp.Core
, MyApp.Application
, MyApp.Infrastructure
, MyApp.Web
, MyApp.Domain
, and half a dozen interfaces.
But here’s the hard truth: in 80% of real-world .NET projects, this level of separation is unnecessary, counterproductive, and slows you down.
Let’s unpack the trap — and how to avoid it.
🎯 What Clean Architecture Promises
Clean Architecture, as popularized by Uncle Bob (Robert C. Martin), is about:
-
Separation of concerns
-
Independence from frameworks
-
Testability
-
Decoupled layers
In theory, your business rules don’t depend on the web framework, the database, or the outside world. That’s great — if you’re writing a library, a long-lived enterprise platform, or something that will evolve dramatically over years.
But what if… you’re just building an internal API that reads from SQL and writes to Redis?
🪤 The Trap: When Clean Becomes Clutter
Here’s what we see too often in .NET shops:
-
Every service is behind an interface, even if there’s only one implementation.
-
Business logic is split across
Domain
,Application
, andInfrastructure
, even for trivial tasks. -
A simple HTTP endpoint touches 5 layers before doing a single useful thing.
-
The solution has 10 projects… to support 5 endpoints.
You didn’t get architecture — you got indirection.
🧠 Real Talk: What You Actually Need
Before reaching for Clean Architecture, ask:
-
How complex is the domain?
-
How likely are we to swap implementations (e.g., change databases)?
-
How long will this project live?
-
Is our team really benefiting from the overhead?
Here’s a practical alternative:
-
Start with a monolithic project using folders (not projects) for structure:
Controllers/
,Services/
,Data/
,Models/
-
Add layers when they’re needed, not before.
-
Use interfaces only where it creates actual flexibility (e.g., mocking time or storage).
-
Write tests around business logic, not HTTP.
🧪 Minimal API, Maximum Clarity
.NET 8’s Minimal APIs are perfect for teams that want to avoid unnecessary ceremony.
app.MapPost(“/orders”, async (OrderDto dto, OrderService svc) =>
{
var result = await svc.CreateOrder(dto);
return Results.Ok(result);
});
Your service can live in a single class file — tested, clear, and performant. No need for a full-blown IOrderRepository
, IUnitOfWork
, or CQRS pipeline… unless you really need them.
✂️ Case Study: Refactoring Down
At [Your Company], we took a microservice built with Clean Architecture and shrank it from:
-
7 projects → 1
-
125 files → 42
-
Avg response time → 30% faster
We removed unnecessary interfaces, moved application logic into cohesive services, and kept only the abstractions that mattered (e.g., IDateTimeProvider
, IExternalApiClient
). It became easier to onboard new devs, easier to debug, and faster to deploy.
📏 Use Clean Architecture When…
-
You’re building a domain that will grow significantly (e.g., ERP, banking, insurance)
-
You have multiple implementations or shared business logic across systems
-
You plan to publish core business rules as a reusable library
🧘 Prefer Simplicity When…
-
Your domain is straightforward (CRUD APIs, internal tooling)
-
Your team is small or needs to move fast
-
You’re not replacing your database any time soon
-
You can easily test your logic in isolation without abstractions
🚀 TL;DR
-
Clean Architecture isn’t bad — it’s just not always needed.
-
Overengineering hurts more than it helps for small to mid-sized .NET apps.
-
Start simple. Refactor as complexity justifies it.
-
You can still write testable, maintainable code with one project and zero interfaces.
🧰 Bonus: Starter Template Without the Bloat?
Want a Minimal API starter with structure, OpenTelemetry, MediatR only if you need it, and no unnecessary layering? Let me know — I’ll drop a link or generate one for you.
Let’s keep .NET development clean — but not clean for the sake of it.