When NOT to Use Clean Architecture — Trade-offs, Complexity, and Alternatives
Honest assessment of when Clean Architecture adds overhead without value: small projects, tight deadlines, CRUD-heavy APIs, and the alternative patterns (Vertical Slice, Minimal API, Modular Monolith) that fit those contexts better.
The Honest Assessment
Clean Architecture is excellent for complex, long-lived applications with evolving business logic. It is overkill for many real-world scenarios. Choosing the wrong architecture costs more time than it saves.
"I've seen more projects fail from premature architecture than from not having enough of it. A 10-endpoint CRUD API that has 8 layers and 40 files is not a better codebase — it is a harder one to maintain."
The Costs of Clean Architecture
Costs:
✗ Boilerplate: every use case needs a command/query record, handler, validator,
repository interface update, and potentially a response DTO
✗ Navigation overhead: following a feature from controller → handler → domain
→ repository → DB is 4 hops in 4 different projects
✗ Onboarding time: new developers need to understand all 4 layers, the Result
pattern, domain events, and the CQRS split before contributing
✗ Indirection: handlers are the right abstraction until they are not — sometimes
a simple function is faster and clearer
Benefits (when they apply):
✓ Testable business logic without infrastructure
✓ Swappable infrastructure (DB, email, cache) without touching business logic
✓ Navigable architecture — clear "where does this go" decisions
✓ Long-term maintainability as complexity grows
✓ Multiple team members can work on different layers without conflictsWhen Clean Architecture Is Wrong
1. Small Projects (Under 15 Endpoints)
// For a small API with 10 endpoints, this is enough:
app.MapPost("/patients", async (CreatePatientDto dto, AppDbContext db) =>
{
var patient = new Patient { Name = dto.Name, MRN = dto.MRN };
db.Patients.Add(patient);
await db.SaveChangesAsync();
return Results.Created($"/patients/{patient.Id}", patient);
});
// Adding layers, handlers, interfaces, and repositories for 10 endpoints
// is not architecture — it is overhead2. Pure CRUD APIs
If every "feature" is:
- Get record by ID
- List records with filtering
- Create record (validate + insert)
- Update record (validate + upsert)
- Delete record
Then Clean Architecture adds layers that have no business logic to contain.
Use Minimal APIs + EF Core + FluentValidation directly.3. Tight Deadlines with Uncertain Requirements
When the requirement is "build a prototype by Friday and we'll see if users like it":
Clean Architecture adds 2-3x the setup time
The prototype may be thrown away — all the infrastructure work is wasted
Start simple. If the prototype survives, migrate toward Clean Architecture
incrementally as the application grows.4. Single-Developer Projects
The collaboration benefits of Clean Architecture (clear layer ownership, parallel
development, explicit interfaces between teams) do not apply when one developer
owns everything. The overhead is all cost, no benefit.Alternatives and When They Fit
Vertical Slice Architecture
Organize by feature, not by layer:
Features/
Patients/
CreatePatient/
CreatePatientEndpoint.cs ← HTTP handling
CreatePatientHandler.cs ← business logic
CreatePatientValidator.cs ← validation
CreatePatientDb.cs ← DB query
Prescriptions/
AddPrescription/
...
Best for:
✓ Teams where each feature is owned end-to-end by one developer
✓ Applications where features are largely independent
✓ When you want to colocate all related code in one place
Drawback:
✗ Cross-feature shared code can become unclear — "where does the shared patient
validation go?"
✗ Architecture rules are harder to enforce with NetArchTestMinimal API Without Layers
// Best for: microservices with 3-5 endpoints, serverless functions
app.MapPost("/prescriptions", async (
AddPrescriptionDto dto,
AppDbContext db,
IValidator<AddPrescriptionDto> validator) =>
{
var validation = await validator.ValidateAsync(dto);
if (!validation.IsValid)
return Results.ValidationProblem(validation.ToDictionary());
var patient = await db.Patients.FindAsync(dto.PatientId);
if (patient is null)
return Results.NotFound();
// ... add prescription
return Results.Ok(new { prescriptionId });
});Modular Monolith
A middle ground: separate modules (Patients, Pharmacy, Billing), each with
its own Clean Architecture internally, but all in one deployable unit.
Best for:
✓ Large applications that are not ready for microservices
✓ When you want module isolation without deployment complexity
✓ As a stepping stone toward microservices
Each module has its own DbContext, its own domain, its own application layer.
Modules communicate through events or a shared kernel, not direct calls.The Decision Framework
Questions to ask before choosing Clean Architecture:
1. Will this application grow beyond 20 features?
YES → Clean Architecture
NO → Minimal API or Vertical Slice
2. Are there genuine domain rules that change independently of infrastructure?
YES → Clean Architecture (the domain layer earns its keep)
NO → Simpler approach
3. Will multiple developers work on this simultaneously?
YES → Clean Architecture (clear layer ownership prevents conflicts)
ONE DEVELOPER → Simpler approach
4. Do you need to swap infrastructure (DB, email provider)?
YES → Clean Architecture (interfaces make this safe)
NO → Probably not worth the overhead
5. Will this application live for more than 2 years?
YES → Clean Architecture (the upfront cost pays back over time)
NO → Simpler approachMigrating From Simple to Clean Architecture
If you start simple and your application grows, migration is incremental:
Phase 1: Extract business logic from controllers → put in service classes
Phase 2: Define interfaces for services → make infrastructure swappable
Phase 3: Separate Domain from Application → domain has no external dependencies
Phase 4: Add architecture tests → enforce the new structure
Phase 5: Add CQRS → separate command and query handlersYou do not need to apply all 5 phases at once. Stop when the architecture fits the complexity of the application.
PRO TIP — The Right Architecture Is the Simplest One That Fits
Clean Architecture is not the goal. Shipping working, maintainable software is the goal. Clean Architecture is one tool that helps achieve it in specific contexts. An application built with Minimal APIs and EF Core that ships and works is better than a theoretically perfect Clean Architecture application that took twice as long and does not meet the deadline.
Interview Answer
Q: Would you always use Clean Architecture on a new project?
No. I choose the architecture that fits the problem. For a 5-endpoint internal API, Clean Architecture adds overhead without value — I'd use Minimal APIs with EF Core and FluentValidation. For a long-lived application with complex business rules and multiple team members, Clean Architecture pays for its overhead within the first few months. The signal I look for is: "does this application have genuine domain logic that needs to be protected from infrastructure changes?" If yes, Clean Architecture. If the answer is "everything is basically CRUD," something simpler ships faster and is easier to maintain.
Key Takeaway
Clean Architecture is a trade: upfront complexity for long-term maintainability. The trade is worthwhile when the application is complex enough and long-lived enough that the investment pays back. It is not worthwhile for small projects, prototypes, or CRUD APIs. The best architecture for your project is the simplest one that keeps the code navigable, testable, and maintainable as it grows — and that might not be Clean Architecture.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.