クリーンアーキテクチャは、Robert C. Martin(通称アンクル・ボブ)が2012年8月13日のブログ記事「The Clean Architecture」で示した設計の考え方です。有名な同心円の図が一人歩きしがちですが、原典の核は「ソースコードの依存は内側にだけ向ける」という1つのルールにあります。本記事では原典の記述を確認しながら4つの層の責務を整理し、TypeScriptの実装例で依存の向きを確かめます。そのうえで「クリーンアーキテクチャはいらない」と言われる理由と、入れる・見送るの判断基準を示す構成です。
まとめ:クリーンアーキテクチャの要点と採用判断
- 定義:業務ルールをUI・DB・フレームワークから独立させる設計。守るべき規則は「依存性ルール(The Dependency Rule)」ただ1つ
- 4層:エンティティ/ユースケース/インターフェースアダプター/フレームワークとドライバー。ただし原典は「4つである必要はない」と明言している
- DIPとの関係:制御の流れが外向きになる箇所でも依存を内向きに保つために、依存性逆転の原則(DIP)を使う
- いらない場面:業務ルールが薄いCRUD中心のアプリ、使い捨てのプロトタイプ。境界ごとに増えるインターフェースとデータ構造の保守コストを回収できない
- 入れる場面:業務ルールが複雑で数年単位で使い続ける、DBや外部サービスを差し替える見込みがある、UIを通さずに業務ルールをテストしたい
以下、原典の定義から順に根拠を示します。
クリーンアーキテクチャの定義と2012年の原典が掲げる5つの性質
提唱者Robert C. Martinのブログ記事(2012年)と書籍版(2017年)
出発点はMartinのブログ「The Clean Code Blog」に2012年8月13日付で掲載された記事です。記事の冒頭で、先行する5つのアーキテクチャを挙げています。Alistair Cockburnのヘキサゴナルアーキテクチャ(Ports and Adapters)、Jeffrey Palermoのオニオンアーキテクチャ、Martin自身のScreaming Architecture、James CoplienとTrygve ReenskaugのDCI、Ivar JacobsonのBCEです。これらは細部こそ違うものの「関心の分離」という同じ目的を持ち、どれも業務ルールの層とインターフェースの層を分けている。その共通項を1枚の図に統合したのがクリーンアーキテクチャだ、というのが原典の位置づけです。
書籍版『Clean Architecture: A Craftsman’s Guide to Software Structure and Design』は2017年9月にPearsonから刊行されました(全34章)。日本語版『Clean Architecture 達人に学ぶソフトウェアの構造と設計』(角征典・高木正弘訳)は2018年7月27日にKADOKAWAから出ています。2026年7月にはZEN大学出版会の発行・KADOKAWAの発売で再刊されました(B5変型・352ページ、ISBN 978-4-04-901203-3、定価3,960円)。
原典が挙げる5つの性質:4つの独立とテスト容易性
原典は、この系統のアーキテクチャで作ったシステムが次の性質を持つとしています。
- フレームワークからの独立:フレームワークを道具として使い、その制約にシステムを押し込めない
- テスト可能:UI・DB・Webサーバーなど外部の要素なしで業務ルールをテストできる
- UIからの独立:Web UIをコンソールUIに替えても業務ルールは変わらない
- DBからの独立:OracleやSQL ServerをMongoDBなどに替えられる
- 外部のあらゆるものからの独立:業務ルールは外の世界について何も知らない
実務で最も効くのは2つ目のテスト容易性です。DBを本当に入れ替えるプロジェクトは多くありませんが、DBなしで業務ルールのテストを回せる利点は初日から得られます。
依存性ルール:ソースコードの依存を内側だけに向ける規則
原典は依存性ルールを「ソースコードの依存は内側にしか向けられない」と定義しています。内側の円は外側の円について何も知ってはならず、外側で宣言した関数・クラス・変数の名前を内側のコードに書くことも禁じられます。名前だけでなくデータ形式も対象で、外側のフレームワークが生成する形式を内側で使ってもいけません。
制御の流れと依存の向きが逆になる境界と依存性逆転の原則
ユースケースが処理結果を画面へ返す場面では、制御はユースケース(内側)からプレゼンター(外側)へ流れます。そのまま呼び出せば内側が外側の名前を知ることになり、ルール違反です。原典はこれを依存性逆転の原則(DIP)で解決します。ユースケース側に「Use Case Output Port」というインターフェースを置き、外側のプレゼンターがそれを実装する。こうすると制御は外へ流れても、ソースコードの依存は内側のインターフェースへ向いたままになります。DIとDIPの違いは依存性の注入(DI)と依存性逆転の原則(DIP)の違いで詳しく扱っています。
境界を越えるデータの形:単純なデータ構造とRowStructureの回避
境界を渡すデータは、単純な構造体やDTO、関数の引数といった「孤立した単純なデータ構造」にします。原典はエンティティそのものやDBの行を渡すことを明確に避けるよう書いています。DBフレームワークが返す行の形(原典の例ではRowStructure)を内側へ渡せば、内側が外側の形式を知ってしまうからです。データは常に「内側の円にとって最も扱いやすい形」で渡します。
4つの同心円の責務と「4層でなくてよい」という原典の但し書き
| 層(内側から) | 置くもの | 変わるきっかけ |
|---|---|---|
| エンティティ | 企業全体で共有する業務ルール | 業務そのものの変更 |
| ユースケース | アプリケーション固有の業務ルール | アプリの操作仕様の変更 |
| インターフェースアダプター | コントローラー・プレゼンター・リポジトリ実装 | 画面やDBとのデータ変換の変更 |
| フレームワークとドライバー | DB・Webフレームワーク・外部サービス | 技術選定やバージョンアップ |
SQLはすべてインターフェースアダプター層、それもDBを扱う部分に閉じ込める、と原典は書いています。MVCのコントローラー・プレゼンター・ビューもこの層に入ります。最も外側の層に書くのは、内側とつなぐための接着コード程度です。
層の数を規則としない「Only Four Circles?」の但し書き
原典には「Only Four Circles?(円は4つだけか)」という節があり、答えは「No」です。円は模式的なもので、4つより多く必要になることもあり、常に4つでなければならないという規則はないと明記されています。変わらないのは依存性ルールだけです。「4層に分けていないからクリーンアーキテクチャではない」という議論は、原典に照らすと成り立ちません。
エンティティとユースケースの線引き
エンティティは「企業全体の業務ルール」で、複数のアプリケーションから使われうるものです。企業ではなく単一のアプリを書いている場合は、そのアプリの業務オブジェクトがエンティティになるとされています。ページ遷移やセキュリティの変更でエンティティが変わってはいけません。ユースケースは「このアプリで何をするか」の手順で、エンティティとのデータの出し入れを指揮します。注文の合計金額の計算はエンティティ、注文を確定して結果を通知する手順はユースケース、と分けると判断しやすくなります。
TypeScriptの実装例で見る依存の向き(注文確定ユースケース)
注文を確定し、合計金額を出力ポートへ渡すユースケースを最小構成で書きます。以下のコードはTypeScript 7.0.2のstrictモードで型チェックを通し、Node.js v26.5.0の組み込みテストランナー(node:test)でテスト2件の成功を確認しています。
内側のエンティティとユースケース:import先は内側だけ
// src/entities/order.ts
// 最内側:業務ルールだけを持ち、import は1行も無い
export type OrderItem = { price: number; qty: number };
export class Order {
private confirmed: boolean;
constructor(readonly id: string, readonly items: OrderItem[], confirmed = false) {
this.confirmed = confirmed;
}
total(): number {
return this.items.reduce((sum, i) => sum + i.price * i.qty, 0);
}
confirm(): void {
if (this.items.length === 0) throw new Error("明細が空の注文は確定できない");
this.confirmed = true;
}
isConfirmed(): boolean {
return this.confirmed;
}
}
// src/usecases/confirm-order.ts
import { Order } from "../entities/order";
// 内側が定義し、外側が実装するインターフェース
export interface OrderRepository {
findById(id: string): Promise<Order | null>;
save(order: Order): Promise<void>;
}
export type ConfirmOrderResult = { orderId: string; total: number };
export interface ConfirmOrderOutputPort {
present(result: ConfirmOrderResult): void;
}
export class ConfirmOrder {
constructor(
private readonly repo: OrderRepository,
private readonly output: ConfirmOrderOutputPort,
) {}
async execute(orderId: string): Promise<void> {
const order = await this.repo.findById(orderId);
if (!order) throw new Error(`注文 ${orderId} が見つからない`);
order.confirm();
await this.repo.save(order);
this.output.present({ orderId: order.id, total: order.total() });
}
}
entities/order.ts にはimport文が1行もありません。usecases/confirm-order.ts がimportするのもエンティティだけです。リポジトリと出力ポートはユースケース側がインターフェースとして定義し、実装は外側に任せます。
外側のアダプター:内側インターフェースの実装とDB行の変換
// src/adapters/sql-order-repository.ts
import { Order } from "../entities/order";
import type { OrderRepository } from "../usecases/confirm-order";
// DBの行の形はこのファイルの外へ出さない
type OrderRow = { id: string; items_json: string; confirmed: 0 | 1 };
export interface SqlClient {
get(sql: string, ...params: unknown[]): Promise<OrderRow | undefined>;
run(sql: string, ...params: unknown[]): Promise<void>;
}
export class SqlOrderRepository implements OrderRepository {
constructor(private readonly db: SqlClient) {}
async findById(id: string): Promise<Order | null> {
const row = await this.db.get("SELECT id, items_json, confirmed FROM orders WHERE id = ?", id);
return row ? new Order(row.id, JSON.parse(row.items_json), row.confirmed === 1) : null;
}
async save(order: Order): Promise<void> {
await this.db.run("UPDATE orders SET confirmed = ? WHERE id = ?", order.isConfirmed() ? 1 : 0, order.id);
}
}
DBの行の型 OrderRow はこのファイルの中だけで使い、ユースケースへは Order に変換してから返します。前の章で見た「DBの行を内側へ渡さない」の実装です。依存の矢印はアダプターからユースケースへ向き、逆はありません。リポジトリが返す Order は内側の円で宣言された型なので、アダプターがそれを知っていても依存性ルールには反しません。一方、画面側へ渡す結果は、原典の「エンティティを渡さない」に従って単純なデータ構造 ConfirmOrderResult にしています。
テスト:メモリ上のリポジトリによるDBなし検証
// src/confirm-order.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { Order } from "./entities/order";
import { ConfirmOrder, type ConfirmOrderResult, type OrderRepository } from "./usecases/confirm-order";
class InMemoryOrderRepository implements OrderRepository {
readonly store = new Map<string, Order>();
async findById(id: string) { return this.store.get(id) ?? null; }
async save(order: Order) { this.store.set(order.id, order); }
}
test("確定した注文の合計が出力ポートへ渡る", async () => {
const repo = new InMemoryOrderRepository();
repo.store.set("A-1", new Order("A-1", [{ price: 1200, qty: 2 }, { price: 300, qty: 1 }]));
let received: ConfirmOrderResult | undefined;
await new ConfirmOrder(repo, { present: (r) => { received = r; } }).execute("A-1");
assert.deepEqual(received, { orderId: "A-1", total: 2700 });
assert.equal(repo.store.get("A-1")?.isConfirmed(), true);
});
test("明細が空なら確定せずに例外", async () => {
const repo = new InMemoryOrderRepository();
repo.store.set("B-1", new Order("B-1", []));
await assert.rejects(new ConfirmOrder(repo, { present: () => {} }).execute("B-1"));
assert.equal(repo.store.get("B-1")?.isConfirmed(), false);
});
DBもWebサーバーも起動せずに、確定処理と「明細が空なら確定しない」という業務ルールを検証できます。原典が挙げた「テスト可能」の中身はこれです。モックやスタブの使い分けはモックとは?スタブとの違い・テストダブル5分類と単体テストでの使い分けを参照してください。
依存の向きはレビューで目視するより機械で止める方が確実です。上の構成なら、内側から外側のディレクトリをimportしていないかを grep -rnE "from \"\.\./(adapters|frameworks)" src/entities src/usecases で検査できます(該当なしで終了コード1になるため、CIでは先頭に ! を付けて違反時だけ失敗させます)。この正規表現は ../../adapters のような深い相対パスや単引用符のimportを拾わないので、本格的にはTypeScriptならdependency-cruiser、JavaならArchUnit、Pythonならimport-linterのような依存検査ツールをCIに組み込みます。
クリーンアーキテクチャがいらない場面と、入れるべき場面の判断基準
「いらない」と言われる理由は境界ごとに増える保守コスト
依存性ルールを厳密に守ると、境界を1つ越えるたびにインターフェースと受け渡し用のデータ構造が増えます。上の例でも、注文確定という1つの操作のために OrderRepository と ConfirmOrderOutputPort の2つのインターフェースを定義しました。画面の入力をほぼそのままDBに保存するCRUD中心のアプリでは、守るべき業務ルールがほとんどありません。インターフェースとDTOの詰め替えだけが増え、変更のたびに触るファイル数が増える。「いらない」という声の多くはこの状況から出ています。
原著自身が扱う妥協案:部分的な境界(Partial Boundaries)
書籍版は、全層を厳密に分けることを前提にしていません。第24章の章題は「Partial Boundaries(部分的な境界)」で、完全な境界を作るコストが見合わない場合の妥協案を扱っています。続く第25章は「Layers and Boundaries」です。提唱者自身も、全部に境界を引くことを求めてはいません。必要になるまで作らないという考え方はYAGNI原則とも一致します。
採用と見送りを分ける条件
| 条件 | 判断 |
|---|---|
| 業務ルールが複雑で、数年単位で改修が続く | 採用する |
| DB・決済・外部APIを差し替える見込みがある | 採用する |
| UIを通さず業務ルールを高速にテストしたい | 採用する |
| 画面とテーブルがほぼ1対1のCRUD管理画面 | 見送る |
| 検証目的で数か月以内に捨てるプロトタイプ | 見送る |
| 業務ルールは一部だけ複雑 | その部分だけ境界を引く |
判断に迷ったら、ユースケース層に書くべき「業務の判断」が本当にあるかを数えてください。ユースケースのほとんどが「リポジトリを呼んで結果を返すだけ」になりそうなら、その規模ではフレームワーク標準の構成で十分です。逆に、割引・在庫引当・与信のような判断がコントローラーやORMのモデルに散らばり始めたら、境界を引くタイミングです。
Android公式ガイドの「ドメイン層」は任意で、クリーンアーキテクチャとは別物
Androidの公式アーキテクチャガイドは、UIレイヤーとデータレイヤーの間に置くドメインレイヤーを「任意のレイヤー(optional layer)」としています。複雑な業務ロジックや、複数のViewModelで再利用するロジックがある場合に限って使うよう書かれています。同じページには、「domain layer」という用語はクリーンアーキテクチャなど他のアーキテクチャでも使われるが意味が異なるので混同しないように、という注記もあります。AndroidアプリでUseCaseクラスを置くことと、クリーンアーキテクチャを採用することは同じではありません。
図をそのままディレクトリにする失敗と、コンポーネント単位で切る代案
よくある失敗は、同心円の図をそのままトップレベルのディレクトリ構成(entities/usecases/adapters/frameworks)にしてしまうことです。ディレクトリを分けても、どこからでもimportできる状態なら依存性ルールは守られません。コードを開いて見えるのが技術的な層の名前だけになり、そのシステムが何をするものなのかも伝わりにくくなります。これは原典が参照しているScreaming Architectureが問題にした点でもあります。前章の実装例は依存の向きを見せるための最小構成なのでこの形にしていますが、実案件では注文・在庫のような業務の単位でディレクトリを切り、その内側で層を分けます。
書籍版の最終章「The Missing Chapter」は、Simon Brownが自身のモジュラモノリス論を寄稿したものです(本人のサイトで、2016年のブログ記事の一版が同章として掲載されたと説明しています)。Brownはレイヤー単位・機能単位・ポートとアダプターの各方式を比べ、レイヤーごとにパッケージを分けると層をまたいで参照させるためにインターフェースをpublicにせざるを得ない点を指摘します。一方で、機能単位(package by feature)へ単純に切り替えることも最適ではないとしています。提案は「package by component」で、UIは外に残したまま、業務ロジックと永続化をコンポーネント単位でまとめ、外部に公開する口を絞る方式です。縦に切る発想はVertical Slice Architectureやモジュラモノリスにも通じます。
src/
order/ # 注文コンポーネント(外部へ公開するのは index.ts だけ)
index.ts
entities/
usecases/
adapters/
inventory/ # 在庫コンポーネント
index.ts
...
web/ # UI(HTTPハンドラー)は外に残す
コンポーネントの外からは index.ts 経由でしか参照させないと決め、依存検査ツールで order/adapters などへの直接importを禁止すれば、ディレクトリ構成と依存性ルールを両立できます。
SOLID原則・DDD・ヘキサゴナル・オニオンとの関係の整理
SOLID原則:書籍版の第7〜11章が土台
書籍版は第3部の第7〜11章でSRP・OCP・LSP・ISP・DIPの5原則を1章ずつ扱い、その後にコンポーネントの原則、アーキテクチャの章へ進む構成です。クリーンアーキテクチャに最も直結するのはDIPで、前述のとおり境界の越え方そのものを決めています。SRPは「変更理由が同じものをまとめる」原則で、層の分け方の判断基準になります。詳しくはSRP(単一責任の原則)とはで解説しています。
DDDとの違い:モデルの作り方と依存の向きの決め方
ドメイン駆動設計(DDD)は、Eric Evansが2003年の著書『Domain-Driven Design』で体系化した、業務の知識をどうモデルにするか(ユビキタス言語・集約・境界づけられたコンテキスト)を扱う設計手法です。クリーンアーキテクチャは、できあがった業務ルールを技術的な詳細からどう切り離すか、つまり依存の向きを扱います。答える問いが違うので競合しません。Microsoftのガイドも、DIPとDDDの原則に従うアプリは似た構成に行き着くと説明しており、DDDで作ったモデルをエンティティ層に置く組み合わせは自然です。DDDの用語はドメイン駆動設計(DDD)とはで整理しています。
ヘキサゴナル・オニオンとの違い:同じ系統の名称の変遷
| 名称 | 提唱者・年 | 図の描き方 |
|---|---|---|
| ヘキサゴナル(Ports and Adapters) | Alistair Cockburn・2005年 | 内側と外側の2区分とポート |
| オニオン | Jeffrey Palermo・2008年 | ドメインを中心にした同心円 |
| クリーン | Robert C. Martin・2012年 | 4つの同心円と依存性ルール |
MicrosoftのASP.NET Core向けガイド「Common web application architectures」は、DIPとDDDの原則に従うアプリは似た構成に行き着き、その構成がヘキサゴナル、Ports and Adapters、オニオン、クリーンと名前を変えて呼ばれてきたと説明しています。同ガイドは以降この構成を「Clean Architecture」と呼び、参照実装のeShopOnWebもこの方式でプロジェクトを分けています。3つの違いは主に図の描き方と用語で、業務ルールを中心に置き依存を内側へ向ける点は共通です。個別の特徴はヘキサゴナルアーキテクチャとはとオニオンアーキテクチャとはで比較しています。読み取りと書き込みでモデルを分けるCQRSは、ユースケース層の中の分け方として組み合わせられます。
よくある質問
クリーンアーキテクチャはいらないのですか?
規模と業務ルールの量によります。画面とテーブルがほぼ1対1で、業務の判断がほとんどないCRUD中心のアプリでは、境界ごとに増えるインターフェースとDTOの保守コストに見合いません。数か月で捨てるプロトタイプも同様です。一方、業務ルールが複雑で長く改修が続くシステムや、DB・外部サービスを差し替える見込みがあるシステムでは効果が出ます。書籍版も第24章で部分的な境界という妥協案を扱っており、全層を厳密に分けることを前提にしていません。
クリーンアーキテクチャの4つの層とは何ですか?
内側から、企業全体の業務ルールを置く「エンティティ」、アプリ固有の業務ルールを置く「ユースケース」、画面やDBとのデータ変換を担う「インターフェースアダプター」、DBやWebフレームワークそのものである「フレームワークとドライバー」です。ただし原典は、円は模式的なもので4つである必要はないと明記しています。守るべきなのは層の数ではなく、ソースコードの依存を内側にだけ向ける依存性ルールです。
クリーンアーキテクチャとDDDの違いは何ですか?
DDDは業務知識をどうモデル化するかを扱う設計手法で、ユビキタス言語や集約、境界づけられたコンテキストといった概念を持ちます。クリーンアーキテクチャは、そのモデルをUI・DB・フレームワークからどう独立させるか、つまり依存の向きを扱います。対象とする問いが違うため対立せず、DDDで設計したドメインモデルをエンティティ層に置く形で併用できます。
クリーンアーキテクチャのメリットとデメリットは何ですか?
最大のメリットは、DBやWebサーバーを起動せずに業務ルールをテストできることです。フレームワークのバージョンアップやDBの変更の影響が外側の層で止まる点も利点になります。デメリットは、境界ごとにインターフェースとデータ構造が増え、コード量と変更時に触るファイル数が増えることです。業務ルールが薄いアプリでは、このデメリットがメリットを上回ります。
クリーンアーキテクチャはどの本や資料から学べばよいですか?
まず無料で読めるMartinの2012年8月13日のブログ記事「The Clean Architecture」で、依存性ルールと4つの円の定義を押さえるのが近道です。体系的に学ぶなら書籍『Clean Architecture 達人に学ぶソフトウェアの構造と設計』で、2026年7月にZEN大学出版会の発行・KADOKAWAの発売で再刊されています。SOLID原則(第7〜11章)から読むと、後半のアーキテクチャの章が理解しやすくなります。