Clean Architecture (Uncle Bob)
Idea (one-liner)
Structure your app into concentric rings where inner rings (entities / use-cases) contain enterprise/business rules and outer rings contain frameworks and UI. Dependencies always point inward.
Layers (outer → inner)
- Infrastructure (EF, DB, web frameworks, email)
- Interface Adapters (controllers, presenters, gateways) — translate to/from domain
- Use Cases / Application Services (interactors)
- Entities / Domain (business rules, value objects, aggregates)
Key rules
- Inner layers are framework-agnostic.
- Use dependency inversion: inner layer defines interfaces; outer layers implement them.
- Controllers and UI are at the outermost layer.
Typical folder structure (C#)
Tiny C# example (Clean)
Domain (inner):
Application (use-case):
Infrastructure (outer):
API wiring (composition root):
Onion Architecture (Jeffrey Palermo)
Idea (one-liner)
Very similar to Clean: domain model in the center, application services around it, and infrastructure at the outermost ring. Emphasis on domain-centric design and keeping domain independent of data concerns.
Layers
- Domain (entities, value objects, interfaces)
- Application Services (business use-cases)
- Infrastructure (implementations)
- Presentation (UI, API) — sometimes shown outside infrastructure
Key rules
- Dependencies point toward the domain core.
- Domain defines repository interfaces; implementations reside in Infrastructure.
- Typically slightly simpler conceptual mapping than Clean but equivalent in practice.
Folder structure
C# example differences
Code examples are essentially identical to Clean — both use dependency inversion and keep domain stable. Onion often expresses the idea with an emphasis on domain-first development.
Hexagonal Architecture (Ports & Adapters — Alistair Cockburn)
Idea (one-liner)
Design your app around the core domain + application logic and expose ports (interfaces). External systems connect via adapters (implementations). The core is decoupled from I/O and UIs.
Structure
-
Domain / Application core (inside)
-
Defines ports (interfaces): e.g.,
IOrderRepository,IEmailSender
-
-
Adapters (outside)
- Primary adapters: UI / CLI / REST controllers (call the core)
- Secondary adapters: DB, message buses, email (implement ports)
Key rules
- The core depends only on ports (interfaces).
- Adapters implement ports and are swappable (DB → Mongo/SQL without changing core).
- Great for systems where many different input/output mechanisms exist.
Folder structure
C# example (port/adapter style)
Core (port definition):
Adapter (EF):
Controller (primary adapter):
Visual / conceptual differences (short)
- Clean vs Onion: almost same in practice. Clean uses explicit layers named “entities/use cases/controllers”, Onion emphasizes domain core and rings. Both use dependency inversion.
- Hexagonal: emphasizes ports & adapters — think of core exposing ports and everything else being adapters. It’s focused on input/output boundaries rather than strict concentric layers.
When to choose which
- Small/Medium app / DDD approach — Onion or Clean both work well; pick the vocabulary your team prefers.
- Many different input/output channels (CLI, REST, event bus, scheduled jobs) — Hexagonal is a natural fit because it models ports/adapters explicitly.
- Strict separation and testability — All three help, but Hexagonal makes adapter-swapping explicit which is great for testing with fakes.
Mapping to DDD concepts (your earlier list)
- Aggregates: live in Domain (core). Repositories in Application or Domain define interfaces that operate on Aggregates.
- Repositories: interface (port) in Domain or Application layer; implementation in Infrastructure/Adapters.
- Domain Events: defined in Domain; published from Aggregates; handled by handlers in Application or Infrastructure (depending on side-effects).
- DTOs: used at outer layers (API/presentation) to transport data in/out. Map to/from domain objects in Interface Adapters / Controllers.
- Bounded Contexts (BC): each BC can be its own Clean/Onion/Hexagonal application — maintain separate domain models and services; integration via well-defined anti-corruption layers or translation adapters.
Concrete interview-ready comparisons (table)
| Concern | Clean | Onion | Hexagonal |
|---|---|---|---|
| Core idea | Inner business rules, outer frameworks | Domain-first rings | Ports (interfaces) and adapters |
| Dependencies | Inward, DI + interfaces | Inward, domain-centric | Core depends only on ports |
| Where repos live | Interface in inner, impl outer | Interface inner, impl outer | Port in core, adapter implements port |
| Best for | General large apps | Domain-driven design | Systems with many I/O channels |
| Testability | Excellent | Excellent | Excellent + explicit adapter fakes |
| Typical complexity | Medium | Medium | Slightly higher modeling upfront |
Extra: short, realistic example — where to put code
-
Domain (
MyApp.Domain) - Order, OrderItem, Money (value object)
- OrderPlacedEvent
- IOrderRepository (or Ports/OrderRepository in Hexagonal)
-
Application (
MyApp.Application) - Use-cases: PlaceOrder, CancelOrder
- Domain event dispatching orchestration (or decoupled into a DomainEvents mechanism)
-
Infrastructure (
MyApp.Infrastructure) - EfOrderRepository implements IOrderRepository
- EmailSender implements IEmailSender
-
API / UI (
MyApp.Api)-
Controllers, DTOs, mappers to domain
-
-
Composition Root
-
Wire DI: register repositories, event bus, mapper, use-cases
-
Practical tips / gotchas
- Don't put EF attributes in domain objects if you want a pure domain model. If you prefer convenience, accept slight coupling.
- Keep domain logic inside domain (rich model) and avoid anemic models that move rules into services.
- Define repository interfaces in the domain (or core) so implementations can be swapped.
- Treat DTOs as translation objects — no business logic in DTOs.
- Bounded Contexts: prefer separate projects/apps when models diverge significantly — use anti-corruption layers for translations.
- Start pragmatic: very small projects may be over-architected if you create many projects — aim for clarity and testability rather than perfect structure.