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
- 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
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.
| 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.
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:
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
- Folders, not boundaries. The four project names exist, but Domain references EF Core and Application references HTTP types. Nothing enforces the rule.
- Anaemic domain. Entities are property bags; all logic sits in handlers or services. The centre is empty and the rings only add cost.
- ORM entity = domain entity. Persistence concerns (lazy loading, nullable keys, navigation properties) leak into the core.
- A use case per CRUD call. Hundreds of
GetXByIdQueryhandlers that only forward to a repository. Ceremony without behaviour. - Interface for everything. Abstractions with exactly one implementation and no test or variation need behind them.
- Template cargo cult. MediatR, CQRS, AutoMapper adopted because the template had them, not because a requirement asked.
- 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
- Judge it by the dependency rule, not by the number of projects: inner code must never name outer code.
- It pays off when the value is in the rules and the system must live long; it is overkill for forms over data.
- Its key benefit is replaceable edges and a testable core; its cost is indirection and feature code spread across layers.
- Enforce the rule with project references and architecture tests, or it decays within months.
- Decide separately where rules live (code or data) and how the system is extended; Clean Architecture answers neither.
Sources
- The Clean Architecture · Robert C. Martin, 2012; expanded in the book Clean Architecture (2017)Primaryblog.cleancoder.com
- Hexagonal Architecture · Alistair Cockburn, 2005Primaryalistair.cockburn.us
- The Onion Architecture, part 1 · Jeffrey Palermo, 2008Primaryjeffreypalermo.com
- Understanding Clean Architecture: A Beginner's Guide · M. B. Khalid, 2024 — typical 4-layer .NET framingSecondarydev.to
- Ready for changes with Hexagonal Architecture · Netflix TechBlogPrimarynetflixtechblog.com
- Domain layer · Android DevelopersPrimarydeveloper.android.com
- Common web application architectures · Microsoft LearnPrimarylearn.microsoft.com
- Hexagonal architectures: adapting to change · AWS Prescriptive GuidancePrimarydocs.aws.amazon.com
- Vertical Slice Architecture · Jimmy BogardPrimaryjimmybogard.com
- jasontaylordev/CleanArchitectureReferencegithub.com
- ardalis/CleanArchitectureReferencegithub.com
- NetArchTestReferencegithub.com