Menu
Theme

Clean Architecture

What Clean Architecture is, when it fits and when it does not, and how to apply and enforce it. For architects choosing or reviewing a layered design, with a worked example for a configuration tool.

Researched
14 Oct 2026
Reading time
10 min
Sources
12
Review due
14 Oct 2029
TL;DR
  • One rule: source-code dependencies point only inwards, toward the business rules.
  • Business rules sit at the centre; UI, database, devices and frameworks are replaceable details at the edge.
  • It is an architectural style, not a design pattern, and a close relative of Hexagonal and Onion.
  • Pays off with real business logic, volatile integrations and a long lifetime. Overkill for forms over data.
  • It does not decide where rules live (code vs. data) or how the system is extended — those are separate decisions.
On this page 13 sections
  1. The idea in one minute
  2. When it fits
  3. The dependency rule
  4. Crossing a boundary
  5. Vocabulary
  6. Martin’s rings vs. the 4-project template
  7. Worked example: a configuration tool
  8. Where it is used in practice
  9. Gains and costs
  10. How it goes wrong
  11. Enforcing it
  12. Relatives and alternatives
  13. Origins

The idea in one minute

Clean Architecture puts the code that encodes business rules at the centre and pushes everything that is a delivery or storage detail to the edge: UI, database, file formats, devices, frameworks, network protocols.

The centre does not know the edge exists. It defines interfaces (ports) for what it needs; outer code implements them (adapters). You can replace the database, the UI technology or a device protocol without touching the rules, and you can test the rules without any of those in place.

When it fits

Fits when… Doesn’t fit when…
Non-trivial business rules (validation, dependencies, compatibility) Mostly CRUD screens over a database
Several or volatile integrations (devices, protocols, formats, external systems) One UI, one database, nothing else
Expected lifetime 5+ years, UI or framework changes likely Prototype, one-off, short-lived tool
High test or certification demands Time-to-market dominates and the team is new to the style
Multiple teams or developers in parallel One or two developers who know the whole code

The dependency rule

There is exactly one hard rule: source-code dependencies point only inwards. Nothing in an inner ring may name anything in an outer ring — no class, function, framework type or data format.

The dependency rule: four concentric rings — Frameworks outside, then Adapters, Use cases and Entities at the centre — with arrows from every side pointing inward, because source code may only depend on rings closer to the centre.FrameworksAdaptersUse casesEntities
Every dependency points inward, from whichever side. Colours follow Martin's original diagram; the number of rings is not fixed, the direction is.
Ring Holds Changes when…
Entities Enterprise-wide business rules and objects that would exist even without this application. The business itself changes. Rarely.
Use cases Application-specific rules: one class or function per thing a user can do. They orchestrate entities and define the ports they need. The application’s behaviour changes.
Adapters Translation between use-case data and the outside world: controllers, presenters, view models, repositories, serializers. A UI, API or storage format changes.
Frameworks Glue only: web or desktop framework, ORM, database, device drivers, message bus. You upgrade or replace a technology.

The rings are ordered by how often and why they change — stable policy inside, volatile detail outside. It is the Stable Dependencies Principle applied to a whole system.

Crossing a boundary

Control flows outward-in and back out (UI → use case → UI), yet source dependencies may only point inwards. Dependency inversion solves this: the use case declares an interface, and the outer ring implements it.

Controller calls the Input Port interface, implemented by the Use Case Interactor. The Interactor calls the Output Port interface, implemented by the Presenter.adapters ringuse-cases ringadapters ringController«interface»Input Port«interface»Output PortPresenterUse Case Interactor
Filled arrow: calls / depends on. Hollow triangle: implements. Control flows Controller → Interactor → Presenter, yet every source dependency crosses a dashed boundary into the use-case ring.

Data crossing a boundary should be plain structures — request and response models, DTOs — never ORM rows, HTTP requests or framework objects. Many teams drop the separate Presenter and return a response object from the use case; that is a common, acceptable simplification.

Vocabulary

The Czech column gives the usual technical translation.

