Clean Architecture
Learning Objectives
Describe the concentric layers: entities, use cases, interface adapters, frameworks and drivers.
Apply the dependency rule and use input/output boundaries.
Weigh the trade-offs and common variations.
Prerequisites: Modules 71–72.
Intuition and Definition
Robert C. Martin's Clean Architecture arranges code in concentric circles:
Entities — enterprise-wide business rules.
Use cases — application-specific business rules (interactors).
Interface adapters — controllers, presenters, gateways, mappers.
Frameworks and drivers — web framework, database, UI, devices.
The dependency rule: source-code dependencies point only inward. Inner circles know nothing about outer ones — not their names, types or data formats. Data crosses boundaries as simple structures; outer layers call inner ones through input boundaries, and use cases return results through output boundaries (presenter interfaces).
Why It Matters
The most valuable and stable code (business rules) is protected from the most volatile code (frameworks, UIs, databases), and is testable in isolation.
Syntax and Language Notes
Clean architecture is hexagonal architecture with more explicit rings and a presenter concept; the C++ example below shows the input/output boundary style.
Complete Example
#include <iostream>
#include <string>
// ---- entities ----
struct Battery {
double stateOfHealth; // 0..1
bool needsReplacement() const { return stateOfHealth < 0.7; }
};
// ---- use case with input and output boundaries ----
struct CheckBatteryInput { std::string vin; double soh; }; // request model
struct CheckBatteryOutput { std::string vin; int sohPercent; bool replace; }; // response model
struct CheckBatteryPresenter { virtual ~CheckBatteryPresenter() = default; virtual void present(const CheckBatteryOutput&) = 0; }; // output boundary
class CheckBatteryInteractor { // input boundary implementation
CheckBatteryPresenter& out_;
public:
explicit CheckBatteryInteractor(CheckBatteryPresenter& p) : out_(p) {}
void execute(const CheckBatteryInput& in) {
Battery b{in.soh};
out_.present({in.vin, static_cast<int>(in.soh * 100 + 0.5), b.needsReplacement()});
}
};
// ---- interface adapters: presenters format for a specific delivery ----
struct ConsolePresenter : CheckBatteryPresenter {
void present(const CheckBatteryOutput& o) override {
std::cout <<
…EV-001: battery health 83% -> ok
EV-002: battery health 64% -> REPLACE
{"vin":"EV-003","soh":70,"replace":true}Step-by-Step Walkthrough
The entity holds the rule (health below 70% → replace).
The interactor receives a plain request model, applies the rule, and hands a plain response model to an *output boundary* — it doesn't know whether that becomes console text or JSON.
Presenters (interface adapters) format for their delivery mechanism.
0.695 rounds to 70% for display, but the rule uses the exact value (< 0.7) → replace: presentation and rules are separate concerns.
Common Mistakes
Ceremony overload: request/response models, interactors and presenters for trivial CRUD.
Entities depending on ORM/framework types. Use cases returning framework types (HttpResponse) — breaks the dependency rule.
Treating the four circles as mandatory exactly four projects — it's a principle, not a folder template.
Trade-offs and Alternatives
Clean architecture maximizes independence and testability at the cost of more types and mapping. Many teams apply the dependency rule pragmatically: domain and use cases independent of frameworks, but returning results directly instead of through presenter interfaces.
Real-World Use
Mobile apps (Android "clean" templates), backend services with multiple delivery mechanisms, long-lived enterprise systems, and embedded applications that must run on several hardware platforms.
Practice
Q1. (Easy) State the dependency rule.
Answer: Source-code dependencies may only point inward, toward higher-level policies.
Q2. (Medium) What is an output boundary?
Answer: An interface (e.g. presenter) declared by the use-case layer through which results are delivered outward without the use case knowing the outer layer.
Q3. (Advanced) Why might 0.695 display as 70% but still trigger replacement, and is that a bug?
Answer: Display rounds for humans while the rule uses the precise value. It's a design decision to make explicit: either show more precision near thresholds or define the rule on the rounded value; the point is that presentation must not silently change business rules.
Interview Question
Q1. "Clean architecture vs hexagonal architecture?" Answer: Both protect business logic with dependency inversion and keep frameworks at the edge. Hexagonal talks about a core with ports and adapters (inside/outside). Clean architecture names more rings (entities, use cases, interface adapters, frameworks) and adds explicit input/output boundaries and presenters. In practice they lead to very similar code; the important part is the inward dependency direction.
Quick Quiz
Q1. Innermost circle?
Answer: Entities (enterprise business rules).
Q2. Where do controllers and presenters live?
Answer: Interface adapters.
Q3. What crosses boundaries?
Answer: Simple data structures (request/response models), not entities or framework types.
Summary
Concentric layers with inward-only dependencies; input/output boundaries isolate use cases.
Apply pragmatically — the dependency rule matters more than the number of rings.
Related: Modules 19, 71, 72, 74.