← Open in the full interactive course (progress tracking, search & more)

Dependency Inversion, from lesson 081, scaled up from one constructor to an entire application's file structure.

Go back to lesson 081. OrderFulfillmentService depended on IInventoryRepository, never on SqlInventoryRepository — the high-level policy never mentioned the low-level, technology-specific detail. That was Dependency Inversion applied to one class's constructor. Now ask a bigger question: what if that same rule — dependencies point toward abstractions, never toward concrete, swappable detail — governed not just one class, but the layout of an entire application? Every folder, every project reference, every "can this file import that file" decision?

That's Clean Architecture: the same idea from lesson 081, applied at the scale of an entire codebase's structure, with one precise, non-negotiable rule about which direction dependencies are allowed to point.

In this lesson, you'll learn Clean Architecture's concentric-circles model, the Dependency Rule that makes the whole thing work, and how to build a small, concrete slice of it — a domain entity, an interface it defines, and an infrastructure implementation that the domain layer never sees.

What Is It?

The Simple Explanation

Clean Architecture is a way of organizing an application into layers, arranged as concentric circles, where the innermost layer holds your most important, most stable code — your actual business rules — and every layer further out holds progressively more replaceable, technology-specific detail. The rule that makes it work: code in an inner circle is never allowed to know that an outer circle exists.

The Technical Definition — the Four Rings

Domain / Entitiescore business objects & rules — no framework references at all
Application / Use-Casesorchestrates domain objects to fulfill specific operations
InfrastructureEF Core, external APIs, file systems, email/SMS providers
Presentation / UIASP.NET Core controllers, Razor pages, a CLI, a mobile client

Why Does It Exist?

PROBLEM → NEED → SOLUTION
PROBLEM
NEED
SOLUTION

Big Picture — the Dependency Rule

The one rule that makes this whole architecture work

Dependencies only ever point inward. An outer layer may depend on an inner layer. An inner layer must never depend on an outer one. The Domain layer doesn't know EF Core exists. It doesn't reference DbContext, HttpClient, or any framework type at all — not even as a compiled reference, let alone actual code calling into it.

This is stated precisely because it's the single fact everything else in this lesson depends on. It is not "try to keep the domain layer mostly clean" — it is an absolute, structural rule: if Order.cs in the Domain project has a using Microsoft.EntityFrameworkCore; anywhere in it, or a method signature that takes a DbContext, the rule has been broken, full stop, regardless of how small or convenient that reference seemed at the time.

ALLOWED vs FORBIDDEN REFERENCES
ALLOWED
  • Infrastructure → references → Domain/Application
  • Presentation → references → Application
  • Application → references → Domain
FORBIDDEN
  • Domain → references → EF Core, ASP.NET Core, anything
  • Application → references → Infrastructure directly
  • Domain → references → Application

How It Works — the Same Trick as Lesson 081, at Larger Scale

HOW THE DOMAIN LAYER GETS PERSISTENCE WITHOUT DEPENDING ON EF CORE
1. THE DOMAIN LAYER DEFINES WHAT IT NEEDS — AS AN INTERFACE IT OWNS
2. THE APPLICATION LAYER DEPENDS ONLY ON THAT INTERFACE
3. THE INFRASTRUCTURE LAYER IMPLEMENTS IT — DEPENDING INWARD, ON THE DOMAIN'S OWN INTERFACE
4. THE COMPOSITION ROOT (238) IS THE ONLY PLACE THAT WIRES THE CONCRETE TYPE IN

Simple Example — a Product, Across Three Layers

// ═══ DOMAIN PROJECT — references NOTHING but the base class library ═══ public class Product { public int Id { get; private set; } public string Name { get; private set; } public decimal Price { get; private set; } public Product(string name, decimal price) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Product name is required."); // a real domain rule, enforced here if (price < 0) throw new ArgumentException("Price cannot be negative."); Name = name; Price = price; } } // The interface lives in the Domain/Application layer too — it's owned by the inside public interface IProductRepository { Task<Product?> GetByIdAsync(int id); Task AddAsync(Product product); } // ═══ APPLICATION PROJECT — references ONLY Domain ═══ public class CreateProductUseCase(IProductRepository repository) { public async Task<int> ExecuteAsync(string name, decimal price) { var product = new Product(name, price); // domain rule enforced inside the constructor await repository.AddAsync(product); return product.Id; } } // ═══ INFRASTRUCTURE PROJECT — references Domain/Application, AND EF Core ═══ public class EfProductRepository(ShopDbContext context) : IProductRepository { public async Task<Product?> GetByIdAsync(int id) => await context.Products.FindAsync(id); public async Task AddAsync(Product product) => await context.Products.AddAsync(product); } // ═══ PRESENTATION PROJECT (ASP.NET Core) — the composition root ═══ // Program.cs — the ONLY place EfProductRepository and IProductRepository are mentioned together builder.Services.AddScoped<IProductRepository, EfProductRepository>(); builder.Services.AddScoped<CreateProductUseCase>();