Term Czech Meaning
Entity entita Object with identity and core business rules. Not the same as an ORM entity.
Use case / Interactor případ užití One application operation, e.g. “Validate configuration”, “Deploy to device”.
Port port, rozhraní Interface owned by the inner ring. Input ports: what the app offers. Output ports: what it needs.
Adapter adaptér Implementation of a port for one technology, e.g. a CAN bus gateway or a JSON file repository.
Gateway / Repository brána, repozitář Output port for data or external systems.
Presenter / View model prezentér Turns a use-case response into something a UI can render.
DTO / Request model přenosový objekt Plain data crossing a boundary.
Composition root kořen kompozice The one outermost place that wires concrete adapters to ports, usually via dependency injection.
Dependency inversion inverze závislostí High-level policy and low-level detail both depend on an abstraction owned by the policy.
Humble Object — Keep hard-to-test code (UI, I/O) so thin it needs no tests; move logic somewhere testable.
Screaming Architecture — Top-level structure should name the domain (“Configurations”, “Devices”), not the framework.

Martin’s rings vs. the 4-project template

Most .NET material — including many beginner articles[4] — describes the community template popularised by Jason Taylor and Steve Smith (Ardalis), not Martin’s book directly. The mapping:

Martin (book) Typical .NET template Typical contents
Entities Domain Entities, value objects, domain events, domain exceptions
Use cases Application Commands/queries and handlers (often MediatR), validation, port interfaces
Adapters + Frameworks Infrastructure (+ Persistence) EF Core DbContext, file and e-mail services, external API clients
Adapters + Frameworks Presentation / Web / UI Controllers, endpoints, Blazor or WPF views and view models; the composition root

The template brings choices Martin never required — CQRS, MediatR, one project per layer, mapping libraries. When someone says “we use Clean Architecture”, ask which of these they mean.

Worked example: a configuration tool

An illustrative C# sketch of a device-parameter configuration tool that validates a configuration against rules and writes it to a device. The project references alone already enforce the dependency rule:

Project references in the example solution: Desktop references Application, and Infrastructure only for wiring; Infrastructure references Application to implement its ports; Application references Domain; Domain references nothing.DesktopUI + composition rootInfrastructureadaptersApplicationuse cases + portsDomainentities + rulesreferences nothingwiring onlyimplements ports
Every reference points toward Domain. Desktop may reference Infrastructure only to wire adapters to ports in the composition root.
src/
  ConfigTool.Domain/            // no project references at all
  ConfigTool.Application/       // → Domain
  ConfigTool.Infrastructure/    // → Application (implements its ports)
  ConfigTool.Desktop/           // WPF/MVVM → Application; composition root also → Infrastructure
tests/
  ConfigTool.Domain.Tests/      // pure unit tests, no mocks needed
  ConfigTool.Application.Tests/ // use cases with in-memory fakes / simulated device
  ConfigTool.ArchitectureTests/ // enforce the dependency rule

Domain — entities and rules, no I/O:

public sealed class DeviceConfiguration
{
    public ConfigId Id { get; }
    public DeviceModel Model { get; }
    private readonly Dictionary<ParameterKey, ParameterValue> _values = new();

    public void Set(ParameterKey key, ParameterValue value)
    {
        var def = Model.Definition(key);   // throws if the model has no such parameter
        def.EnsureInRange(value);          // invariant lives with the data
        _values[key] = value;
    }

    public ValidationResult Validate(IEnumerable<IConfigRule> rules) =>
        ValidationResult.Combine(rules.Select(r => r.Check(this)));
}

Application — one use case and the ports it owns:

public interface IConfigurationRepository { Task<DeviceConfiguration?> Load(ConfigId id); }
public interface IRuleCatalog { IReadOnlyList<IConfigRule> For(DeviceModel model); }
public interface IDeviceGateway { Task Write(DeviceAddress target, ConfigImage image); }

public sealed class DeployConfiguration(
    IConfigurationRepository repo, IRuleCatalog rules, IDeviceGateway device)
{
    public async Task<DeployResult> Handle(DeployRequest req)
    {
        var cfg = await repo.Load(req.ConfigId);
        if (cfg is null) return DeployResult.NotFound(req.ConfigId);

        var check = cfg.Validate(rules.For(cfg.Model));
        if (!check.IsValid) return DeployResult.Invalid(check.Errors);

        await device.Write(req.Target, ConfigImage.From(cfg));
        return DeployResult.Ok();
    }
}

