← Back to Clean Architecture

Clean Architecture

The Dependency Rule

Source code dependencies point in one direction, and the compiler can prove it.

The Dependency Rule says source code can depend inward, never outward. Business logic sits at the centre; the web framework, the database, and every other detail sit outside it and point back in. Say that as a diagram and it sounds like a suggestion. Say it as a project reference and it becomes something the compiler enforces.

A reference the compiler enforces

eShopOnWeb puts the rule into its project files. ApplicationCore — entities, interfaces, and use cases — references nothing but a shared DTO project:

<ItemGroup>
  <ProjectReference Include="..\BlazorShared\BlazorShared.csproj" />
</ItemGroup>ApplicationCore is allowed to depend on a shared DTO, not on Infrastructure. The compiler will refuse an outward project reference.

No reference to Infrastructure. Infrastructure references back the other way:

<ItemGroup>
  <ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
</ItemGroup>Infrastructure points inward. Details may know the centre; the centre must not know the details.

Swap SQL Server for Postgres and Infrastructure.csproj is the file that changes. ApplicationCore does not need to know a swap happened, because nothing in it could have referenced Entity Framework, a connection string, or a DbContext in the first place.

What a use case is allowed to know

OrderService lives in ApplicationCore and orchestrates one use case: turn a basket into an order.

public class OrderService : IOrderService
{
    private readonly IRepository<Order> _orderRepository;
    private readonly IUriComposer _uriComposer;
    private readonly IRepository<Basket> _basketRepository;
    private readonly IRepository<CatalogItem> _itemRepository;
    private readonly IMediator _mediator;Every collaborator is an interface. A project reference to Infrastructure would not compile.

    public OrderService(IRepository<Basket> basketRepository,
        IRepository<CatalogItem> itemRepository,
        IRepository<Order> orderRepository,
        IUriComposer uriComposer, IMediator mediator)
    {
        _orderRepository = orderRepository;
        _uriComposer = uriComposer;
        _basketRepository = basketRepository;
        _itemRepository = itemRepository;
        _mediator = mediator;
    }The constructor is where the outside world is admitted — as abstractions. The use case never constructs a repository.

    public async Task CreateOrderAsync(int basketId, Address shippingAddress)
    {
        var basketSpec = new BasketWithItemsSpecification(basketId);
        var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec);

        Guard.Against.Null(basket, nameof(basket));
        Guard.Against.EmptyBasketOnCheckout(basket.Items);

        var catalogItemsSpecification = new CatalogItemsSpecification(
            basket.Items.Select(item => item.CatalogItemId).ToArray());
        var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);

        var items = basket.Items.Select(basketItem =>
        {
            var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);
            var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name,
                _uriComposer.ComposePicUri(catalogItem.PictureUri));
            return new OrderItem(itemOrdered, basketItem.UnitPrice, basketItem.Quantity);
        }).ToList();

        var order = new Order(basket.BuyerId, shippingAddress, items);
        await _orderRepository.AddAsync(order);CreateOrderAsync is the use case. It talks to repositories, not a DbContext.

        var orderCreatedEvent = new OrderCreatedEvent(order);
        await _mediator.Publish(orderCreatedEvent);Publishing OrderCreated through IMediator keeps Infrastructure a listener, not a dependency.
    }
}

Every dependency OrderService takes — IRepository<T>, IUriComposer, even IMediator for announcing that the order happened — is an interface. Nothing here names Entity Framework, SQL Server, or an in-memory store. OrderService does not know which one is answering FirstOrDefaultAsync, and it does not need to.

The next stop is what lives at the centre those interfaces protect: Entities.