Code → Meaning → Result: Product.cs has no using Microsoft.EntityFrameworkCore; anywhere — you could delete the entire Infrastructure project and Product.cs would still compile. That's the Dependency Rule made concrete: the most important code in the application — what a valid product actually is — survives untouched even if the database technology underneath it is torn out and replaced entirely.

Real-World Example — Order Placement, End to End

A checkout flow, walked through every layer, showing exactly where each piece of code is allowed to live:

LayerContainsReferences
DomainOrder, OrderLine, the rule "an order needs at least one line," IOrderRepositorynothing (base class library only)
ApplicationPlaceOrderUseCase — checks stock via IInventoryRepository, charges via IPaymentGateway, saves via IOrderRepositoryDomain
InfrastructureEfOrderRepository, StripePaymentGateway, SmtpOrderNotifierApplication, Domain
PresentationOrdersController, calling PlaceOrderUseCaseApplication

Notice the controller in Presentation depends on PlaceOrderUseCase in Application — not directly on EfOrderRepository or StripePaymentGateway in Infrastructure. And PlaceOrderUseCase itself depends only on interfaces defined in Domain. If the team switches payment providers from Stripe to a new vendor, exactly one new Infrastructure class is written, and exactly one line changes in the composition root (238) — Order, PlaceOrderUseCase, and OrdersController are never touched.

Analogy

A Courthouse and Its Utilities

Think of the Domain layer as the actual law being interpreted inside a courthouse — the rules that decide a case's outcome. Those rules don't change because the building's electrical wiring changed, or because the courthouse switched from paper filing to a digital records system, or because a different security company now runs the metal detectors. The law is written in terms of legal principle, not in terms of any specific vendor's equipment.

The building's electricians, IT staff, and security contractors (Infrastructure) all have to work within a structure the law defines — they don't get to rewrite legal principle to suit whichever equipment is easiest to install. And the public entrance, the clerk's counter, the courtroom layout (Presentation) is how people interact with the whole system — but it, too, doesn't determine what the law says. Swap out the entire records system, or the entire security vendor, and the law itself is untouched — because it was never written in terms of them in the first place.

Under the Hood — How .NET Actually Enforces (or Fails to Enforce) This

FROM CONVENTION TO ENFORCEMENT
1. Project references are the primary enforcement mechanism
2. Without separate projects, the rule is just a convention, easy to violate silently
3. The composition root is where every arrow finally converges

Common Confusion

1. "This is a brand-new idea" — it isn't, you already learned its core principle

Clean Architecture didn't invent a new rule — it took the Dependency Inversion Principle from lesson 081 and applied it at the scale of an entire application's structure instead of one class. "High-level modules should not depend on low-level modules; both should depend on abstractions" is, word for word, the same idea as "the Domain layer should not depend on the Infrastructure layer; both depend on interfaces the Domain layer defines." If you understood 081, you already understand the core of this lesson — the new part is applying it consistently across every layer of a whole codebase, not just one constructor.

2. "The Application layer and the Domain layer are the same thing" — they're related but distinct

Domain holds the objects and rules themselves — what a valid Order is. Application holds the orchestration of those objects to fulfill a specific operation — the steps involved in placing an order, in what sequence, calling which abstractions. Many smaller applications combine these into one project pragmatically; larger ones separate them because the rate of change differs — core domain rules tend to be very stable, while specific application workflows change more often as requirements evolve.

Common Mistakes

Mistake 1 — Putting an EF Core attribute or navigation-property shape directly on a domain entity, "just this once"

Adding [Table("Products")] or shaping Product's constructor around what EF Core's change tracker prefers (a public parameterless constructor, mutable setters everywhere) rather than around what the domain rules actually require — a small, seemingly harmless leak of Infrastructure concerns into Domain.

Keep the entity's shape driven entirely by domain rules; use EF Core's Fluent API configuration (in the Infrastructure layer) to map that entity to a table, rather than decorating the entity itself with persistence-specific attributes.

Mistake 2 — Letting the Application layer reference Infrastructure directly, "just for this one repository"

CreateProductUseCase taking EfProductRepository as a constructor parameter instead of IProductRepository — a single exception that quietly breaks the Dependency Rule and welds the use-case to EF Core specifically.

Every dependency the Application layer takes must be an abstraction defined inward, in Domain or Application itself — with zero exceptions, the same discipline lesson 081 taught for any single class.

Mistake 3 — Applying full Clean Architecture ceremony to a genuinely small application

Splitting a five-screen internal tool into four separate .csproj projects, with the accompanying interfaces, composition-root wiring, and layer discipline — for an application that will likely never swap its database or its framework, maintained by one developer.

