👋 はじめに
クリーンアーキテクチャの図を見ると、
「円がいっぱいあって、結局どうすればいいの…?」
となった経験はありませんか?
実際、現場のエンジニアでも
- 何が目的なのかよく分からない
- 層は知ってるけど実務にどう落とすの?
- 小規模案件に導入すると途端に“過剰設計感”が出る
といった悩みが多いアーキテクチャです。
この記事では クリーンアーキテクチャの本質だけにフォーカスし、
最低限これだけ知っていればOK というエッセンスだけをカジュアルに整理しました。
TypeScript の極小サンプルコードもあるので、実装イメージまで掴めます。
🎯 記事のゴール
この記事を読み終えると次のことができるようになります。
- クリーンアーキテクチャを 一言で説明
- 4 層の役割(Entities / UseCases / InterfaceAdapters / Frameworks)が理解できる
- 自プロジェクトにどう当てはめるかイメージできる
- 最小構成でクリーンアーキ風の設計が再現できる
🧭 クリーンアーキテクチャを一言でいうと?
Uncle Bob(Robert C. Martin)が提唱した、
「ビジネスロジックを外界(フレームワーク・DB)から守るための設計」
です。
もっと雑に言うと…
“技術の寿命にビジネスロジックを巻き込ませないための仕組み”
この考え方を支える鉄則が Dependency Rule(依存の向き):
依存は内側(ビジネスルール)へ向く
- Express は UseCase を知らない
- DB は Entity を知らない
- 内側は外の詳細を import しない
という構造を作るだけで“クリーンアーキっぽさ”が生まれます。
🧩 クリーンアーキの4層(図を文字だけで超シンプル解説)
① Entities(エンティティ)
ビジネスルールの“原則”部分。
技術要素を知らないのが理想。
② Use Cases(ユースケース)
操作手順(アプリ固有のシナリオ)。
DB も UI も知らず、抽象インターフェースに依存する。
③ Interface Adapters(アダプタ)
Webフレームワーク・DB・外部APIを
ユースケースが扱いやすい形に変換する層。
④ Frameworks & Drivers
Express / Spring / DB / 外部API / メッセージキューなどすべてここ。
最も外側で技術詳細の塊。
💡 他アーキテクチャとの関係
Layered / Hexagonal / Onion と本質は共通。
「ドメインを中心に据えて、外側が変わっても壊れない構造をつくる」
という思想に基づいているだけ。
🛠 メリット(実務で効くポイント)
- Express→Fastify のような乗り換えが楽
- UseCase が“純粋なクラス”なのでテスト容易
- 外部API変更をアダプタ側だけで吸収しやすい
- 責務分離が自然にでき、保守性UP
⚠️ 注意点(“罠”ポイント)
特に小規模プロジェクトで発生しがち:
- 層だけ立派で中身が薄くなる
- ただのフォルダ分けになる
- 初手からガチでやりすぎると過剰設計に
→ まずは “依存の向き” だけ意識すれば十分。
🏪 例え話:チェーン店の本部と店舗
- Entities → 本部ルールブック
- UseCases → 業務マニュアル
- Adapters → 店長(店舗事情に合わせて橋渡し)
- Frameworks → 店舗設備(レジ、建物、回線など)
目的:
設備(技術詳細)が変わっても、マニュアル(ユースケース)とルールブック(エンティティ)は変わらない状態。
これがクリーンアーキの思想です。
🧪 TypeScriptでつくる“最小”クリーンアーキ構成
ディレクトリ構成
src/
core/
domain/
usecase/
adapters/
db/
mail/
web/
frameworks/
Entity
export class User {
constructor(public readonly id: string, public readonly email: string) {
if (!email.includes('@')) throw new Error('Invalid email');
}
}
UseCase
export class RegisterUser {
constructor(private readonly repo: UserRepository, private readonly mailer: Mailer) {}
async execute(input: { email: string }) {
const exists = await this.repo.findByEmail(input.email);
if (exists) throw new Error('Already exists');
const user = new User(crypto.randomUUID(), input.email);
await this.repo.save(user);
await this.mailer.sendWelcomeMail(user.email);
}
}
Adapter(DB)
export class InMemoryUserRepository implements UserRepository {
private users: User[] = [];
async findByEmail(email: string) { return this.users.find(u => u.email === email) ?? null; }
async save(user: User) { this.users.push(user); }
}
Framework(Express)
app.post('/users', controller.handle);
🧾 まとめ:エッセンスはこの2つ
- 依存はビジネスルール(内側)へ向ける
- 技術詳細は外側に追い出し、いつでも交換できるようにする
これさえ守れば、図の形やフォルダ構造に厳密でなくても
「実質クリーンアーキ」になります。