# Dependency Rule in Clean Architecture Explained

> Source code dependencies must only point inward. That one sentence decides your project references, where your interfaces live, and why the DI container sits at the edge. Here is how the Dependency Rule works, how control can flow outward while dependencies point inward, and how to enforce it with the compiler and architecture tests.

Published: 2026-08-11. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/dependency-rule-clean-architecture

Strip away the diagrams and the folder structures, and Clean Architecture reduces to a single rule: source code dependencies point inward.
Everything else (the layers, the interfaces, the DI wiring) exists to serve that rule.
Here is what it means in practice, the part about control flow that confuses everyone at first, and how to make the compiler enforce it for you.

## What Is the Dependency Rule?

Robert C. Martin defines it simply:

> Source code dependencies must only point inward.

In [Clean Architecture](https://milanjovanovic.tech/blog/clean-architecture-and-the-benefits-of-structured-software-design), the application is organized in concentric circles:

1. **Domain** (innermost) - entities, value objects, domain events
2. **Application** - use cases, commands, queries, interfaces
3. **Infrastructure** (outermost) - database access, external services, frameworks

The Dependency Rule says: **nothing in an inner circle can know anything about an outer circle.**

- Domain doesn't reference Application
- Application doesn't reference Infrastructure
- Infrastructure references Application and Domain

Arrows always point inward. Never outward.

## Why the Dependency Rule Matters

Without it, your domain logic gets polluted with infrastructure concerns:

```csharp
// WRONG - Domain depends on Infrastructure
public class Order
{
    public void Confirm()
    {
        // Domain class knows about EF Core
        using var context = new AppDbContext();
        context.Orders.Update(this);
        context.SaveChanges();

        // Domain class knows about email service
        var emailService = new SendGridEmailService();
        emailService.Send(this.CustomerEmail, "Order confirmed!");
    }
}
```

This code can't be tested without a database and an email service. It can't be reused in a different context. And changing your email provider means changing your domain.

With the Dependency Rule:

```csharp
// RIGHT - Domain knows nothing about infrastructure
public class Order
{
    public void Confirm()
    {
        // Pure business logic
        Status = OrderStatus.Confirmed;
        ConfirmedAt = DateTime.UtcNow;
        RaiseDomainEvent(new OrderConfirmedDomainEvent(Id));
    }
}
```

The domain is pure. How to persist the order and send the email is handled by outer layers.

## How to Enforce It in .NET

### Project References

Set up your project references to match the Dependency Rule:

```xml
<!-- Domain - references NOTHING -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>
</Project>

<!-- Application - references Domain only -->
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <ProjectReference Include="..\Domain\Domain.csproj" />
  </ItemGroup>
</Project>

<!-- Infrastructure - references Application (and Domain transitively) -->
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <ProjectReference Include="..\Application\Application.csproj" />
  </ItemGroup>
</Project>

<!-- API - references Infrastructure and Application -->
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <ProjectReference Include="..\Application\Application.csproj" />
    <ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
  </ItemGroup>
</Project>
```

The compiler enforces this. If Domain tries to reference an Infrastructure class, it won't compile.

### Dependency Inversion

The Application layer defines interfaces. The Infrastructure layer implements them:

```csharp
// Application layer - defines what it needs
public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id, CancellationToken ct);
    void Add(Order order);
}

public interface IEmailService
{
    Task SendOrderConfirmationAsync(string email, Guid orderId, CancellationToken ct);
}
```

```csharp
// Infrastructure layer - implements using specific technology
public class OrderRepository : IOrderRepository
{
    private readonly AppDbContext _dbContext;

    public OrderRepository(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct)
    {
        return await _dbContext.Orders
            .Include(o => o.LineItems)
            .FirstOrDefaultAsync(o => o.Id == id, ct);
    }

    public void Add(Order order) => _dbContext.Orders.Add(order);
}
```

The Application layer calls `IOrderRepository`. It doesn't know (or care) that Entity Framework Core is behind it.

This is the **Dependency Inversion Principle** at work - high-level modules (Application) don't depend on low-level modules (Infrastructure). Both depend on abstractions.

## Flow of Control vs Source Dependencies

Here's the part that confuses most people when they first meet the Dependency Rule.

At runtime, control flows **outward**: the [Application layer](https://milanjovanovic.tech/blog/application-layer-clean-architecture) calls the database, sends emails, publishes messages.
So how can dependencies point inward?

Because a *source dependency* is about what your code references at compile time, not who calls whom at runtime.

Walk through a single request:

1. The API controller (outer) calls a command handler (inner). Control flows inward, dependency points inward. No conflict.
2. The handler calls `IOrderRepository.GetByIdAsync(...)`. Control is about to flow outward into EF Core, but the handler only references an interface *it owns*.
3. The DI container resolved `IOrderRepository` to `OrderRepository` from Infrastructure at startup. The call lands in the outer layer without the inner layer ever naming it.

The interface is the trick.
It lets control flow outward while the compile-time arrow keeps pointing inward.
When you see a diagram of Clean Architecture, the arrows show source dependencies, not call direction.

![The command handler references the IOrderRepository interface it owns in the application layer, the Infrastructure OrderRepository implements that interface, and at runtime control flows outward from the handler to the implementation while both source dependencies point inward to the interface](https://milanjovanovic.tech/blogs/articles/dependency-rule-clean-architecture/control-vs-dependency.png)

## The Rule Applies to NuGet Packages Too

Project references are only half the story.
A Domain project with zero project references but a `Microsoft.EntityFrameworkCore` package reference violates the Dependency Rule just the same.

My guidelines:

- **Domain**: no packages. Entities, value objects, and domain events are plain C#.
- **Application**: contracts-only packages at most (MediatR contracts, FluentValidation). No EF Core, no HTTP clients, no cloud SDKs.
- **Infrastructure**: this is where the package weight belongs - EF Core, message brokers, external SDKs.

The gray zone is logging.
`Microsoft.Extensions.Logging.Abstractions` in the Application layer is a pragmatic exception most teams accept: it's an abstraction package with no implementation baggage.

## Where Does the DI Container Fit?

Someone has to know about *all* the layers to wire them together.
That's the **composition root**: the API project's `Program.cs`.

The outermost layer referencing everything isn't a violation.
It's the design: the most volatile, framework-heavy project depends on everything, and nothing depends on it.

```csharp
builder.Services
    .AddApplication()       // handlers, validators, behaviors
    .AddInfrastructure(builder.Configuration); // EF Core, email, auth
```

If you find yourself wanting to resolve services inside the Domain layer (service locator style), that's the Dependency Rule being violated at runtime even though the compiler is happy.
Domain objects receive what they need as method parameters; they don't ask a container.

## What Breaks the Dependency Rule

### 1. Framework Attributes in Domain

```csharp
// WRONG - Domain depends on EF Core
public class Order
{
    [Key]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(100)]
    public string CustomerName { get; set; }
}
```

Fix: Use EF Core's Fluent API configuration in the Infrastructure layer:

```csharp
// Infrastructure layer
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> builder)
    {
        builder.HasKey(o => o.Id);
        builder.Property(o => o.CustomerName).IsRequired().HasMaxLength(100);
    }
}
```

### 2. Using Concrete Services in Application

```csharp
// WRONG - Application depends on Infrastructure
public class PlaceOrderCommandHandler
{
    private readonly AppDbContext _dbContext;  // Infrastructure class
}
```

Fix: Depend on abstractions defined in the Application layer:

```csharp
// RIGHT - Application depends on its own interfaces
public class PlaceOrderCommandHandler
{
    private readonly IOrderRepository _repository;
    private readonly IUnitOfWork _unitOfWork;
}
```

### 3. Leaking Infrastructure Types

```csharp
// WRONG - Application returns infrastructure types
public class GetOrderQueryHandler
{
    public async Task<DbSet<Order>> Handle(GetOrderQuery query)
    {
        return _dbContext.Orders;  // Leaking DbSet<T>
    }
}
```

Fix: Map to DTOs or domain objects:

```csharp
public async Task<OrderResponse?> Handle(GetOrderQuery query)
{
    return await _dbContext.Orders
        .Where(o => o.Id == query.OrderId)
        .Select(o => new OrderResponse(
            o.Id,
            o.Customer.Name,
            o.TotalAmount.Amount,
            o.Status.Name,
            o.CreatedAt))
        .FirstOrDefaultAsync();
}
```

## Architecture Tests

Enforce the Dependency Rule automatically with architecture tests:

```csharp
[Fact]
public void Domain_Should_Not_Reference_Application()
{
    var result = Types
        .InAssembly(typeof(Order).Assembly)
        .ShouldNot()
        .HaveDependencyOn(typeof(PlaceOrderCommand).Assembly.GetName().Name)
        .GetResult();

    result.IsSuccessful.Should().BeTrue();
}

[Fact]
public void Domain_Should_Not_Reference_Infrastructure()
{
    var result = Types
        .InAssembly(typeof(Order).Assembly)
        .ShouldNot()
        .HaveDependencyOn(typeof(AppDbContext).Assembly.GetName().Name)
        .GetResult();

    result.IsSuccessful.Should().BeTrue();
}

[Fact]
public void Application_Should_Not_Reference_Infrastructure()
{
    var result = Types
        .InAssembly(typeof(PlaceOrderCommand).Assembly)
        .ShouldNot()
        .HaveDependencyOn(typeof(AppDbContext).Assembly.GetName().Name)
        .GetResult();

    result.IsSuccessful.Should().BeTrue();
}
```

Run these in CI. If someone adds a wrong dependency, the build fails.

I wrote more about this approach in [**enforcing software architecture with architecture tests**](https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests), including rules that go beyond layer references (naming conventions, sealed handlers, interface placement).

Violating the Dependency Rule is also the most common of the [**Clean Architecture anti-patterns**](https://milanjovanovic.tech/blog/clean-architecture-anti-patterns) I see in real codebases - usually starting with a single EF Core attribute on a domain entity.

## Summary

The Dependency Rule is the foundation of Clean Architecture. It keeps your domain and application logic independent of frameworks, databases, and external services.

Enforce it through:
1. **Project references** - the compiler prevents invalid dependencies
2. **Dependency Inversion** - interfaces in Application, implementations in Infrastructure
3. **Architecture tests** - automated verification in CI

Get the Dependency Rule right, and everything else in Clean Architecture falls into place.

Thanks for reading, and stay awesome!

---

## Frequently asked questions

### What is the Dependency Rule in Clean Architecture?

Source code dependencies must only point inward, toward higher-level policies. The domain references nothing, the application layer references only the domain, and infrastructure references the application layer. Nothing in an inner circle knows about an outer circle.

### How is the Dependency Rule different from the Dependency Inversion Principle?

The Dependency Rule is an architectural constraint about the direction of dependencies between layers. The Dependency Inversion Principle is the mechanism that makes it possible: inner layers define abstractions, and outer layers implement them.

### Does the Dependency Rule apply to NuGet packages?

Yes. A domain project that references EF Core violates the rule just as much as one referencing an Infrastructure project. Keep the domain free of packages, and limit the application layer to contracts-only packages.

### How does the application layer call the database if it cannot reference infrastructure?

Through interfaces it defines itself, like IOrderRepository. At runtime the DI container injects the infrastructure implementation, so control flows outward while the source dependency still points inward.

### How do you enforce the Dependency Rule in .NET?

Three layers of defense: project references so the compiler blocks invalid dependencies, dependency inversion so implementations stay in outer layers, and architecture tests in CI that fail the build when someone introduces a violation.
