Skip to content

Commit 75e49ed

Browse files
committed
Minor update.
1 parent 67935b3 commit 75e49ed

File tree

6 files changed

+417
-312
lines changed

6 files changed

+417
-312
lines changed

_posts/2025-08-01-clean-architecture.md

Lines changed: 142 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ author: steven
1212

1313
*Why the best systems aren’t the flashiest — they’re the cleanest.*
1414

15-
> “Architecture is about intent. It’s about deciding what you want your code to **mean**, not just what you want it to **do**.”
15+
> “Architecture is about intent. It’s about deciding what you want your code to **mean**, not just what you want it to **do**.”
1616
> *Anonymous Software Architect, probably cleaning up your codebase right now.*
1717
1818
---
@@ -21,28 +21,28 @@ author: steven
2121

2222
Let’s unpack the layers from the inside out, using the updated, real-world diagram below.
2323

24-
![Clean Architecture Diagram ](https://github.com/git-steven/git-steven.github.io/raw/master/assets/images/clean-architecture.png)
24+
![Clean Architecture Diagram ](https://github.com/git-steven/git-steven.github.io/raw/master/assets/images/diagram.architecture.clean.drawio.png)
2525

26-
> **Diagram Caption:**
27-
> The concentric rings show dependency direction — all arrows point inward.
26+
> **Diagram Caption:**
27+
> The concentric rings show dependency direction — all arrows point inward.
2828
> The gray wedge is a *novel visualization of Cross-Cutting Concerns*, showing shared utilities like logging, config, and validation that affect every layer but depend on none.
2929
3030
---
3131

3232
## The Problem With “Getting Things Done”
3333
Every developer knows this feeling.
3434

35-
You’re deep in a feature branch, the code is working, the tests are green, and everything feels like progress. Then the next sprint lands — and you need to change *just one thing*.
36-
You open the files and realize:
35+
You’re deep in a feature branch, the code is working, the tests are green, and everything feels like progress. Then the next sprint lands — and you need to change *just one thing*.
36+
You open the files and realize:
3737

38-
- The web controller knows too much about the database.
39-
- The domain object is importing HTTP classes.
40-
- A business rule lives in a utility package called “helpers.”
38+
- The web controller knows too much about the database.
39+
- The domain object is importing HTTP classes.
40+
- A business rule lives in a utility package called “helpers.”
4141
- Changing a single endpoint means touching five different layers that somehow all depend on each other.
4242

4343
Suddenly, that working system feels more like a house of cards.
4444

45-
This is where **Clean Architecture** comes in — the antidote to tight coupling and creeping entropy.
45+
This is where **Clean Architecture** comes in — the antidote to tight coupling and creeping entropy.
4646

4747
---
4848

@@ -54,35 +54,49 @@ Originally popularized by **Robert C. Martin (Uncle Bob)**, Clean Architecture f
5454

5555
> “Dependencies should always point inward — toward the core of your system.”
5656
57-
That simple idea changes everything.
57+
That simple principle—**dependencies flow inward**changes everything.
5858

59-
The innermost part of your application — the **business rules** — should know nothing about frameworks, databases, or user interfaces.
59+
The innermost part of your application — the **business rules** — should know nothing about frameworks, databases, or user interfaces.
6060
Conversely, the outermost part of your system — the controllers, databases, APIs — should depend on the inner rules, never the other way around.
6161

62-
This creates a *cone of stability*: as frameworks, tools, and technologies come and go, your business logic remains untouched.
62+
> This creates a *cone of stability*: as frameworks, tools, and technologies come and go, your business logic remains untouched.
6363
6464
---
6565

66+
## 🧪 Testing Made Natural
6667

67-
### **1. Domain Layer (Entities & Business Rules)**
68+
Clean Architecture creates a testing paradise:
6869

69-
At the very center lies the beating heart of your application:
70-
- **Entities** represent your core business concepts.
70+
- **Domain Layer**: Pure unit tests. No mocks, no frameworks. Lightning fast.
71+
- **Application Layer**: Test use cases by mocking repository/gateway interfaces.
72+
- **Interface Adapters**: Test controllers with mocked use cases.
73+
- **Integration Tests**: Test only the outer ring against real infrastructure.
74+
75+
> The dependency direction means you can test each layer in isolation, building confidence from the inside out.
76+
77+
---
78+
79+
### � Domain Layer (Entities & Business Rules)
80+
81+
At the very center lies the beating heart of your application:
82+
- **Entities** represent your core business concepts.
7183
- **Business Rules** define invariants and logic that must always hold true.
7284

73-
This layer contains **pure code** — no frameworks, no external references, no side effects.
85+
This layer contains **pure code** — no frameworks, no external references, no side effects.
7486
It’s what you would bring with you if the entire tech stack changed overnight.
7587

76-
Example:
88+
Example:
7789
If you’re building a billing system, your *Invoice*, *Customer*, and *PaymentPolicy* entities belong here.
7890

79-
They shouldn’t care if you store data in PostgreSQL or MongoDB.
80-
They shouldn’t care if your API is REST, GraphQL, or gRPC.
91+
They shouldn’t care if you store data in PostgreSQL or MongoDB.
92+
They shouldn’t care if your API is REST, GraphQL, or gRPC.
8193
They care only about business truth.
94+
> "A good test: if you could lift your domain code into a different language or framework with zero changes to the logic itself, you've achieved true independence."
95+
8296

8397
---
8498

85-
### 🟡 **2. Application Layer (Use Cases & Orchestration)**
99+
### 🟡 Application Layer (Use Cases & Orchestration)
86100

87101
Surrounding the domain is the **Application Layer**, sometimes called the *Use Case* or *Interactors* layer.
88102

@@ -91,14 +105,14 @@ This is where **workflows** live — the orchestration of domain rules to achiev
91105
- “Publish Post”
92106
- “Send Welcome Email”
93107

94-
The application layer doesn’t know how to persist data or send emails. It just knows *that it needs to*.
108+
The application layer doesn’t know how to persist data or send emails. It just knows *that it needs to*.
95109
So it calls abstractions — interfaces like `UserRepository` or `EmailGateway` — without knowing who implements them.
96110

97-
That’s dependency inversion in action.
111+
> That’s dependency inversion in action.
98112
99113
---
100114

101-
### 🔴 **3. Interface Adapters**
115+
### 🔴 Interface Adapters
102116

103117
Here, the **outside world** starts to meet your **inner logic**.
104118

@@ -108,15 +122,15 @@ The Interface Adapter layer includes:
108122
- Repository Interfaces (the boundaries between persistence and business logic)
109123
- Auth Contexts and Gateways (like S3 or event buses)
110124

111-
This layer’s job is to **translate**:
112-
- From external formats → into internal structures your use cases can understand.
125+
This layer’s job is to **translate**:
126+
- From external formats → into internal structures your use cases can understand.
113127
- And back again → into formats the world expects.
114128

115-
It’s the bridge between domain purity and framework practicality.
129+
> It’s the bridge between domain purity and framework practicality.
116130
117131
---
118132

119-
### 🔵 **4. Frameworks & Drivers**
133+
### 🔵 Infrastructure
120134

121135
Finally, at the outer ring lies all the infrastructure you *can change* — or at least, you want to *be able to*.
122136

@@ -131,8 +145,9 @@ This includes:
131145

132146
These are essential, but **they should never define your business logic**.
133147

134-
They depend inward, implementing interfaces that the inner layers define.
135-
This is the inversion of control that keeps your system clean — your *core* defines the contracts; your *infrastructure* provides the implementations.
148+
They depend inward, implementing interfaces that the inner layers define.
149+
150+
> This is the inversion of control that keeps your system clean — your *core* defines the contracts; your *infrastructure* provides the implementations.
136151
137152
---
138153

@@ -142,81 +157,140 @@ Most Clean Architecture diagrams draw the four concentric rings and stop there
142157

143158
But in real life, these utilities *permeate everything*.
144159

145-
Your logging system affects controllers and use cases.
146-
Your validation framework interacts with both API models and domain entities.
160+
Your logging system affects controllers and use cases.
161+
Your validation framework interacts with both API models and domain entities.
147162
Your cryptography or hashing utilities might be used anywhere.
148163

149164
So, how do we visualize that?
150165

151-
### 💡 The Solution: The Cross-Cutting Wedge
166+
### 💡 The Solution: The "Cross-Cutting Wedge"
152167

153-
Instead of a fifth ring or a footnote, this diagram projects a **gray wedge** outward from the center — cutting through every layer.
168+
Instead of a fifth ring or a footnote, this diagram projects a **gray wedge**
169+
outward from the center — cutting through every layer.
154170

155171
Inside it sit the typical utilities shared across all levels:
156-
- 📟 **Logging**
157-
- ⚙️ **Utils**
158-
- 🧹 **Validation**
159-
- 🔐 **Crypto**
160-
-**Config**
161-
- 💥 **Exceptions**
162-
163-
This “cross-cutting slice” communicates two powerful truths:
164-
1. These utilities *touch every layer* — they’re universal.
165-
2. But they *don’t depend on any layer* — they’re foundational.
172+
- 📟 **Logging** - observability without coupling
173+
- ⚙️ **Utils** - pure functions with no dependencies
174+
- 🧹 **Validation** - input checking at boundaries
175+
- 🔐 **Crypto** - security primitives
176+
-**Config** - environment-agnostic settings
177+
- 💥 **Exceptions** - standardized error handling
166178

167-
It’s both visually elegant and conceptually accurate — something that most architectural diagrams fail to show clearly.
179+
**Key principle**: These utilities can be *used by* any layer but *depend on* none of them.
180+
They're foundational infrastructure that makes other layers possible without creating coupling.
168181

182+
> This is different from the outer "Infrastructure" layer, which implements external system concerns. Cross-cutting utilities are internal helpers that remain pure and reusable.
169183
---
170184

185+
171186
## 🔀 The Dependency Rule (and Why It’s Sacred)
172187

173188
> **Source code dependencies always point inward.**
174189
175-
- The Domain depends on nothing.
176-
- The Application depends only on the Domain.
177-
- The Interface Adapters depend only on the Application and Domain.
178-
- The Frameworks depend on everyone above.
190+
- The Domain layer depends on nothing.
191+
- The Application layer depends only on the Domain.
192+
- The Interface Adapters layer depend only on the Application and Domain.
193+
- The Infrastructure layer on everyone above.
194+
195+
> That’s why the arrows in the diagram point inward — visually enforcing that rule.
196+
197+
198+
### 🔄 The Magic of Dependency Inversion
199+
200+
The Dependency Rule works through a crucial technique: **Dependency Inversion**.
201+
202+
Instead of the Application layer depending on concrete Infrastructure implementations,
203+
it defines *interfaces* (like `UserRepository` or `PaymentGateway`) and depends on those abstractions.
204+
205+
The Infrastructure layer then *implements* these interfaces, creating a reversal of
206+
the typical dependency direction. The high-level policy (Application) dictates the
207+
contract, and the low-level details (Infrastructure) conform to it.
208+
209+
> This is why you can swap databases or frameworks without touching the core logic.
179210
180-
That’s why the arrows in your diagram point inward — visually enforcing that rule.
211+
---
212+
213+
## ⚠️ Common Pitfalls
214+
215+
**The Framework Sneaks In**
216+
Domain entities accidentally import framework annotations (@Entity, @JsonProperty).
217+
Keep the domain pure - use mapping at the adapter boundary.
218+
219+
**Anemic Domain Models**
220+
All logic ends up in use cases, leaving entities as data bags.
221+
Rich domain models with behavior are key.
222+
223+
**Over-Engineering Small Apps**
224+
A CRUD app with 3 endpoints doesn't need Clean Architecture.
225+
Use judgment - apply when you expect complexity or longevity.
181226

182227
---
183228

184229
## 🧹 A Microservice Example
185230

186231
| Layer | Responsibility | Example Component |
187232
|--------|----------------|-------------------|
188-
| �aude3 Domain | Defines what a `Payment` is and how it behaves | `Payment`, `PaymentPolicy` |
233+
| 🟣 Domain | Defines what a `Payment` is and how it behaves | `Payment`, `PaymentPolicy` |
189234
| 🟡 Application | Orchestrates the workflow | `ProcessPaymentUseCase` |
190-
| 🔴 Interface Adapters | Translates between HTTP & domain | `PaymentController`, `PaymentRequestDTO`, `PaymentRepository` |
191-
| 🔵 Frameworks & Drivers | Implements persistence & integrations | `JpaPaymentRepository`, `KafkaPublisher`, `AuthClient` |
192-
| Cross-Cutting | Provides shared services | `Logger`, `Validator`, `CryptoUtil`, `ExceptionMapper` |
235+
| 🟢 Interface Adapters | Translates between HTTP & domain | `PaymentController`, `PaymentRequestDTO`, `PaymentRepository` |
236+
| 🔵 Infrastructure | Implements persistence & integrations | `JpaPaymentRepository`, `KafkaPublisher`, `AuthClient` |
237+
| Cross-Cutting | Provides shared services | `Logger`, `Validator`, `CryptoUtil`, `ExceptionMapper` |
193238

194-
Each layer does its job — and nothing else.
239+
> Each layer does its job — and nothing else.
195240
196241
---
197242

198243
## 🧠 Why Clean Architecture Matters (Even in 2025)
199244

200-
Because frameworks change, tools change — **your business rules don’t**.
201-
The more tightly coupled your system is to today’s framework, the more painful tomorrow’s rewrite will be.
245+
Because frameworks change, tools change — **your business rules don’t**.
246+
The more tightly coupled a system is to today’s framework, the more painful tomorrow’s rewrite will be.
202247

203248
Clean Architecture buys you:
204-
- **Independence** — swap databases or frameworks with minimal pain.
205-
- **Testability** — mock external dependencies easily.
206-
- **Clarity** — newcomers can understand the flow at a glance.
207-
- **Longevity** — code that still makes sense years later.
249+
- **Independence** — swap databases or frameworks with minimal pain.
250+
- **Testability** — mock external dependencies easily.
251+
- **Clarity** — newcomers can understand the flow at a glance.
252+
- **Longevity** — code that still makes sense years later.
253+
254+
> In short: it’s not about building slower. It’s about building smarter — with an architecture that bends but doesn’t break.
255+
256+
---
257+
258+
## Clean vs other architectures
259+
260+
| Feature | Layered | Hexagonal | Clean |
261+
|----------------|----------|-------|------------|
262+
| Learning Curve | Easy | Moderate | Moderate |
263+
| Initial Setup Time | Fast | Moderate | Moderate |
264+
| Testability | Good | Excellent | Excellent |
265+
| Flexibility | Moderate | High | High |
266+
| Cognitive Load | Low | Medium | Medium |
267+
| Best Use Case | Web apps, APIs, microservices | Multi-protocol integrations | Complex domains |
268+
269+
> Clean Architecture shines when you need separation of concerns and a good long-term architecture.
270+
271+
---
272+
273+
## ✨ Migrating to Clean Architecture
274+
275+
You don't need a rewrite. Start small:
276+
277+
1. **Identify your core domain** - extract business rules from controllers
278+
2. **Create use case classes** - move orchestration logic out of controllers
279+
3. **Define repository interfaces** - abstract data access behind contracts
280+
4. **Move entities to the domain** - strip framework dependencies
281+
5. **Implement adapters** - connect your clean core to existing infrastructure
208282

209-
In short: it’s not about building slower. It’s about building smarter — with an architecture that bends but doesn’t break.
283+
> Focus on new features first; build them cleanly while old code remains. Gradually migrate hot paths. Clean Architecture rewards incremental adoption.
210284
211285
---
212286

213-
## 🗭 Final Thoughts
287+
## 🏁 Final Thoughts
214288

215-
Clean Architecture isn’t dogma — it’s a guide.
289+
Clean Architecture isn’t dogma — it’s a guide.
216290
It means being *intentional* about where code lives, who depends on whom, and how change flows through your system.
217291

218-
The moment you respect the **Dependency Rule**, your codebase starts to breathe again.
219-
Suddenly, frameworks feel optional. Tests become easy. New features slot in naturally.
292+
The moment you respect the **Dependency Rule**, your codebase starts to breathe again.
293+
Suddenly, frameworks feel optional. Tests become easy. New features slot in naturally.
220294
And your architecture — well — feels *clean*.
221295

222296
> **“Code that’s clean today stays alive tomorrow.”**
@@ -225,9 +299,9 @@ If you’re tired of fighting tangled dependencies and fragile frameworks, it’
225299

226300
---
227301

228-
### 🧹 About the Diagram
302+
### ✍️ About the Diagram
229303

230-
This article’s diagram introduces a new visual concept — the **Cross-Cutting Concerns Wedge** — that projects from the core outward, cutting across all layers.
304+
This article’s diagram introduces a new visual concept — the **Cross-Cutting Concerns Wedge** — that projects from the core outward, cutting across all layers.
231305
It shows how utilities like logging, configuration, and validation *touch every layer* but *depend on none* — completing the story Clean Architecture has always wanted to tell.
232306

233307
---

0 commit comments

Comments
 (0)