Infrastructure — adapters, swappable per technology:

public sealed class CanOpenDeviceGateway(ICanBus bus) : IDeviceGateway { /* SDO writes… */ }
public sealed class SimulatedDeviceGateway : IDeviceGateway { /* tests, demos, CI */ }
public sealed class JsonFileConfigurationRepository : IConfigurationRepository { /* … */ }
public sealed class YamlRuleCatalog : IRuleCatalog { /* rules as data */ }

Where it is used in practice

Few companies publish “we use Clean Architecture” verbatim. The ideas show up as hexagonal designs, official platform guidance and widely used templates.

  • Google — Android app architecture guide. Recommends UI and data layers plus an optional domain layer of use cases for complex or shared logic — a deliberately softened Clean Architecture that warns against layers you don’t need.[6]
  • Microsoft — “Common web application architectures”. The .NET architecture e-book presents Clean Architecture (with Onion and Hexagonal) as the recommended structure for domain-heavy ASP.NET Core apps; the eShopOnWeb reference app follows it.[7]
  • Netflix Studio Workflows (hexagonal). A studio-production app pulled data from many services over gRPC, JSON API and GraphQL that kept moving. A hexagonal core of entities, repositories and interactors let them swap data sources without touching business logic.[5]
  • AWS Prescriptive Guidance. Uses ports and adapters to keep domain logic independent of AWS services.[8]
  • Jason Taylor and Ardalis templates (.NET). The two most-used starting points and the origin of the 4-layer structure. Ardalis now also ships a minimal, single-project variant organised by vertical slices — a hint where the community is heading.[10][11]
  • Embedded — “Clean Embedded Architecture” (James Grenning, chapter 29 of Martin’s book). Hardware and OS abstraction layers so logic can be tested off-target.

Gains and costs

What it buys

  • Testable core. Rules and use cases run in milliseconds without UI, database or hardware.
  • Replaceable edges. New UI technology, storage, file format or device protocol means a new adapter, not a rewrite.
  • Deferred decisions. Build behaviour before choosing a database or UI framework.
  • Clear placement rules. Less arguing about where code goes; easier onboarding and parallel work.
  • Longevity. Frameworks age faster than business rules; the core outlives them.

What it costs

  • Indirection and boilerplate. Interfaces, DTOs, mappers, handlers — one feature touches 4–6 files across projects.
  • Mapping overhead. Domain ↔ DTO ↔ persistence ↔ view model, often for near-identical shapes.
  • Slower start. Visibly heavier for the first months; pays back only if the system lives and changes.
  • Learning curve. Teams new to it mis-place logic, and the layers exist in name only.
  • Low feature cohesion. One feature is spread across layers — the core of the vertical-slice critique.

Martin himself says full boundaries are often too expensive and suggests partial boundaries where full ones aren’t justified. Pragmatism about layers is consistent with the source.[1]

How it goes wrong

  1. Folders, not boundaries. The four project names exist, but Domain references EF Core and Application references HTTP types. Nothing enforces the rule.
  2. Anaemic domain. Entities are property bags; all logic sits in handlers or services. The centre is empty and the rings only add cost.
  3. ORM entity = domain entity. Persistence concerns (lazy loading, nullable keys, navigation properties) leak into the core.
  4. A use case per CRUD call. Hundreds of GetXByIdQuery handlers that only forward to a repository. Ceremony without behaviour.
  5. Interface for everything. Abstractions with exactly one implementation and no test or variation need behind them.
  6. Template cargo cult. MediatR, CQRS, AutoMapper adopted because the template had them, not because a requirement asked.
  7. The real complexity is elsewhere. In many systems the hard part is the rule model, schema versioning or import/export; neat layering doesn’t address any of it.

Enforcing it

An architecture that isn’t checked decays within months. Good answers to “how is the rule enforced?”:

  • Project or module references that make wrong dependencies fail to compile — the cheapest guard.
  • Architecture tests in CI — NetArchTest[12] or ArchUnitNET (.NET), ArchUnit (Java), dependency-cruiser (TypeScript), import-linter (Python).
  • Review checklist and ADRs recording why each boundary exists.
