はじめに
Webアプリケーションのアーキテクチャは、時代とともに構造が整理されてきた。
本記事では、その変遷をJavaの技術スタックを軸に、2層構造(Model 1)からMVCパターン(Model 2)、レイヤードアーキテクチャ、そしてヘキサゴナルアーキテクチャ・クリーンアーキテクチャに至るまでの流れを図解して整理する。
前提条件
- 本記事はJava(JSP/Servlet、Spring等)をベースとしたWebアプリケーションのアーキテクチャの変遷を扱う
- 対象読者はジュニアエンジニアを脱却し、設計やアーキテクチャに関心を持ち始めたエンジニアを想定している
- 各アーキテクチャの網羅的な解説ではなく、「なぜその構造が生まれたのか」という変遷の因果関係に焦点を当てる
- スコープはModel 1からクリーンアーキテクチャまでとする
本記事で扱うアーキテクチャの変遷
本記事では、以下の流れでアーキテクチャの変遷を追っていく。
各構造は「前の構造の課題を解決するために生まれた」という因果関係でつながっている。
| # | アーキテクチャ | 概要 | 前の構造の課題 |
|---|---|---|---|
| 1 | Model 1(JSP中心) | JSPが表示・ロジック・画面遷移をすべて担う構造 | ― |
| 2 | Model 2 / MVCパターン | Servlet(Controller)・JSP(View)・JavaBean(Model)に役割を分離 | Model 1の密結合 |
| 3 | 伝統的レイヤードアーキテクチャ | Service・DAO・DTO等で層を分割 | Modelの肥大化(Fat Model) |
| 4 | エヴァンス本初期提唱時のレイヤードアーキテクチャ | ドメイン層を独立させた4層構造 | Serviceの肥大化(Fat Service) |
| 5 | DIP適用レイヤードアーキテクチャ | ドメイン層がインフラ層に依存しない構造 | ドメイン層がインフラの影響を受ける |
| 6 | ヘキサゴナルアーキテクチャ | ポートとアダプターで外部との接点を抽象化 | 層の上下関係に縛られた構造の限界 |
| 7 | オニオンアーキテクチャ | 同心円状の層でドメインを中心に保護する構造 | ポート/アダプターの層構造が未整理 |
| 8 | クリーンアーキテクチャ | 同心円状の依存ルールでアーキテクチャを統合的に整理 | 各アーキテクチャの思想の統合 |
以降、各構造を順に詳しく見ていく。
Model 1(JSP中心の構造)
Webシステムの黎明期に主流だった構造。JSPがリクエストの受付・ビジネスロジック・画面表示をすべて担っており、プレゼンテーションとロジックが密結合していた。UIを変更するとビジネスロジックにまで影響が及ぶなど、変更にとにかく弱かった。
3層構造(MVCパターン)
3層構造では、Model 1における「密結合」という致命的な欠点を改良するために、MVCパターンに基づいたアーキテクチャが提唱された。
MVCはModel-View-Controllerの略で、ビジネスロジック(Model)、ユーザーインターフェース(View)、制御ロジック(Controller)を分離するアーキテクチャパターンです。これにより、変更に強い構造となる。
2層の課題を解決する為、3層構造が普及し始めた
しかし、以下の図に有る様に、Modelの肥大化(Fat Model)を招いていた
Web開発ではMVCと3層構造が組み合わされることが多い
ここでは、3層構造とMVCパターンの組み合わせで提示している
伝統的なレイヤードアーキテクチャ(ドメイン駆動設計の影響を受ける前)
上述の様に3層、MVCパターン採用時は、Modelが肥大化していた。当時はModelがデータもビジネスロジックもDB操作も指してしまっていた。バリデーションルールもアーキテクチャとしては明確ではなく、Controllerで入力値のフォーマットチェック、Modelで業務ロジックのチェックを行うなど、責務の境界が曖昧であった。
バリデーションの配置について、個人的経験では役割に応じたValidatorクラスを定義して整理していたプロジェクトもあった。ただし、これはプロジェクト固有の工夫であり、アーキテクチャとして標準化されたものではなかった。
そこで、伝統的なレイヤードアーキテクチャでは、3層構造(MVC)において発生してしまった「Modelの肥大化(Fat Model)」という欠点を改良するために、より役割を細分化した階層構造として以下の様に提唱された。
- プレゼンテーション層
- ビジネス層(サービス層)
- データアクセス層
この時代のレイヤードは「データアクセス層」に依存するアーキテクチャであった。
DBテーブル設計を最初に行い、テーブルと対になるEntityを定義し、EntityをDAOからServiceに渡して、業務ロジックに合う形でEntityからDTOに加工してViewに渡す流れであった。
結果として、MVCが抱えていた「Fat Model」の課題は、Service層とDAOを分離することで理論上は解消されたかに見えたが、ビジネスロジックが整理しきれたわけでは無かった。
結局のところ、伝統的なレイヤードは「物理的な階層化」には成功したが、ビジネスロジックを整理する点が欠けていた。
伝統的なレイヤードアーキテクチャへの個人的見解
中規模のプロジェクトを担当していたが、ビジネスロジックは簡単に整理しきれたものでは無く、似たようなクラス名のServiceが存在し、ソースコードからユーザーが行いたいビジネスのユースケースを洗い出すのは難しかった感が有る
担当したメンバーしか詳細仕様を把握できないプロジェクトも有った。
特定の業務ロジックへ改修を入れる際、Controller、DAOはシンプルで読みやすいが、ビジネスロジックが何を行っているか調べる際は混在したService層から探し出さねばならず、「属人化」という弊害も見受けられた
エヴァンス本初期提唱時のレイヤードアーキテクチャ(ドメイン駆動設計の影響後)
伝統的レイヤードアーキテクチャでは、業務ロジックがService層に集中し、Fat Serviceが課題となっていた。これは、Serviceが「ユースケースの調整(トランザクション管理、処理の順序制御など)」と「純粋な業務ルール(計算、判定、状態遷移など)」の両方を担っていたことに起因する。
エヴァンスが提唱したDDDでは、この2つの責務を明確に分離した。
DDD(Domain-Driven Design:ドメイン駆動設計)
ドメイン(業務)を最優先に考えるソフトウェア設計手法
DDD以前は、開発側の事情(開発しやすさ、実装済みのコード、構築済みのDBテーブル定義)を優先してコーディングしているが、業務ルールを最優先に考える
顧客が業務で使用している業務用語(ユビキタス言語)や業務ルール、業務フローをそのままコードに反映する手法
ウォーターフォール開発手法より、アジャイル開発手法が親和性がある
Application層はユースケースの調整役に徹し、Domain層に純粋な業務ルールを凝集させる。これにより、Serviceは薄くなり、業務ルールはドメインオブジェクト(Entity、Value Object等)に閉じ込められるため、Fat Serviceが解消される。
この考え方に基づき、以下の4層構造が提案された
エヴァンス本初期提唱時のレイヤードアーキテクチャへの個人的見解
Domain層に業務ルールを寄せると言っても、大げさなことをする訳ではなくて、
金額計算や状態遷移の判定、値の妥当性チェックなど、従来Serviceに寄っていた細かい業務判断をEntityやValue Objectへ少しずつ移していく感覚に近い
結果として、Serviceは処理の段取りだけを担うようになり、業務ルールそのものはドメインの中に自然と集まっていくイメージである。無理に“凝集させる”というよりは、業務ルールを分解していく感覚である
DIP(依存性の逆転原則)を適用したレイヤードアーキテクチャ
エヴァンス本初期提唱時のレイヤードアーキテクチャでは、ドメイン中心で考えるべき「ドメイン層」がインフラの影響を受けてしまう課題があった。これを解決するために考えられたのが、DIPを適用したアーキテクチャである。
DIP(Dependency Inversion Principle:依存性逆転の原則)
ソフトウェア設計(特にオブジェクト指向)におけるSOLID原則の1つ
「上位モジュールは下位モジュールに依存してはならず、両者は『抽象(インターフェース)』に依存すべきである」という原則
スタイルとしては「ドメインを技術基盤(Infra)から完全に独立させる」スタイル
ドメイン層にRepositoryという名のインターフェースを置き、インフラ層にそのインターフェースをimplementsした実装クラス(SQL発行など)を置く。
これにより「依存性の逆転(DIP)」が起き、インフラ層がドメイン層に依存する構造となり、DDDの理想の形が実現される
DIP(依存性の逆転原則)を適用したレイヤードアーキテクチャへの個人的見解
DIPの概念に触れた当初は、正直すこし戸惑いがあった。
RepositoryインターフェースをDomain側に置くという考え方自体は理解できたが、実装がInfra側に寄るため、「ここまで分ける必要があるのか」と疑問に思った。
しかし、テストコードを書いてみると、その意図がようやく理解できた気がした。
アプリケーション層やドメイン層がインフラ層に依存しなくなることで、テストが格段に容易になった。
特にアプリケーション層はDBテーブルに依存せず、インターフェース(Repository)のみに依存しているため、モックをDIで注入するだけでテストが成立した。
以前のアーキテクチャでは、層同士がそのままクラスを受け渡していたため、単体テストが難しく、結局は結合テスト中心で動作確認するケースが多かった。
それが、DIPとDIの組み合わせによって、従来とは比べものにならないほどテストを書きやすくなり、まさに目からうろこであった。
ヘキサゴナルアーキテクチャ(Ports and Adapters)
DIP適用レイヤードアーキテクチャでは、ドメインをインフラから独立させることに成功した。しかし、依然として「上位層→下位層」という層の上下関係が前提にあり、外部との接点が「上(UI)」と「下(DB)」に固定的に捉えられがちであった。実際のシステムでは、外部APIの呼び出し、メッセージキュー、バッチ起動など、UIやDBに収まらない多様な入出力が存在する。
アリスター・コーバーンが提唱したヘキサゴナルアーキテクチャ(別名:Ports and Adapters)は、この課題に対して「層の上下」という概念自体を捨て、アプリケーションの中心にドメインを置き、外部とのすべての接点を「ポート(インターフェース)」と「アダプター(実装)」で抽象化した構造になっている。
ポートには2種類ある。
- ドライビングポート(入力側):外部からアプリケーションを駆動するためのインターフェース。Controller等のアダプターがこのポートを呼び出す
- ドリブンポート(出力側):アプリケーションが外部リソースを利用するためのインターフェース。DB接続や外部API呼び出し等のアダプターがこのポートを実装する
この構造により、ドメインは入出力の技術的詳細を一切知らず、ポートを差し替えるだけでUIをWebからCLIに変更したり、DBをRDBからNoSQLに変更したりできる。DIP適用レイヤードアーキテクチャで実現した「ドメインの独立」を、層の上下関係に縛られない、より汎用的な形で表現したアーキテクチャだ
ヘキサゴナルアーキテクチャの例
ヘキサゴナルアーキテクチャの構造を、引き続き「レストランの経営」を例に理解を深めてみる。
レイヤードアーキテクチャは「上(お客さん)→ 下(厨房・倉庫)」という縦の階層構造であった。しかし実際のレストランには、お客さんやDB(倉庫)以外にも、Uber Eatsの配達員、食材の仕入れ業者、予約サイト、電話注文など、多様な外部との接点が存在する。これらを「上」と「下」に無理やり分類するのは不自然である。
ヘキサゴナルアーキテクチャは、この「上下」の概念を捨て、レストランの中心に キッチン(アプリケーション) を置き、外部とのすべての接点を 「窓口のルール(ポート)」 と 「窓口の担当者(アダプター)」 で整理する。
ヘキサゴナルアーキテクチャ(別名:ポート&アダプター)の最大の特徴は、「ビジネスの核心(レシピと調理)」を「外部の手段(注文方法や仕入れ先)」から完全に切り離すことにある。
1. ドメイン(Domain):秘伝のレシピ
六角形の中心にあるのは、お店の価値そのものであるレシピ!
「ハンバーグには肉を200g使う」「焼き時間は5分」といった、ビジネス上の絶対的なルールが格納されている
電話で注文が来ようが、ネット予約だろうが、レシピの内容(ビジネスルール)は変わらない。
2. アプリケーション(Application):シェフ(調理担当)
レシピを熟知し、実際に料理を完成させる司令塔にあたる
「注文票を受け取る」→「レシピ通りに焼く」→「お皿に盛り付ける」
という一連の作業(ユースケース)を実行する
シェフは「誰が注文したか(UI)」や「どこの肉屋から届いたか(DB)」の詳細は気にせず、目の前の「注文票(ポート)」に従って動くだけ
3. ポート(Port):共通の伝票・依頼書
図にある「点線の枠」で、中(シェフ)と外(店員や業者)をつなぐ標準的な窓口(インターフェース)にあたる。
- ドライビングポート(入力側): 「標準注文票」。店員もアプリも、この形式でシェフに注文を伝えなければならない
- ドリブンポート(出力側): 「食材発注書」。シェフが「肉が欲しい」と伝えるための決まったルール
4. アダプター(Adapter):店員・アプリ・仕入れ業者
図にある「外側の箱」で、特定の技術や道具を使ってポートとやり取りするための翻訳機にあたる。
ドライビングアダプター(入力側)
- 店員: お客さんの「声」を「標準注文票」に書き換える
- モバイルアプリ: 「タップ操作」を「標準注文票」データに変換する
ドリブンアダプター(出力側)
- 精肉店 (RDB): シェフの発注書を受け取り、冷蔵庫(データベース)から肉を取り出す
- 野菜市場 (外部API): 発注書を受け取り、外部の市場から野菜を調達する
この設計のメリット
| 状況 | ヘキサゴナルの対応 |
|---|---|
| 新しい注文アプリを導入したい | 新しい「入力アダプター」を作るだけでOK。シェフやレシピは一切変えない。 |
| 仕入れ先の肉屋が廃業した | 別の肉屋(新しい「出力アダプター」)に変えるだけ。調理手順は変わらない。 |
| お店をオープンする前に味を試したい | 「テスト用アダプター」を使えば、本物の肉屋やアプリがなくても、レシピ(ドメイン)の正しさを検証できる。 |
ポイント:「窓口のルール」を差し替えるだけで外部が変わる
この構造の最大の利点は、キッチン(アプリケーション)は窓口のルール(ポート)だけを知っていればよいという点である。
- 注文方法を「店内のみ」から「Uber Eats対応」に拡張したい → 新しいドライビングアダプター(Uber Eats受付担当)を追加するだけ。キッチンの変更は不要
- 食材の仕入れ先を「市場」から「農家直送」に変えたい → ドリブンアダプター(仕入れ担当者)を差し替えるだけ。キッチンの変更は不要
レイヤードアーキテクチャでは「上(UI)」と「下(DB)」に固定されていた外部との接点が、ヘキサゴナルアーキテクチャでは六角形の各辺にポートを自由に配置できる。これにより、入出力の種類がいくら増えても、キッチン(アプリケーション)は一切影響を受けない構造が実現される。
ヘキサゴナルアーキテクチャへの個人的見解
ヘキサゴナルアーキテクチャを個人で学習したとき、最初に感じたのは「考え方そのものはシンプルなのに、実際にコードへ落とし込むと意外と奥が深い」という点であった。
ポートとアダプタという構造は理解しやすかったが、どこまでをポートに切り出すべきか、アダプタとして扱う部分をどの粒度で分けるべきかなど、実際に手を動かすほど迷う場面が多かった。
ただ、学習を進めるうちに「アプリケーションの中心にあるべきものを守る」という思想が非常に分かりやすく、個人的には好きな構造であった。
インフラに左右されず、ドメインやアプリケーションの本質を保とうとする設計は、長期運用を考えると筋が通っていると思った。
実務で触れたわけではないが、コード例を作りながら試してみると、依存方向がはっきりすることで見通しが良くなる場面も多く、学習としては非常に得るものが多かった。
一方で、プロジェクト規模やチームの習熟度によっては過剰設計と受け取られそうだとも感じるので、実際に適用するにはバランス感覚が要りそうだなと思った次第。
オニオンアーキテクチャ
ヘキサゴナルアーキテクチャは「層の上下」を捨て、ポートとアダプターで外部との接点を抽象化した。しかし、アプリケーション内部の層構造(ドメイン層、アプリケーションサービス層、インフラ層など)をどう整理するかについては明確な指針を示していなかった。
ジェフリー・パレルモが提唱したオニオンアーキテクチャは、ヘキサゴナルアーキテクチャの「ドメインを中心に据える」思想を受け継ぎつつ、アプリケーション内部の層を同心円状に整理した構造である。玉ねぎの皮のように、内側から外側へ向かって層が重なる構造となる
同心円は内側から以下の順で構成される。
- Domain Model:EntityやValue Objectなど、純粋なビジネスルールを表現する中核
- Domain Services:単一のEntityに属さないドメインロジックを担う
- Application Services:ユースケースの調整役。ドメインオブジェクトを操作して処理を組み立てる
- Infrastructure:DB、UI、外部サービス等の技術的詳細。最も外側に位置する
依存のルールはヘキサゴナルアーキテクチャと同様に「外側から内側へ」のみ許可される。ヘキサゴナルが「外部との接点をどう扱うか」に焦点を当てたのに対し、オニオンは「アプリケーション内部の層をどう分割するか」を明確にしたところがポイントだ。
オニオンアーキテクチャの例
オニオンアーキテクチャの構造を、引き続き「レストランの経営」を例に理解を深めてみる。
ヘキサゴナルアーキテクチャでは、六角形の内側を「アプリケーション」としてひとまとめに扱い、外部との境界をどう扱うかに焦点を当てていた。しかし、「キッチンの中をどう組織するか」については明確な指針がなかった。
オニオンアーキテクチャは、このキッチンの内部を玉ねぎの皮のように同心円状に整理する。中心に最も大事なものを置き、外側に向かって「変わりやすいもの」を配置していく。
同心円をレストランで例えると
| オニオンの層(内側→外側) | レストランの例え | 説明 |
|---|---|---|
| Domain Model(最も内側) | レシピ | 「カレーにはこのスパイス配合」「塩分はこの範囲」といった純粋な業務ルール。誰が作っても、どんな厨房でも、このルールは変わらない |
| Domain Services | 複数のレシピにまたがる調理ルール | 「セットメニューの場合、メインとサイドの味のバランスはこう調整する」など、単一のレシピに属さないドメインロジック |
| Application Services | 料理長(調理の段取り) | 「注文を受けたら→在庫を確認→調理指示→盛り付け→提供」というユースケースの調整役。レシピ(Domain Model)を使って業務フローを組み立てる |
| Infrastructure(最も外側) | ホールスタッフ、仕入れ業者、食券機、DB等 | 注文の受付方法、食材の調達先、厨房の設備など技術的な詳細。最も変わりやすい部分 |
ヘキサゴナルとの違い:「キッチンの中」を整理した
ヘキサゴナルアーキテクチャでは「キッチン全体」と「外の世界」の境界に注目していた。オニオンアーキテクチャは、そのキッチンの中をさらに整理し、 「レシピ(Domain Model)」「調理ルール(Domain Services)」「料理長の段取り(Application Services)」 という層に分けた。
これにより、以下のことが明確になる。
- レシピ(Domain Model)は、料理長の段取り(Application Services)を知らない
- 料理長の段取り(Application Services)は、ホールスタッフや仕入れ業者(Infrastructure)を知らない
- 依存の方向は常に「外側から内側へ」の一方通行である
ヘキサゴナルアーキテクチャが「キッチンと外の世界の境界をどう扱うか」を解決したのに対し、オニオンアーキテクチャは「キッチンの中をどう組織するか」を明確にしたアーキテクチャである。
オニオンアーキテクチャへの個人的見解
オニオンアーキテクチャを学習したときは、ヘキサゴナルと似ているようでいて、考え方の置き方が少し違うなという印象だった。
同心円状に“内側ほど重要なものがある”という構造は直感的で分かりやすく、ドメインを中心に据えるという発想がより強調されている感じがした。
個人でコードを書きながら試してみたのだが、依存の向きを常に内側へ寄せるというルールは、実装方針として迷いが減る点が良かった。
ただ、一方で層をきれいに分けようと頑張りすぎると、インターフェースやクラスが少し増えがちで、「ここまで厳密にやるべきなのか?」とも思った
それでも、ドメインを中心に守りながらシステム全体を組み立てるという思想は腑に落ちるところが多く、長期運用を考えるとメリットが有るなと感じた。
まだ個人学習でしか使っていないが、好感を持っている。実務で使う機会があればもう少し深く試してみたい。
クリーンアーキテクチャ
ヘキサゴナルアーキテクチャ、オニオンアーキテクチャ、DIP適用レイヤードアーキテクチャは、いずれも「ドメインを中心に据え、外部の詳細から独立させる」という同じ思想を持っていた。しかし、これらは別々の文脈で提唱されたため、用語や構造の表現が異なり、統一的な理解が難しかった。
ロバート・C・マーティン(Uncle Bob)が提唱したクリーンアーキテクチャは、これらの思想を統合し、同心円状の図で依存ルールを明確にしたもの
中心となるルールは1つだけ。「依存の方向は常に外側から内側へ向かう。内側の円は外側の円について何も知らない」
同心円は内側から以下の順で構成される。
- Entities:ビジネスルールを表現するドメインオブジェクト。最も変更されにくい中核
- Use Cases:アプリケーション固有のビジネスルール。Entityを操作してユースケースを実現する
- Interface Adapters:外部と内部のデータ形式を変換する層。Controller、Presenter、Gateway等が該当する
- Frameworks & Drivers:最も外側の層。Web、DB、UIフレームワーク等の技術的詳細を配置する
クリーンアーキテクチャは新しいアーキテクチャパターンを発明したというよりも、既存の「ドメイン中心」のアーキテクチャ群が共有していた本質的なルール(依存は外から内へ)を統一・整理したものという解釈で良いと思う。
一言でいうと、「大事なもの(ビジネスルール)を中心に置いて、外側の都合に振り回されないようにする設計の考え方」である
クリーンアーキテクチャの処理フロー
同心円図だけでは「実際の処理がどう流れるのか」が分かりにくいので、処理フローも追記する。
Controller(インターフェースアダプター層)がInput Port(ユースケース層のインターフェース)を呼び出し、Use Case Interactor(ユースケース層の実体である実装クラス)が業務ロジックを実行する。結果はOutput Port(ユースケース層のインターフェース)を通じてPresenter(インターフェースアダプター層)に渡される。
ここで重要なのは、PresenterはOutput Portを実装(implements)する側だという点だ。つまりインターフェースアダプター層がユースケース層のインターフェースに依存しており、依存性の逆転(DIP)が適用されている。これによって、ユースケース層は外側の層について何も知らないという依存ルールが守られる。
クリーンアーキテクチャの例
クリーンアーキテクチャの構造を「レストランの経営」を例に示しながら理解を深めてみる。
一番内側:Entities(レシピ・味の基準)
「このカレーはこのスパイス配合で作る」「塩分はこの範囲」といった料理の本質的なルールがここにあたる。お店の内装が変わろうが、注文方法が変わろうが、レシピ自体は変わらない。ビジネスの核心であり、最も安定した部分だ。
2番目:Use Cases(注文〜提供の流れ)
「注文を受けたら→在庫を確認→調理指示→盛り付け→提供」という業務の手順にあたる。レシピ(Entities)を使って具体的な業務フローを組み立てる層で、「ランチセットならサラダ→メイン→ドリンクの順で出す」のような段取りを担う。
3番目:Interface Adapters(ホールスタッフ・伝票システム)
お客さんの注文をキッチンが分かる形に変換したり、逆に出来上がった料理をお客さんに届けられる形に整えたりする。「テーブル3番、カレー1つ」を調理指示書に書き換えるイメージで、外の世界と内側の世界の「通訳」にあたる。
一番外側:Frameworks & Drivers(店舗・食券機・Uber Eats)
注文方法が「口頭」「食券機」「Uber Eats」のどれであっても、中のレシピや業務手順には影響しない。厨房の設備がガスからIHに変わっても、レシピの本質は同じだ。最も変わりやすい部分で、技術的な詳細がここに集まる。
処理フローをレストランで例えると
上述の処理フロー図の登場人物を、レストランの登場人物に対応付けると以下のようになる。
| クリーンアーキテクチャの登場人物 | レストランの例え | 役割 |
|---|---|---|
| Controller | ホールスタッフ | お客さんの注文を受け取り、キッチンに伝える |
| Input Port | 注文受付窓口のルール(インターフェース) | 「注文はこの形式で伝えてね」という取り決め。ホールスタッフはこのルールに従って注文を伝える |
| Use Case Interactor | 料理長 | 注文受付窓口(Input Port)から注文を受け取り、レシピ(Entity)を参照しながら実際に調理を指揮する。クリーンアーキテクチャの処理の中心人物 |
| Output Port | 料理の受け渡しルール(インターフェース) | 「出来上がった料理はこの形式で渡してね」という取り決め。料理長はこのルールに従って結果を渡す |
| Presenter | 盛り付け・配膳担当 | 料理長から受け取った料理を、お客さんに見せる形(お皿に盛り付け、トレーに載せるなど)に整える |
処理の流れをレストランで表現すると以下のようになる。
- お客さんが注文する(リクエスト)
- ホールスタッフ(Controller) が注文を聞き取り、注文受付窓口のルール(Input Port)に従って調理指示書を作成する
- 料理長(Use Case Interactor) が調理指示書を受け取り、レシピ(Entity)を参照しながら調理を指揮する
- 料理が完成したら、料理長は料理の受け渡しルール(Output Port)に従って結果を渡す
- 盛り付け・配膳担当(Presenter) が料理を受け取り、お客さんに提供できる形に整えて届ける
ここでのポイントは、 料理長(Use Case Interactor)はホールスタッフ(Controller)や盛り付け担当(Presenter)が誰であるかを知らない という点だ。
料理長は「注文受付窓口のルール」と「料理の受け渡しルール」というインターフェースだけを知っている。だから、ホールスタッフがアルバイトからベテランに交代しても、配膳方法がテーブルサービスからカウンター受け取りに変わっても、料理長の仕事は一切変わらない。
これが依存性の逆転(DIP)の効果であり、クリーンアーキテクチャの「内側は外側を知らない」というルールの本質にあたる。
なぜ嬉しいのか?
| メリット | レストランの例 |
|---|---|
| テストしやすい | 厨房がなくてもレシピの正しさは検証できる |
| 変更に強い | 注文方法を変えても料理の品質は変わらない |
| 技術に依存しない | ガスでもIHでもカレーの味は同じ |
クリーンアーキテクチャと他のアーキテクチャの違い(レストランを例に理解する)
クリーンアーキテクチャは、DIP適用レイヤード・ヘキサゴナル・オニオンの思想を統合したものである。しかし「結局何が違うの?」という疑問は残りやすい。ここでは、レストランの例を使って4つのアーキテクチャの違いを整理する。
DIP適用レイヤードアーキテクチャとの違い
DIP適用レイヤードアーキテクチャは、「レストランの縦の指揮系統を整理した」構造である。
- ホールスタッフ(Presentation)→ 料理長(Application)→ レシピ(Domain)→ 仕入れ業者(Infrastructure)という上下の階層がある
- DIPにより、レシピ(Domain)が仕入れ業者(Infrastructure)に依存しない構造を実現した
- ただし、あくまで 「上から下へ」という縦の流れ が前提であり、外部との接点は「上(お客さん)」と「下(仕入れ業者)」に固定されている
クリーンアーキテクチャとの違いは以下の通り。
| 観点 | DIP適用レイヤード | クリーンアーキテクチャ |
|---|---|---|
| 構造のイメージ | 縦の階層(上から下へ) | 同心円(中心から外へ) |
| 外部との接点 | 上(UI)と下(DB)に固定 | 全方位に配置可能 |
| レストランで言うと | 「ホール→厨房→倉庫」の縦ライン | レシピを中心に、注文方法も仕入れ先も全方位から差し替え可能 |
DIP適用レイヤードは「縦の指揮系統の中で依存を逆転させた」のに対し、クリーンアーキテクチャは「縦横の概念を捨て、レシピを中心に全方位を整理した」構造になっている。
ヘキサゴナルアーキテクチャとの違い
ヘキサゴナルアーキテクチャは、「レストランと外の世界の境界線を整理した」構造である。
- 「キッチン(内側)」と「お客さん・仕入れ業者(外側)」の間に ポート(伝票のルール)とアダプター(窓口担当者) を置いた
- これにより、注文方法や仕入れ先を自由に差し替えられるようになった
- ただし、キッチンの中をどう組織するか(レシピと料理長の関係、盛り付け担当の役割など)については明確な指針がなかった
クリーンアーキテクチャとの違いは以下の通り。
| 観点 | ヘキサゴナル | クリーンアーキテクチャ |
|---|---|---|
| 焦点 | キッチンと外の世界の境界 | キッチンの内部構造+境界の両方 |
| 内部の層分け | 明確な指針なし | Entities・Use Cases・Interface Adapters・Frameworks & Driversの4層 |
| レストランで言うと | 「窓口のルールと担当者を決めよう」 | 「窓口も決めるし、キッチン内のレシピ・料理長・盛り付け担当の役割分担も決めよう」 |
ヘキサゴナルが「レストランの外壁と窓口の設計図」だとすれば、クリーンアーキテクチャは「外壁・窓口に加えて、キッチン内部の組織図まで含めた総合設計図」にあたる。
オニオンアーキテクチャとの違い
オニオンアーキテクチャは、「キッチンの内部を同心円状に整理した」構造であり、クリーンアーキテクチャと最も似ている。
- Domain Model → Domain Services → Application Services → Infrastructure という同心円の層を定義した
- 依存の方向は「外側から内側へ」のみ
では何が違うのか?
| 観点 | オニオン | クリーンアーキテクチャ |
|---|---|---|
| 位置づけ | 独立したアーキテクチャパターン | 複数のアーキテクチャ思想の統合・整理 |
| Interface Adapters層 | 明確に定義されていない | Controller・Presenter・Gatewayなどを明確に配置 |
| 処理フロー | 層の依存ルールが中心 | Input Port / Output Portによる処理の流れも明示 |
| レストランで言うと | 「キッチン内をレシピ・調理ルール・料理長・外部に分けよう」 | 「さらに、ホールスタッフと盛り付け担当の役割、注文の受け渡しルールまで明確にしよう」 |
オニオンアーキテクチャが「キッチン内部の層を整理した」のに対し、クリーンアーキテクチャは「層の整理に加えて、注文の受け渡し方(Input Port / Output Port)や、ホールスタッフ・盛り付け担当(Interface Adapters)の役割まで具体的に定義した」構造になっている。
4つのアーキテクチャの関係を一言で
レストランの設計に例えると、4つのアーキテクチャは以下のように段階的に整理が進んだものである。
| アーキテクチャ | レストランで何を整理したか |
|---|---|
| DIP適用レイヤード | 縦の指揮系統の中で、レシピが仕入れ業者に振り回されないようにした |
| ヘキサゴナル | 縦横の概念を捨て、外の世界との窓口を統一的に整理した |
| オニオン | キッチン内部を同心円状に層分けした |
| クリーン | 上記すべてを統合し、「依存は外から内へ」のルールで一枚の設計図にまとめた |
クリーンアーキテクチャは「新しい料理法の発明」ではなく、「先輩たちが試行錯誤してきたキッチン設計のベストプラクティスを、一枚の設計図にまとめたもの」という解釈で良いと思う。
クリーンアーキテクチャのまとめ
クリーンアーキテクチャは「新発明」ではなく、ヘキサゴナルアーキテクチャやオニオンアーキテクチャが共有していた 「大事なものを中心に、外側の都合から守る」 という思想を、同心円の図と「依存は外から内へ」というシンプルなルールで統一的に整理したものだ。
クリーンアーキテクチャへの個人的見解
クリーンアーキテクチャを学習したときは、レイヤー構造の思想としては理解しやすいのに、実際にコードへ落とし込むとかなりストイックな設計になるという印象を受けた。
特に依存の向きを円の中心へ寄せ続けるというルールはかなり厳格で、考え方としては筋が通っているものの、慣れないうちは「ここまで徹底するべきか」と戸惑った。
実装例を自分で書いてみると、ユースケースが明確になり、アプリケーションの意図が整理されていく手応えが確かにあった。
ただ、その反面、インターフェースの数が増えやすく、構造が大きく見えてしまう瞬間もあり、バランスの取り方が難しいアーキテクチャだなと思う
ただ、中心にドメインやユースケースを置くという考えは納得できた。長期的に保守開発を続ける場合、効果的なアーキテクチャだと思う。
個人的理解だが、「アプリケーションの本質とは」を考え直すきっかけになった
まとめ
アーキテクチャ変遷の全体像
本記事で扱った8つのアーキテクチャは、すべて「前の構造の課題を解決するために生まれた」という因果関係でつながっている。以下の比較表で全体を俯瞰する。
| # | アーキテクチャ | 何が課題だったか | どう解決したか | 依存の方向 |
|---|---|---|---|---|
| 1 | Model 1(JSP中心) | ― | JSPが全部やる(表示もロジックも) | 分離なし |
| 2 | MVC(Model 2) | 表示とロジックが密結合 | Controller・View・Modelに役割を分離 | Controller → Model → View |
| 3 | 伝統的レイヤード | Modelが肥大化(Fat Model) | Service・DAO・DTO等で層を分割 | 上位層 → 下位層(一方向) |
| 4 | エヴァンス本レイヤード | Serviceが肥大化(Fat Service) | Domain層を独立させ、業務ルールをドメインオブジェクトに凝集 | 上位層 → 下位層(一方向) |
| 5 | DIP適用レイヤード | ドメイン層がインフラに依存 | Repositoryインターフェースで依存性を逆転 | インフラ層 → ドメイン層(逆転) |
| 6 | ヘキサゴナル | 層の上下関係に縛られる | ポートとアダプターで外部との接点を抽象化 | 外側 → 内側 |
| 7 | オニオン | アプリ内部の層構造が未整理 | 同心円状にDomain Model・Domain Services・Application Services・Infraを整理 | 外側 → 内側 |
| 8 | クリーン | 各アーキテクチャの思想がバラバラ | 既存の思想を統合し「依存は外から内へ」のルールで統一 | 外側 → 内側 |
レストランの例えで振り返る
本記事ではレストランの経営を例に各アーキテクチャを説明した。最後に、レストランの登場人物がどのアーキテクチャのどの要素に対応するかを整理する。
| レストランの登場人物 | 対応するアーキテクチャの要素 | 役割 |
|---|---|---|
| レシピ | Domain Model / Entity | 誰が作っても変わらない、ビジネスの核心となるルール |
| 料理長 | Application Service / Use Case Interactor | レシピを使って業務フローを組み立てる調整役 |
| ホールスタッフ | Controller / ドライビングアダプター | お客さんの注文をキッチンに伝える入力側の窓口 |
| 盛り付け・配膳担当 | Presenter / 出力側アダプター | 料理をお客さんに届けられる形に整える |
| 注文票・発注書 | Port(インターフェース) | キッチンと外部をつなぐ共通のルール |
| 仕入れ業者・食券機 | Infrastructure / Frameworks & Drivers | 最も変わりやすい技術的な詳細 |
結局、何が大事なのか?
8つのアーキテクチャを見てきたが、変遷を通じて一貫しているメッセージは1つだ。
「大事なもの(ビジネスルール)を中心に置いて、変わりやすいもの(UI・DB・外部サービス等)の都合に振り回されないようにする」
Model 1の時代は全部が混ざっていた。MVCで役割を分け、レイヤードで層を分け、DDDでドメインを独立させ、ヘキサゴナルで上下の概念を捨て、クリーンアーキテクチャで思想を統一した。アプローチは異なるが、目指しているゴールは同じといえる。
「なぜこのアーキテクチャが提唱されたのか」に着目すると、現在提唱されているアーキテクチャの理解が進むのではないかと思う










