grimoire-dotnet-clean-architecture

star 0

Clean Architecture best practices for modern .NET (current: .NET 10) — the Dependency Rule, layer responsibilities (Domain, Application, Infrastructure, Presentation), and the 4-project solution layout, with rich domain models, value objects, domain events, the result pattern, and CQRS with or without MediatR (commercial since v13 — covers Wolverine and plain handler alternatives). Includes architecture tests with ArchUnitNET, when NOT to use Clean Architecture (vertical slice and hybrid approaches), and avoiding over-abstraction: generic repositories over EF Core, interface-for-everything, anemic domains, leaky IQueryable boundaries. Use when structuring a .NET solution, designing or reviewing layers, or deciding where business logic belongs. Triggers: clean architecture, onion architecture, hexagonal, ports and adapters, dependency rule, domain layer, application layer, use case, CQRS, MediatR, Wolverine, value object, domain event, anemic model, repository pattern, vertical slice, ArchUnitNET.

anton-kochev By anton-kochev schedule Updated 6/11/2026

name: grimoire.dotnet-clean-architecture description: "Clean Architecture best practices for modern .NET (current: .NET 10) — the Dependency Rule, layer responsibilities (Domain, Application, Infrastructure, Presentation), and the 4-project solution layout, with rich domain models, value objects, domain events, the result pattern, and CQRS with or without MediatR (commercial since v13 — covers Wolverine and plain handler alternatives). Includes architecture tests with ArchUnitNET, when NOT to use Clean Architecture (vertical slice and hybrid approaches), and avoiding over-abstraction: generic repositories over EF Core, interface-for-everything, anemic domains, leaky IQueryable boundaries. Use when structuring a .NET solution, designing or reviewing layers, or deciding where business logic belongs. Triggers: clean architecture, onion architecture, hexagonal, ports and adapters, dependency rule, domain layer, application layer, use case, CQRS, MediatR, Wolverine, value object, domain event, anemic model, repository pattern, vertical slice, ArchUnitNET."

Clean Architecture for .NET

How to apply Clean Architecture properly on modern .NET. Examples target .NET 10 and reflect the current (2026) ecosystem — including the MediatR licensing change and the shift toward pragmatic, hybrid architectures. The focus is solution structure and where logic belongs — C# language idioms, web API design, and testing mechanics have their own skills.

This skill provides guidance, not enforcement. Check the project's existing structure first and match it. Don't restructure a working solution unless asked.

Should you use Clean Architecture?

Match architectural ceremony to domain complexity — this is the single most important decision.

Your project Recommendation
Complex business rules, long-lived (5+ years), large team Clean Architecture — the layer boundaries pay for themselves
CRUD-heavy, API-focused, rapid delivery, shifting requirements Vertical slice architecture — feature folders, minimal layering
Mixed: mostly simple, a few genuinely complex areas Hybrid (the modern default) — feature folders at the macro level, clean-architecture discipline inside the complex features
Microservice candidates Vertical slices — slices become service boundaries

The cost is real: in a fully-layered solution, a trivial feature can touch 8–10 files across 4 projects. Don't pay that tax for a simple domain. Conversely, complex invariants scattered through handlers and controllers cost far more over time.

→ Deep dive (incl. vertical slice and hybrid layouts): reference/solution-structure.md

The Dependency Rule & solution layout

One rule makes everything work: source-code dependencies point inward only. Domain references nothing. Nothing inner knows anything about anything outer.

            ┌──────────────────────────────┐
            │   Web (Presentation)         │──┐
            │   endpoints, composition root│  │
            └──────────────┬───────────────┘  │
                           │ references       │ references
            ┌──────────────▼───────────────┐  │
            │   Infrastructure             │  │
            │   EF Core, external services │  │
            └──────────────┬───────────────┘  │
                           │ references       │
            ┌──────────────▼───────────────┐  │
            │   Application                │◄─┘
            │   use cases, ports, validation
            └──────────────┬───────────────┘
                           │ references
            ┌──────────────▼───────────────┐
            │   Domain                     │
            │   entities, value objects,   │
            │   domain events, invariants  │
            └──────────────────────────────┘
Layer Contains Must NOT reference
Domain Entities, value objects, domain events, domain services, domain exceptions Anything — no NuGet framework packages, no EF Core, no other project
Application Use-case handlers, port interfaces (abstractions), validators, DTOs EF Core, ASP.NET Core, Infrastructure
Infrastructure DbContext + EF configurations, external service adapters, port implementations Web
Web (Presentation) Endpoints/controllers, request/response contracts, Program.cs composition root Domain internals directly (go through Application)

→ Deep dive: reference/solution-structure.md

Domain modeling quick-reference

