The Dependency Rule — what does "dependencies point inward" actually mean?
The Dependency Rule is the one rule in Clean Architecture:
Source code dependencies can only point inward.
It sounds abstract. In practice it means three concrete things.
1. The innermost layer references nothing
Your Domain project has zero <ProjectReference> lines. It can't using Microsoft.EntityFrameworkCore. It can't reference Microsoft.AspNetCore.*. It can't depend on your ORM, your web framework, or your logging library.
<!-- Domain/Domain.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<!-- NO ItemGroup with project references -->
</Project>
If a teammate adds using System.Web to a domain entity, the build fails. Architecture enforced by the compiler.
2. The use case defines what it needs; the infrastructure implements it
A use case in Application might need to read orders from a database. But Application doesn't know about EF Core. So it defines an interface:
// Application/UseCases/PlaceOrder/IOrderRepository.cs
public interface IOrderRepository
{
Task AddAsync(Order order, CancellationToken ct);
Task<Order?> GetAsync(Guid id, CancellationToken ct);
}
The Infrastructure project implements it:
// Infrastructure/Persistence/EfOrderRepository.cs
public class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _db;
public EfOrderRepository(AppDbContext db) => _db = db;
public async Task AddAsync(Order order, CancellationToken ct) {
_db.Orders.Add(order);
await _db.SaveChangesAsync(ct);
}
public Task<Order?> GetAsync(Guid id, CancellationToken ct) =>
_db.Orders.FirstOrDefaultAsync(o => o.Id == id, ct);
}
At runtime, the DI container injects EfOrderRepository wherever the use case asks for IOrderRepository. The use case never sees EF Core.
This is the Dependency Inversion Principle — and it's the trick that makes the whole pattern work.
3. The composition root is the ONLY place that wires layers
// Web/Program.cs — the composition root
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<IEmailSender, SendGridEmailSender>();
builder.Services.AddSingleton<IClock, SystemClock>();
Nowhere else in the codebase does anyone new EfOrderRepository(). The composition root is the seam where abstractions bind to concrete implementations.
Why this matters
- Test in isolation — give the handler
new FakeOrderRepository()in tests, no DB needed - Swap implementations — switch from EF Core to Dapper, change ONE file (the registration in
Program.cs) - Build forces correctness — can't accidentally reference EF in domain code, compiler stops you
Common violations (and how to spot them)
Violation 1: Domain entity has EF attributes
[Table("orders")] // ❌ Domain depending on EF
public class Order { [Key] public Guid Id { get; set; } }
Fix: configure EF in Infrastructure/Persistence/AppDbContext.cs via fluent API.
Violation 2: Use case directly using EF Core
public class PlaceOrderHandler {
private readonly AppDbContext _db; // ❌ Application referencing Infrastructure
}
Fix: inject IOrderRepository, not AppDbContext.
Violation 3: Repository exposing IQueryable
public interface IOrderRepository {
IQueryable<Order> Query(); // ❌ Application now coupled to LINQ-to-Entities
}
Fix: return concrete collections. If you need flexible queries, that's CQRS — use a separate query service.
Violation 4: Domain layer doing IO
public class Order {
public void Cancel() {
var now = DateTime.UtcNow; // ❌ Domain dependent on system clock
}
}
Fix: inject an IClock interface; pass now as a parameter into domain methods.
How to enforce the rule in CI
Beyond .csproj references, add an architecture test using NetArchTest:
[Fact]
public void Domain_should_have_no_dependencies()
{
var result = Types.InAssembly(typeof(Order).Assembly)
.ShouldNot()
.HaveDependencyOn("Microsoft.EntityFrameworkCore")
.GetResult();
Assert.True(result.IsSuccessful);
}
Runs as part of your test suite. Catches violations before code review.
Interview rule of thumb
"Dependencies point inward = the Domain knows nothing about Infrastructure. Application defines interfaces; Infrastructure implements them. The composition root wires them together. Project references enforce it at compile time."