[Fact]
public void Domain_has_no_outward_dependencies()
{
    var result = Types.InAssembly(typeof(DeviceConfiguration).Assembly)
        .ShouldNot()
        .HaveDependencyOnAny("ConfigTool.Application", "ConfigTool.Infrastructure",
                             "Microsoft.EntityFrameworkCore", "System.Windows")
        .GetResult();

    Assert.True(result.IsSuccessful);
}

Relatives and alternatives

Several of these are complementary rather than competing — a system may need Clean Architecture plus a plugin model plus MVVM.

Option In one line Choose it when…
Classic layered (N-tier) UI → Business → Data; dependencies point down to the database. Mostly forms over data, small team, short life.
Hexagonal / Ports & Adapters Core + ports; no prescribed inner rings. You want the key benefit with less ceremony. Often the pragmatic form of Clean.
Onion Domain model → domain services → application services → infrastructure. Effectively Clean with DDD vocabulary.
Vertical Slice Organise by feature; each slice owns request-to-storage code.[9] Many independent features of uneven complexity. Optimises change locality.
Modular monolith Independent modules with explicit public APIs in one deployable. Several sub-domains or teams. Combines well with Clean inside each module.
Plugin / Microkernel Small host + plugins loaded behind contracts. New device families, formats or variants must be added without rebuilding.
Rule engine / Model-driven Behaviour lives in data or models (rules, constraints, metamodel, DSL). Product configurators, AUTOSAR-like tools, experts maintaining rules.
Pipes & filters Import → validate → transform → generate as composable stages. The system is essentially a compiler or generator.
Event sourcing Store every change as an event; state is derived. Full audit trail and undo/redo are hard requirements.
MVVM / MVU UI-level presentation pattern. Any rich UI — lives inside the outer rings; complementary.

Origins

Robert C. Martin published it in a 2012 blog post[1] and expanded it in Clean Architecture: A Craftsman’s Guide to Software Structure and Design (2017). He presented it as a synthesis of earlier ideas:

Year Idea Contribution
1992 Entity–Control–Boundary (Ivar Jacobson) Separate use-case control logic from boundary objects and entities.
2005 Hexagonal / Ports & Adapters (Alistair Cockburn)[2] The core talks to the world only through ports, so tests can drive it as easily as a UI.
2008 Onion Architecture (Jeffrey Palermo)[3] Concentric layers with the domain model at the centre, infrastructure outside.
2012 Clean Architecture (Robert C. Martin) Unifies the above under one dependency rule and names the rings.

Key takeaways

  1. Judge it by the dependency rule, not by the number of projects: inner code must never name outer code.
  2. It pays off when the value is in the rules and the system must live long; it is overkill for forms over data.
  3. Its key benefit is replaceable edges and a testable core; its cost is indirection and feature code spread across layers.
  4. Enforce the rule with project references and architecture tests, or it decays within months.
  5. Decide separately where rules live (code or data) and how the system is extended; Clean Architecture answers neither.

Sources

  1. The Clean Architecture · Robert C. Martin, 2012; expanded in the book Clean Architecture (2017)Primaryblog.cleancoder.com
  2. Hexagonal Architecture · Alistair Cockburn, 2005Primaryalistair.cockburn.us
  3. The Onion Architecture, part 1 · Jeffrey Palermo, 2008Primaryjeffreypalermo.com
  4. Understanding Clean Architecture: A Beginner's Guide · M. B. Khalid, 2024 — typical 4-layer .NET framingSecondarydev.to
  5. Ready for changes with Hexagonal Architecture · Netflix TechBlogPrimarynetflixtechblog.com
  6. Domain layer · Android DevelopersPrimarydeveloper.android.com
  7. Common web application architectures · Microsoft LearnPrimarylearn.microsoft.com
  8. Hexagonal architectures: adapting to change · AWS Prescriptive GuidancePrimarydocs.aws.amazon.com
  9. Vertical Slice Architecture · Jimmy BogardPrimaryjimmybogard.com
  10. jasontaylordev/CleanArchitectureReferencegithub.com
  11. ardalis/CleanArchitectureReferencegithub.com
  12. NetArchTestReferencegithub.com
↑ ↓ to moveEnter to openFull-text search by Pagefind