See "When Should I Use It?" below — this level of structural rigor earns its cost on applications with real, ongoing complexity and a real, multi-person team; it's genuine overhead on a small, simple one.

When Should I Use It?

SituationLeans toward
A large, long-lived application with real, non-trivial business rules and a multi-person teamFull Clean Architecture — separate projects, strict Dependency Rule enforcement
Business logic that genuinely needs to outlive a specific framework or database choiceFull Clean Architecture — this is exactly the scenario it's built for
A small internal tool, a prototype, or a project with one developer and no real complexity to isolateSkip the multi-project ceremony — a well-organized single project with clear folders is honest and sufficient
"Every professional application should be Clean Architecture" as the only justificationReconsider — this is exactly the same blanket-rule trap lesson 081 and 148 both warned against, applied to a bigger decision
Rule of thumb: the Dependency Rule's underlying discipline — don't let business logic depend on framework detail — is worth internalizing on every project, regardless of size. The full multi-project, ceremony-heavy structure is worth its real cost specifically on applications big enough and long-lived enough to actually benefit from swapping infrastructure, or scaling a team around clearly separated layers.

Mental Model

Domain = the center, the most stable, no outside dependencies at all.
Application = orchestrates the domain, depends only on Domain and its own abstractions.
Infrastructure & Presentation = the detail — swappable, technology-specific, depends inward.
The Dependency Rule = arrows only point inward; an inner layer never knows an outer one exists.

Remember: this is lesson 081's Dependency Inversion Principle, applied to a whole application's file structure instead of one class's constructor.

Key Takeaway


Check Your Understanding

You've seen the Dependency Rule applied to a real Product example across four layers. Let's confirm you can spot it holding — and breaking — in new scenarios.

1. According to the Dependency Rule, which of these is allowed?

Show answer

Correct: B

Why B is correct: This is precisely the allowed direction — Infrastructure, an outer layer, depends inward on Domain, an inner layer, in order to implement an interface Domain defines. Arrows pointing inward are exactly what the Dependency Rule permits.

Why A is incorrect: This is the Domain layer depending outward on Infrastructure — precisely forbidden by the Dependency Rule, which the lesson states as an absolute rule with no exceptions.

Why C is incorrect: The Domain layer must have zero dependency on any framework, including ASP.NET Core — this describes exactly the kind of leak Mistake 1 and the rule's precise statement warn against.

Why D is incorrect: This is Mistake 2 directly — the Application layer bypassing an abstraction to reference Infrastructure concretely breaks the rule just as much as Domain depending on Infrastructure would.

Reinforcement: Only inward-pointing references are allowed — an outer layer may know about an inner one; an inner layer must never know an outer one exists.

2. Why does this lesson describe Clean Architecture as "Dependency Inversion (081), applied at the scale of an entire application's architecture"?

Show answer

Correct: B

Why B is correct: Lesson 081's DIP states that high-level modules should depend on abstractions, not low-level details — Clean Architecture applies that identical structural rule (Domain/Application depending only on abstractions they own, never on Infrastructure directly) at the scale of an entire application's project structure.

Why A is incorrect: The lesson explicitly and directly connects the two — this is not a superficial comparison, it's the same principle at a different scale.

Why C is incorrect: As lesson 081 established, DIP itself requires no DI container — it's a compile-time reference structure. The same is true for Clean Architecture's Dependency Rule, which is fundamentally about project references, not runtime tooling.

Why D is incorrect: Neither is a C# language feature at all — both are design principles/architectural patterns independent of any specific C# version.

Reinforcement: Recognizing the same underlying principle recurring at different scales — one constructor, then an entire codebase — is the core insight this lesson builds on.

3. A small internal tool with one developer, five screens, and no realistic plan to ever change database or framework is split into four separate Clean Architecture projects with full layer separation. What does this lesson say about that decision?

Show answer

Correct: B

Why B is correct: This is Common Mistake 3 and the "When Should I Use It?" table directly — full Clean Architecture's structural overhead is worth paying for genuine complexity and long-lived, multi-person projects; applying it reflexively to a small, simple tool is unnecessary ceremony.

Why A is incorrect: This is exactly the blanket-rule trap the lesson explicitly warns against — "every professional application should be Clean Architecture" is called out as a weak justification on its own.

Why C is incorrect: EF Core works perfectly well referenced from an Infrastructure project in a multi-project solution — this isn't a technical limitation at all.

Why D is incorrect: The lesson names no specific screen count as a threshold — the deciding factors are complexity, longevity, and team size, not a fixed number of screens.

Reinforcement: Weigh the real complexity and lifespan of the specific project, not a blanket rule, when deciding how much of Clean Architecture's structural ceremony to adopt.

You've now scaled Dependency Inversion from one class up to an entire application's structure. Next: Hexagonal Architecture — a closely related idea, described through a different vocabulary, pursuing the exact same goal.


dotnetmadeeasy.com — Learn C# and .NET, the right way.