ソフトウェアアーキテクチャについて読んでいると、必ずセットで登場する三つの名前に気づくでしょう:Hexagonal Architecture、Onion Architecture、そしてClean Architectureです。それぞれ独自の図解・独自の語彙・独自の熱狂的な支持者を持っています。三つを一度に読もうとすると、同じ文を「最良の言い方」で言い合っている三人の議論を眺めているような気分になります。
正直に言えば、これらは同じアイデアを独立して発見し、少し異なる絵で描いたものです。それぞれが他のものが強調していない小さなニュアンスを加えています。共通の北極星が見えれば、違いは自然に明確になり――チームがすでに好んでいる語彙を選ぶだけでよくなります。
三つが共有する一つのアイデア
これらのアーキテクチャはすべて、ある一つのルール――依存ルールと呼ばれることもある――の上に成り立っています:ソースコードの依存は常に内向き、ビジネスルールへ向かわなければならず、フレームワーク・データベース・I/Oへ向かってはならない。
引力のようなものだと考えてください。あなたのドメイン――あなたのプロダクトを価値あるものにするロジック――が中心にあります。その周りにあるすべてのもの(データベース・Webフレームワーク・決済SDK・メールベンダー・CLIツール)が外側を周回しています。外側のレイヤーは内側のレイヤーについて知ることが許されます。内側のレイヤーは外側のレイヤーの存在を知ることを絶対に禁じられています。
なぜこれが重要なのでしょうか?最もよく変わるものは外側にあるからです:決済プロバイダーを乗り換える、新しいデータベースへ移行する、フロントエンドを書き直す、モバイルアプリを追加する。ビジネスルールがそれらの詳細と絡み合っていれば、すべての変更が最も大切なコードに波及します。絶縁されていれば、ベンダーの交換は端での局所的なスワップになり――コアは気づきません。
Alistair Cockburn・Jeffrey Palermo・Robert C. Martin はそれぞれ異なる角度からこのパターンを発見しました。Cockburn は六角形を描き、接続点を「ポート」と呼びました。Palermo は同心円を描き、それらを「レイヤー」と呼びました。Martin も同心円を描き、各レイヤーに厳密な名前をつけました。すべての図における矢印の向きは同じです:内向きです。
ソースコードの依存は内向きです。ドメインがインターフェースを定義し、外の世界がそれを実装します。どこかでその向きを逆にすれば、どの名前を使っていようとアーキテクチャが壊れます。
Hexagonal Architecture(Ports & Adapters)
Alistair Cockburn は2005年に Hexagonal Architecture を Ports & Adapters という別名で発表しました。コアとなる洞察は対称性にあります:あなたのアプリケーションには二種類の外部が存在します――アプリを駆動するもの(ボタンをクリックするユーザー・テストランナー・スケジュールジョブ)と、アプリが駆動するもの(データベース・メールプロバイダー・決済API)。どちらの側もポート――ドメインが所有するインターフェース――とアダプター――現実の技術をそれらのインターフェースに接続する具体的な実装――を通じてやり取りします。六角形の形はただの視覚的なヒントで、接続点(ポート)が多数あることを示しています。「上」と「下」だけではありません。ポート・アダプター・プライマリ/セカンダリの側、そしてコード例の完全な解説は、このシリーズの前回の記事――Ports & Adapters――で初めから丁寧に取り上げています。ここで押さえておくべきポイントは、Hexagonal が三つの中で最も対称的であるということです:入ってくる呼び出しと出ていく呼び出しを同じ厳密さで扱い、その語彙(ポート・アダプター・ドライビング・ドリブン)は最も具体的で機械的です。
Onion Architecture
Jeffrey Palermo は2008年に Onion Architecture を発表しました。彼は同じ内向き依存ルールを維持しながら、異なる絵を選びました:玉ねぎの層のような同心円です。最も内側のリングはドメインモデル――コアエンティティと値オブジェクト、テクノロジーが一切入り込まない純粋なビジネス概念です。それを囲むのがドメインサービスのリングで、複数のドメインオブジェクトを協調させるロジックを持ちながら、外の世界については何も知りません。さらに外側にアプリケーションサービスのリングがあり、ユースケースが生き、オーケストレーションが行われます。最も外側にはインフラストラクチャとUIの詳細――データベース・Webフレームワーク・外部APIがあります。
他の二つほど声高に言わない Onion 独自の強調点は、ドメインモデルとドメインサービスの区別です。Palermo は「Order を表すもの」と「Order を処理するサービス」を混同しがちな .NET チームのために書いていました――これは実際に役立つ分離です。Onion はまたレイヤリングを非常に視覚的にします:任意のクラスのインポートリストを見れば、何をインポートすることが許されているかに基づいて、即座にどのリングに属するかがわかります。ドメインサービスがデータベースライブラリをインポートしようとしていれば、明らかに間違ったリングにいます。
Clean Architecture
Robert C. Martin(Uncle Bob)は2017年に Clean Architecture を出版し、長年かけて磨いてきたアイデアを集大成しました。彼も同じ同心円を描きましたが、各リングに固有の名前と目的を与えました。内側から外側へ:エンティティ(企業全体のビジネスルール――コンピューターがなくても真実であり続けるもの)、ユースケース(アプリケーション固有のビジネスルール――ソフトウェアがユーザーに対して行うべきこと)、インターフェースアダプター(コントローラー・プレゼンター・ゲートウェイ――ユースケースの世界と外の世界の間を翻訳するコード)、そしてフレームワーク&ドライバー(最外リング――データベース・Webフレームワーク・UIライブラリ・デバイスドライバー)。
Clean Architecture が加える最も特徴的なものは、明示的に命名されたユースケースレイヤーです。多くのコードベースでは「ユースケース」は非公式な概念です――十個の異なることをする UserService があるかもしれません。Martin は各ユースケースを一級市民・命名されたアーティファクトにすることを主張します:その名前が文字通りに何をするかを表すクラスまたは関数(PlaceOrder・RegisterUser・GenerateInvoice)。彼はこれを「叫ぶアーキテクチャ」と呼んでいます――新しい開発者がプロジェクト構成を開いたとき、使用しているフレームワークではなく、システムが何をするかを即座に理解できるべきです。Martin はまたバウンダリーの概念――各リング境界での明示的なインターフェースのシーム――と、バウンダリーを越えるデータはシリアライズ可能な単純なデータ構造でなければならず、隠れた振る舞いを持つリッチなドメインオブジェクトであってはならないというアイデアを形式化しました。
Martin の「叫ぶアーキテクチャ」というフレーズは、シンプルな願望を言い表しています:プロジェクトの任意のフォルダーを開けば、テックスタックではなくビジネスの意図が即座に理解できるべきです。PlaceOrder.ts と RegisterUser.ts を含む use-cases/ というフォルダーはその意図を叫んでいます。他のすべてを含む controllers/ というフォルダーは何も有益なことを囁きません。
並べて比較:語彙の対応表
三つすべてについて読む際の最大の実践的なハードルは、重複する概念に異なる言葉を使っていることです。以下が翻訳表です:
| 概念 | Hexagonal(Cockburn) | Onion(Palermo) | Clean(Martin) |
|---|---|---|---|
| 最内コア | ドメイン(特定のリング名なし) | ドメインモデルリング | エンティティリング |
| ビジネスユースケース | 六角形内部のアプリケーションロジック | アプリケーションサービスリング | ユースケースリング(明示的・命名済み) |
| 翻訳レイヤー | アダプター(ドライビング+ドリブン) | インフラストラクチャリング | インターフェースアダプターリング |
| 最外テクノロジー | 六角形の外側(HTTP・DB など) | UI / インフラストラクチャリング | フレームワーク&ドライバーリング |
| 接続のシーム | ポート(ドメインが所有するインターフェース) | 各リング境界の暗黙的なインターフェース | バウンダリー(各境界での明示的なインターフェース) |
| 主な強調点 | 対称ポート;テスト容易性;各アダプターの交換可能性 | 同心円リング;ドメインモデルとドメインサービスの区別 | 明示的な命名済みユースケース;叫ぶ構造;厳密なデータバウンダリー |
| 最適なケース | 多くのインテグレーションがあり、各アダプターを独立して交換できる必要がある場合 | 非自明なサービスオーケストレーションを持つリッチなドメインモデル | 共通語彙と「叫ぶ」フォルダー構造を必要とする大規模チーム |
実践における本当の違い
共通ルールの背後には、実際にコードを書くときに現れる強調点の本当の違いがあります。
Hexagonal は最も機械的です。精確で命名されたスロットを提供します:ここにドライビングアダプター、そこにそれが呼ぶポート、それを処理するユースケース、アウトプットポート、そしてドリブンアダプター。これらのスロットを忠実に従えば、構造はほぼ自分自身を書いてくれます――そして両側が対称であるため、データベースがドメインに侵入しないよう保つ規律は、ドメインがHTTPレイヤーに漏れないよう保つ規律と同じです。トレードオフは、Hexagonal が六角形の内部をどう構成するかを教えてくれないことです――コアにドメインモデルとドメインサービスのようなサブレイヤーを持つべきかどうかは無言です。
Onion は最もレイヤー化されています。Hexagonal が一つのバウンダリー(六角形の内側か外側か)を描くところを、Onion はコアの内部に複数のリングを描きます。ドメインが独自の内部構造を必要とするほど大きい場合に価値があります――純粋なエンティティをそれらを協調させるドメインサービスから区別し、ドメインサービスをユースケースをオーケストレーションするアプリケーションサービスから区別するといった具合に。リングのメタファーは視覚的に直感的ですが、厳格であればあるほど規律が必要です:急いでいるときにリングをまたいで手を伸ばしたくなりますが、Onion は Hexagonal のポート/アダプター語彙のような機械的なチェックを提供しません。
Clean は最も規定的です。Martin はすべてのレイヤーとすべての概念に名前をつけています。これは強みでもあり、議論の元でもあります。強みは、チームが明確な語彙を共有できることです――二人のエンジニアが「ユースケースバウンダリー」について議論するとき、全員が何を意味するかを知っています。議論の元は、規定された構造が小さなアプリケーションには重く感じられることです:すべての機能に明示的な InputBoundary・OutputBoundary・UseCase インターフェースを作るのは、プロトタイプを構築しているときには多くのセレモニーです。Clean Architecture はまたすべてのリング境界をまたぐデータ転送オブジェクト(DTO)の使用を主張しており、コードは増えますがシームが非常に明確になります。その見返りはスケールで現れます――レイヤー間での「リッチなオブジェクト漏れ」を防ぐことが仮定的な問題ではなく本物の問題になるときです。
要約すると:Hexagonal はものをどうつなぎ合わせるかについて最も明確な機械的ガイダンスを与えます。Onion はドメインの最も明確な内部レイヤリングを与えます。Clean は最も明確な語彙と最も意見のあるフォルダー構造を与えます。実際のコードベースのほとんどは、気づかないうちに三つ全部をブレンドしています。
どれを選ぶべきか
三つすべてが同じコアルールを共有しているので、どれを選ぶかは語彙・チームサイズ・状況が求めるニュアンスの問題です。
| チーム&ステージ | 推奨アプローチ | 理由 |
|---|---|---|
| ソロ / 初期スタートアップ | Hexagonal を軽く。データベースと最も変動しやすいベンダーの周りに一〜二つのポート。 | 完全性よりスピードが重要。いくつかのインターフェースで高速テストと交換パスが手に入り、フルセレモニーは後回しでよい。 |
| 小規模チーム・成長中(≈ Series A) | すべてのI/Oに明示的なポートインターフェースを持つ Onion か Hexagonal。 | テストがボトルネックになり始め、新メンバーはドメインを素早く理解する必要がある。リングラベル(またはポート名)で構造が一目でわかる。 |
| 中規模(複数スクワッド) | チーム間の共有契約としての Clean Architecture 語彙。 | バウンダリーが曖昧だとスクワッドが踏み合う。各ユースケースと各バウンダリーを命名することで、チームが独立して作業できる明確なシームができる。 |
| 大企業 | 明示的なバウンダリー・バージョン管理された契約・各ポートへの複数アダプターを持つ三つすべて。 | 規制要件・レガシーシステム・マルチベンダー調達はすべて、フル Clean + Hexagonal バウンダリーだけが提供できる明示性を要求する。 |
この表に対応するいくつかの実例パターンを紹介します:
フィンテックスタートアップ(8人のエンジニア)が二つのポート――PaymentGateway と LedgerRepository――を使い、他はすべて直接呼び出しのままにしました。18ヶ月後に決済プロバイダーを乗り換えたとき、新しいアダプタークラス一つと配線の一行変更で済みました。コードベースの他の部分は一切触られませんでした。これが Hexagonal の最もシンプルで最も効果的な姿です。
中規模SaaS企業(60人のエンジニア)が Onion のリングラベルをコードレビューのルールとして採用しました:「ドメインモデルリングにあるものはリング外からインポートしない、例外なし」。新しいエンジニアは最初のPRレビューでバウンダリーポリシーを理解しました。リング語彙がショートカットになり――「ここでリングをまたいでいる」は全員が理解する完結したレビューコメントになりました。
エンタープライズ銀行(数百人のエンジニア・20年来のコードベース)が、命名された UseCase インターフェース・各リング境界での明示的なDTOオブジェクト・各アウトプットポートへの二つのアダプター(旧メインフレーム+新コアバンキングシステムを並行稼働)を持つフル Clean Architecture 語彙を使いました。セレモニーは重かったですが、監査人とコンプライアンスチームがアーキテクチャを仕様書のように読め、バウンダリーがコード内の本物のシームであったためスクワッドが独立してデプロイできました。
本当に悪い結果は、共通の用語集なしに同じチームで三つの名前を互換的に使うことです。チームの半分が「ポート」と呼び、もう半分が「バウンダリー」と呼べば、コードレビューが翻訳作業になります。一つの語彙を選んで書き留め、それを守ってください。基礎となるルール――依存は内向き――こそが重要です。
まとめ
- 一つのルール、三つの図。Hexagonal・Onion・Clean Architecture はすべて同じ依存ルールを強制します:ソースコードの依存は内向き、ビジネスルールへ向かい、フレームワークやI/Oへは向かいません。
- Hexagonal(Cockburn、2005)は最も機械的なガイダンスを与えます:ポートとアダプター、対称的なドライビングとドリブン側。多くのインテグレーションを交換する必要がある場合に最適。
- Onion(Palermo、2008)はドメイン内部に明示的な同心円リングを追加し、ドメインモデルをドメインサービスから、ドメインサービスをアプリケーションサービスから分離します。ドメインが独自の内部構造を必要とするほど大きい場合に最適。
- Clean(Martin、2017)はすべてのレイヤーを命名し、ユースケースを一級市民アーティファクトにします。「叫ぶアーキテクチャ」を追加します――フォルダー構造がビジネスの意図を宣言します。共通語彙と各バウンダリーでの厳密なDTOを必要とする大規模チームに最適。
- これらは競合せず、積み重なります。成熟したコードベースのほとんどは三つすべてをブレンドしています:Hexagonal のポート/アダプター配線、ドメイン内部の Onion リング規律、アプリケーションレイヤーでの Clean ユースケース命名。
- チームに合わせた量で:スタートアップには二ポートの Hexagonal セットアップで十分。フルの Clean セレモニーはエンタープライズスケールで元が取れます。まだ必要でない語彙のコストを払わないでください。
- チームごとに一つの語彙を選んで書き留めてください。名前よりも一貫性が重要です。
このシリーズの次の記事は、三つすべてを可能にするメカニズムについてより深く掘り下げます:Dependency Injection & Inversion of Control――ドメインがフレームワークに一切触れることなく、アダプターをポートに実行時に接続する実践的な配線です。