← Back to Insights
/ Engineering Insights

How I structure multi-layered backends to promote separation of concerns, testability, and maintainability

After years of refactoring .NET backends, one architecture pattern consistently holds up under pressure: true layered separation. Here's exactly how I structure it and why every layer boundary is a deliberate decision.

How I structure multi-layered backends to promote separation of concerns, testability, and maintainability
Emile Ndagijimana
Emile Ndagijimana
June 19, 2026
/
Architecture & Design

The problem with flat architectures

Every project starts simple. A controller calls a service. A service calls the database. It works until it doesn't. Six months in, you have controllers with business logic, services reaching directly into the database, and a test suite that requires a live SQL Server connection to run a single assertion.

I've refactored this pattern more times than I can count. The fix is always the same: real layer separation, enforced through project structure not just naming conventions.

The four layers I use in every .NET project

My standard structure maps directly to the Onion Architecture model, adapted for the realities of enterprise .NET development.

1. Domain — the pure core

No dependencies. None. This layer contains your entities, value objects, domain events, and repository interfaces. It defines what the system models contracts without implementations. If this project references an ORM, a logger, or an HTTP client, something has gone wrong.

2. Application — orchestration without infrastructure

Use cases, commands, queries (CQRS-style with MediatR), validators, and DTOs live here. The Application layer depends only on Domain. It orchestrates work — calling domain methods, dispatching events but it never talks to a database directly. It trusts the interfaces defined in Domain.

3. Infrastructure — the adapter layer

EF Core repositories, Azure Blob clients, SMTP services, external API wrappers — all here. Infrastructure implements the interfaces declared in Domain. This is the only layer that should ever import a NuGet package for I/O. Swapping a SQL Server repository for Cosmos DB should require changes in exactly one project.

4. Presentation — HTTP as a delivery mechanism

Controllers, middleware, request validators, response mappers. The Presentation layer translates HTTP into Application commands. It knows nothing about your database or domain rules — it maps and dispatches.

Why this pays off in the real world

At ADENES, we inherited a system where insurance claim processing logic was embedded directly in API controllers. Extracting that into a proper Domain + Application structure took three months — but slashed the time to test a new claim rule from hours to minutes. Unit tests run without a database. New developers onboard in days, not weeks.

"The goal isn't clean architecture for its own sake. The goal is a codebase where the cost of change stays low as the system grows."

The non-obvious rule: dependency direction is everything

Dependencies always point inward. Domain knows nothing. Application knows Domain. Infrastructure knows Domain and Application. Presentation knows Application. Enforce this with .NET project references — not naming conventions. If a reference creates a circular dependency or points the wrong direction, the build fails. That's your architecture enforcing itself.

One practical tip for existing codebases

Don't try to refactor everything at once. Start with one domain concept. Extract the interface. Move the implementation. Write one unit test that runs without the database. That first test is proof of concept — and it's usually enough to convince the rest of the team.

/ My SaaS

I write about building Contractly Pro

Real decisions, real tradeoffs, real architecture — from solo founder to production SaaS. Read the Founder Journal or try the app.

Explore our collection of 200+ Premium Webflow Templates

Need to customize this template? Hire our Webflow team!