Rule Do this
Rich, not anemic Behavior lives on the entity — invariants enforced in methods, not in handlers checking flags
Guard the invariants Private setters, factory methods, guard clauses; an entity can never exist in an invalid state
Value objects for concepts record types with validation at creation — Money, Email, DateRange — equality by value
Encapsulate collections Private List<T> field, expose IReadOnlyCollection<T>; mutations only via entity methods
Domain events for side effects Entity raises the event; dispatch happens at SaveChanges — keeps aggregates decoupled
Result pattern for expected failures Result<T> for business-rule violations; exceptions for bugs and truly exceptional states
public sealed class Order
{
    private readonly List<OrderLine> _lines = [];
    public IReadOnlyCollection<OrderLine> Lines => _lines.AsReadOnly();

    public Result Ship()
    {
        if (_lines.Count == 0)
            return Result.Failure(OrderErrors.EmptyOrderCannotShip);

        Status = OrderStatus.Shipped;
        Raise(new OrderShippedDomainEvent(Id));
        return Result.Success();
    }
}

→ Deep dive: reference/domain-layer.md

Use cases & CQRS quick-reference

Rule Do this
One handler per use case A single class with one public method — the unit of testing and of change
Thin orchestration, fat domain The handler loads aggregates, calls domain methods, saves — business rules live in Domain
CQRS is a pattern, not a library Commands mutate via the domain; queries may bypass it entirely (Dapper/EF projections straight to DTOs)
Ports are owned by Application Define IEmailSender, IApplicationDbContext where they're consumed; Infrastructure implements them
Validate at the boundary Input validation (FluentValidation) on the command; business invariants stay in the domain

MediatR is commercial since v13 (April 2025). Options, in order of preference for new projects: plain handler interfaces + DI (no dependency, shown below), Wolverine (source-generated, adds retries/sagas/outbox), or pin MediatR 12.x (last free version). Don't pick a mediator library by habit — most solutions only use 10% of it.

For CQRS beyond the handler split — the maturity ladder, read models and projections, the outbox pattern, idempotency, and event sourcing with Marten — defer to the grimoire.dotnet-cqrs skill.

public interface ICommandHandler<in TCommand, TResult>
{
    Task<TResult> Handle(TCommand command, CancellationToken ct);
}

public sealed class ShipOrderHandler : ICommandHandler<ShipOrderCommand, Result>
{
    private readonly IApplicationDbContext _db;

    public ShipOrderHandler(IApplicationDbContext db) => _db = db;

    public async Task<Result> Handle(ShipOrderCommand command, CancellationToken ct)
    {
        var order = await _db.Orders.FindAsync([command.OrderId], ct);
        if (order is null) return Result.Failure(OrderErrors.NotFound);

        var result = order.Ship();          // business rule lives in the domain
        if (result.IsFailure) return result;

        await _db.SaveChangesAsync(ct);
        return Result.Success();
    }
}

→ Deep dive (incl. dispatch without MediatR, decorator pipelines): reference/application-layer.md

Avoiding over-abstraction

The most common Clean Architecture failure is too much of it.

Anti-pattern Instead
Generic IRepository<T> wrapping EF Core DbContext already is a repository + unit of work; expose it via a port (IApplicationDbContext) or write specific repositories for genuine aggregate-access patterns
Interface for every class Abstract only what you swap or mock at a boundary (I/O, time, external services) — not handlers, not domain services with one implementation
IQueryable<T> across layer boundaries Return materialized results/DTOs; an exposed IQueryable leaks EF semantics and makes the boundary a lie
Layering a trivial CRUD feature Collapse it — endpoint + DbContext is fine for simple reads; save the ceremony for real logic

→ Deep dive: reference/testing-and-antipatterns.md

Testing the architecture

What How
Domain Plain unit tests, no mocks — the payoff of a rich domain model
Use-case handlers Unit tests mocking ports only
Dependency rule Architecture tests in CI — ArchUnitNET (NetArchTest is unmaintained since 2023; NetArchTest.eNhancedEdition is the fallback)
Infrastructure Integration tests against real dependencies (Testcontainers)
[Fact]
public void Domain_Should_Not_Depend_On_Outer_Layers() =>
    Types().That().ResideInAssembly(DomainAssembly)
        .Should().NotDependOnAny(
            Types().That().ResideInAssembly(ApplicationAssembly, InfrastructureAssembly))
        .Check(Architecture);

→ Deep dive: reference/testing-and-antipatterns.md

Which reference file do I need?

You're working on… Open
Project layout, composition root, vertical slice / hybrid decision reference/solution-structure.md
Entities, value objects, domain events, result pattern reference/domain-layer.md
Handlers, CQRS, ports, MediatR alternatives, validation reference/application-layer.md
EF Core, repositories, external services, endpoints, DTO mapping reference/infrastructure-and-presentation.md
Architecture tests, test strategy, anti-pattern catalog reference/testing-and-antipatterns.md

Recommendations draw on Jason Taylor's CleanArchitecture template (.NET 10), the Ardalis template, Milan Jovanovic's Clean Architecture writings, and Microsoft's architecture guidance — cited in the reference files.

Install via CLI
npx skills add https://github.com/anton-kochev/grimoire --skill grimoire-dotnet-clean-architecture
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
anton-kochev
anton-kochev Explore all skills →