LASSIC Media らしくメディア
クリーンアーキテクチャ入門|ヘキサゴナル・オニオンとの違い
監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)
この記事の結論
- クリーンアーキテクチャの核は、ソースコードの依存を内側の業務の規則へだけ向ける「依存関係のルール」です。
- ヘキサゴナル・オニオンも同じ目的の設計で、違いは内と外の描き方と、中心に何を置くかにあります。
- 小さなアプリには重く、規則を守るには円の数を決めてimportの向きをCIで確かめる手当てが要ります。
※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。
クリーンアーキテクチャは、業務の規則をデータベースや画面、フレームワークから切り離し、ソースコードの依存を内側へだけ向ける設計の考え方です。同心円の図は広く知られていますが、「4つの層に分けること」「フォルダ構成の型」と受け取られ、層の数だけファイルが増えて手間ばかりかかる、という声も聞かれます。
本記事では、システム開発に携わるエンジニアやPMの方に向けて、提唱者であるRobert C. Martinが2012年に公開した原文をもとに、クリーンアーキテクチャの仕組みを整理します。あわせて、元になったヘキサゴナルアーキテクチャとオニオンアーキテクチャとの違い、依存の向きをPythonの短い例で確かめる方法、実務での使いどころ、つまずきやすい点、開発を委託するときに確かめたい点も扱います。
目次
クリーンアーキテクチャとは
Robert C. Martinは、2012年8月13日にブログで公開した「The Clean Architecture」で、それまでに提案されていた設計を並べました。*1 Alistair Cockburnのヘキサゴナルアーキテクチャ、Jeffrey Palermoのオニオンアーキテクチャなどです。Martinは、これらは細部こそ違うもののよく似ており、どれも関心の分離を目的にソフトウェアを層に分けていると書き、それらを1つの考え方にまとめようとしました。
Martinは、こうした設計で作ったシステムが持つ性質として、次の5つを挙げています。
- フレームワークから独立している:フレームワークを道具として使い、その制約にシステムを押し込まない
- テストできる:業務の規則を、画面やデータベース、Webサーバーなしでテストできる
- 画面から独立している:Webの画面をコンソールに替えても、業務の規則は変わらない
- データベースから独立している:OracleやSQL Serverを別の製品に替えられる
- 外部のものから独立している:業務の規則は外の世界について何も知らない
ここでいう業務の規則とは、「注文の金額は0円より大きい」のような、画面やデータベースが何であっても変わらない決まりのことです。業務の言葉とモデルをどう切り出すかは「ドメイン駆動設計とは」で扱っています。クリーンアーキテクチャが扱うのは、そうして切り出した規則を、どの向きの依存で外側とつなぐかです。
依存関係のルールと4つの円
原文の図は同心円で、内側へ進むほど上位の方針、外側ほど具体的な仕組みを表します。この設計を成り立たせる規則が、依存関係のルールです。Martinは「source code dependencies can only point inwards」と書いています。*1 ソースコードの依存は内側へだけ向ける、という意味です。
内側の円のコードは、外側の円で宣言された名前を一切書きません。関数、クラス、変数のどれもです。外側の円で使うデータの形式、特にフレームワークが生成する形式も、内側で使わないようにします。円はそれぞれ次の役割を持ちます。*1
- エンティティ:企業全体で共通の業務の規則。画面の遷移やセキュリティが変わっても影響を受けない
- ユースケース:そのアプリケーション固有の規則。エンティティとのデータのやり取りを取りまとめる
- インターフェースアダプター:ユースケースに都合のよい形と、データベースやWebに都合のよい形を相互に変換する。コントローラーやプレゼンターはここに入り、SQLもこの層に閉じる
- フレームワークとドライバー:データベースやWebフレームワークなどの道具。書くコードは内側とつなぐ最小限にとどめる
Martinは「The Web is a detail. The database is a detail.」と書き、Webもデータベースも細部として外側に置くとしています。*1 円の数は4つと決まっているわけではなく、必要なら増やしてよいとも書いています。変えてはいけないのは、依存が常に内側を向くことです。
境界の越え方と渡すデータ
処理の流れは、外から内へ一方向に進むとは限りません。原文の図の右下では、コントローラーがユースケースを呼び、ユースケースの結果がプレゼンターで画面向けに整えられます。処理はいったん内側へ入り、再び外側へ出ていきます。一方で、ソースコードの依存はどれもユースケースの方を向いています。
この食い違いを解くのが、依存性逆転の原則です。上位の方針のコードは下位の仕組みを直接呼ばず、自分の側で決めたインターフェースを呼ぶ、という原則です。ユースケースはプレゼンターを直接呼ばず、内側の円に置いたインターフェース(原文では Use Case Output Port)を呼び、外側のプレゼンターがそれを実装します。インターフェースの実装を実行時に外から渡す手段は「依存性注入(DI)とは」で扱っています。
境界を越えて渡すデータは、単純なデータ構造にします。*1 関数の引数でも、データ転送用のオブジェクトでもかまいません。避けるのは、エンティティそのものや、データベースのフレームワークが返す行のオブジェクトをそのまま内側へ渡すことです。行のオブジェクトを受け取った時点で、内側のコードが外側の形式を知ることになるからです。データは常に、内側の円に都合のよい形で渡します。
ヘキサゴナル・オニオンとの違い
ヘキサゴナルアーキテクチャは、Alistair Cockburnが2005年9月4日付の技術報告にまとめたもので、別名をポートとアダプター(Ports and Adapters)といいます。*2 狙いは、アプリケーションを利用者、別のプログラム、自動テスト、バッチのどれからでも同じように動かせるようにし、実際の機器やデータベースから切り離して開発とテストができるようにすることです。
Cockburnが問題にしたのは、画面のコードに業務の処理が入り込むことでした。新しい層を作って業務の処理を入れないと約束しても、破られたことを検知する手段が無ければ、数年後にはその層も散らかると書いています。*2
そこでCockburnは、層の上下ではなく、アプリケーションの内と外の非対称に目を向けました。外とのやり取りの目的ごとにポートを定め、技術ごとのアダプターをつなぎます。1つのポートには、画面、テスト用の仕組み、バッチなど複数のアダプターがつながります。六角形の6という数に意味は無く、ポートを描き足す余白を取るためだとしています。
オニオンアーキテクチャは、Jeffrey Palermoが2008年7月29日のブログで名付けたパターンです。*3 Palermoは、従来の層構造では画面が業務の処理を経由してデータアクセスに依存してしまう点を問題にしました。中心にドメインモデルを置き、その周りに保存と取り出しのためのリポジトリのインターフェースを置きます。実装は外側に置き、「The database is not the center. It is external.」と書いています。*3
| 観点 | ヘキサゴナル | オニオン | クリーンアーキテクチャ |
|---|---|---|---|
| 提唱者と時期 | Alistair Cockburn(2005年) | Jeffrey Palermo(2008年) | Robert C. Martin(2012年) |
| 描き方 | 内と外の2つに分け、ポートを描き足す | 中心から外へ重なる同心円 | 4つを目安とする同心円 |
| 中心に置くもの | アプリケーション全体 | ドメインモデル | エンティティ(企業全体の業務の規則) |
| 外とのつなぎ方 | ポートごとに技術別のアダプター | 中心側のインターフェースを外側で実装 | 内側のインターフェースを外側で実装し、単純なデータを渡す |
3つは対立する流派ではありません。Palermoは、ヘキサゴナルとオニオンが、インフラを外側の層に置いてアダプターのコードを書くという前提を共有していると書いています。*3 クリーンアーキテクチャは、その内側をエンティティとユースケースの2つに分け、境界を越える方法と渡すデータの形まで明文化したもの、と捉えると整理しやすくなります。
Pythonで見る具体例
注文を受け付ける処理を例に、依存の向きをコードで確かめます。内側のファイルには、エンティティ、ユースケース、ユースケースが使う保存のインターフェース(ポート)だけを置きます。インターフェースにはPython標準の typing.Protocol を使いました。継承しなくても、同じメソッドを持つクラスなら型チェッカーが受け入れる仕組みです。*4
# app/core.py(内側の円:外側の名前を一つも知らない)
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class Order: # エンティティ
order_id: str
amount: int
class OrderRepository(Protocol): # 内側が決める出力ポート
def save(self, order: Order) -> None: ...
class PlaceOrder: # ユースケース
def __init__(self, repo: OrderRepository) -> None:
self.repo = repo
def run(self, order_id: str, amount: int) -> Order:
if amount <= 0:
raise ValueError("amount must be > 0")
order = Order(order_id, amount)
self.repo.save(order)
return order
このファイルは sqlite3 もWebフレームワークも import していません。SQLを書くのは外側のアダプターで、アダプターは内側の Order と PlaceOrder の名前だけを参照します。どのリポジトリを渡すかは、一番外側の起動部分で決めます。
# app/adapters.py(外側の円:内側の名前だけを参照する)
import sqlite3
from app.core import Order, PlaceOrder
class SqliteOrderRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self.conn = conn
conn.execute("CREATE TABLE orders (id TEXT, amount INTEGER)")
def save(self, order: Order) -> None: # SQLはこの層に閉じる
self.conn.execute("INSERT INTO orders VALUES (?, ?)",
(order.order_id, order.amount))
if __name__ == "__main__": # 組み立ては一番外側で行う
conn = sqlite3.connect(":memory:")
PlaceOrder(SqliteOrderRepository(conn)).run("A-001", 1200)
print(conn.execute("SELECT * FROM orders").fetchall())
$ python -m app.adapters
[('A-001', 1200)]
ユースケースのテストでは、save で受け取った注文をリストにためるだけの偽物のリポジトリを渡せば、データベースなしで「0円の注文は ValueError になる」ことを確かめられます。Cockburnが挙げた、メモリ上のモックのデータベースをつなぐやり方と同じです。偽物の種類と使い分けは「テストダブルとは」で扱っています。
依存関係のルールは、守られているかを機械で確かめられるようにしておくと崩れにくくなります。次のスクリプトは、内側のファイルが外側のモジュールを import していないかを標準の ast モジュールで調べます。
# check_deps.py(内側が外側を import していないかをCIで確かめる)
import ast
import pathlib
import sys
INNER = pathlib.Path("app/core.py")
OUTER = {"app.adapters", "sqlite3", "flask", "sqlalchemy"}
tree = ast.parse(INNER.read_text(encoding="utf-8"))
names = [a.name for n in ast.walk(tree) if isinstance(n, ast.Import) for a in n.names]
names += [n.module for n in ast.walk(tree) if isinstance(n, ast.ImportFrom) and n.module]
bad = [m for m in names if m in OUTER or m.split(".")[0] in OUTER]
if bad:
sys.exit(f"dependency rule violated: {bad}")
print("ok:", sorted(set(names)))
$ python check_deps.py
ok: ['dataclasses', 'typing']
試しに core.py へ from app.adapters import SqliteOrderRepository の1行を足すと、スクリプトは「dependency rule violated: [‘app.adapters’]」と出して終了コード1で止まりました。Python 3.12で3つとも実行して確かめています。
実務での使いどころ
効果が出やすいのは、長く使い続け、途中でデータベースや画面、外部サービスが替わる見込みのあるシステムです。Palermoも、オニオンアーキテクチャは小さなWebサイトには向かず、長く使う業務アプリケーションや振る舞いの複雑なアプリケーションに向くと書いています。*3
- 業務の規則が多いシステム:受発注、与信、料金計算など、規則をデータベースなしでテストしたい
- 入口が複数あるシステム:同じ処理を画面、API、バッチ、メッセージの受信から呼ぶ
- 基盤の入れ替えを控えたシステム:データベースやクラウドのサービス、フレームワークの更新を予定している
- レガシーの改修:業務の規則を画面やSQLから少しずつはがし、内側へ移す
反対に、画面の入力をそのまま保存するだけの管理画面や作り捨ての試作では、変換のコードが増えるだけになりがちです。1つのシステムの中でも、業務の規則が集まる部分にだけ適用し、単純な画面はフレームワークの標準的な作りに任せる使い分けができます。
つまずきやすい点
よく見かけるのは、円の図をフォルダ構成の型として写し、中身は以前のままという状態です。entities、usecases といったフォルダを作っても、ユースケースがORMのモデルやWebフレームワークのリクエストを直接受け取っていれば、依存は外側を向いたままです。見るべきはフォルダ名ではなく、import の向きです。
- ORMのモデルをそのままエンティティとして使い、内側がデータベースの形を知ってしまう
- 実装が1つしか無く替える予定も無いものまで、すべてにインターフェースを作る
- 層ごとに同じ項目のデータ型を作り、変換のコードが処理の本体より長くなる
- 円を4つに固定し、ユースケースが1行の受け渡しだけになる
- トランザクションの範囲をどの円で決めるかを決めないまま進める
インターフェースと変換の数は、替える見込みのある外側の数に合わせて決めます。4つの円は目安です。破られたことを検知する手段が無いと層が崩れるというCockburnの指摘のとおり、前節のような依存の検査をCIに入れておくと、崩れ始めたときに気づけます。
外部に委託するときに確認しておきたい点
開発を委託するときに「クリーンアーキテクチャで作ります」と言われても、その中身は会社やチームによって違います。設計書や途中の成果物で、次の点を確かめておきます。
- どの部分に適用し、どの部分はフレームワークの標準の作りで済ませるかが書かれているか
- 円(層)の数と、それぞれに何を置くかが決まっているか
- 依存の向きを検査する仕組みがCIに入っているか
- 業務の規則のテストが、データベースなしで短い時間で回るか
- トランザクションや認証を、どの層で扱うかが決まっているか
設計の妥当性を第三者の目で点検する進め方は「技術選定・アーキテクチャレビューを外注で補強」で扱っています。納品後に自社や別の会社が保守する場合は、どの判断でどの層に置いたかを短い記録に残してもらうと、引き継いだ側が規則を守り続けやすくなります。
まとめ:クリーンアーキテクチャで確かめておきたい3つの点
確かめておきたい点は3つです。第一に、クリーンアーキテクチャの核は、ソースコードの依存を内側の業務の規則へだけ向ける依存関係のルールで、円の数やフォルダの名前ではないこと。第二に、ヘキサゴナル・オニオンも、インフラを外側の層に置いて業務の規則を守るという同じ目的の設計で、違いは内と外の描き方と中心に置くものにあること。第三に、小さなアプリには重いため適用する範囲を絞り、依存の向きをCIで確かめる手当てまで含めて設計することです。
よくある質問
クリーンアーキテクチャでは、4つの層に分けなければなりませんか
分ける必要はありません。Robert C. Martinは原文で、4つの円は模式的なもので、必要ならもっと多くてもよいと書いています。変えてはいけないのは、ソースコードの依存が常に内側を向くことです。規模が小さければ、業務の規則とそれ以外の2つに分ける形から始める方法もあります。
クリーンアーキテクチャとドメイン駆動設計は同じものですか
別のものです。ドメイン駆動設計は、業務の言葉とモデルをどう切り出すかを扱う設計の進め方です。クリーンアーキテクチャは、切り出した業務の規則をデータベースや画面とどの向きの依存でつなぐかを扱います。組み合わせて使われることが多く、ドメインモデルを内側の円に置く形になります。
既存のシステムにも取り入れられますか
取り入れられます。新しく足す機能や、改修の多い業務の規則から始め、画面やSQLに埋もれている規則を少しずつ内側のコードへ移します。移した部分には依存の検査とテストを先に用意し、元に戻らないようにしておくと進めやすくなります。
ヘキサゴナルアーキテクチャの六角形には意味がありますか
6という数に意味はありません。Alistair Cockburnは、六角形にしたのはポートとアダプターを必要なだけ描き足せる余白を取るためで、1列に積む層の図から離れるためだと説明しています。自身が出会ったポートは2〜4つだったとも書いています。
保守しやすい設計でのシステム開発のご相談
元請(プライムベンダー)として、アーキテクチャの設計とテストの自動化を含めたシステム開発から、保守・運用までご提案します。
Remoguとリラシクなら、システムの設計や改修に加わるITエンジニアも探せます。
Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。
出典
- *1 参考:Robert C. Martin「The Clean Architecture」(Clean Coder Blog、2012年8月13日)(https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)。出典:先行する設計の列挙、5つの性質、The Dependency Rule、4つの円の役割、Only Four Circles?、Crossing boundaries、What data crosses the boundaries を参照(2026年10月確認)
- *2 参考:Alistair Cockburn「The Hexagonal (Ports & Adapters) Architecture」(HaT Technical Report 2005.02、2005年9月4日)(https://alistair.cockburn.us/hexagonal-architecture/)。出典:Intent、Motivation(新しい層の約束が破られる問題)、内と外の非対称、ポートとアダプター、六角形の意味、モックのデータベースを参照(2026年10月確認)
- *3 参考:Jeffrey Palermo「The Onion Architecture : part 1」(Programming with Palermo、2008年7月29日)(https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)。出典:従来の層構造の問題、中心へ向かう依存の規則、ドメインモデルとリポジトリのインターフェース、適用範囲、ヘキサゴナルとの共通の前提を参照(2026年10月確認)
- *4 参考:Python Software Foundation「typing — Support for type hints」(https://docs.python.org/3/library/typing.html)。出典:typing.Protocol(structural subtyping、PEP 544)の説明を参照(2026年10月確認)