ヘキサゴナルアーキテクチャの“違和感“を徹底解剖!3つの疑問と本質を図解でスッキリ理解
Back to Topこれは豆蔵デベロッパーサイトアドベントカレンダー2025第8日目の記事です。
ヘキサゴナルアーキテクチャ(Ports & Adapters)って、なんとなくわかった気はするけど、どこか腑に落ちないところありませんか?私の場合はだいたい次の3点でした。
- 「依存は外側 → 内側」と言うけれど入力ポートと実装で依存が逆向きに見えるのはなぜ?それっていいの?
- 入力のアダプタはポートを実装していないのに出力のアダプタはポートを実装しているのはなにか気持ち悪い
- そもそも、オニオンアーキテクチャと何が違うの?
今回の記事は、これらのモヤモヤを整理したときのメモを例を交えながら説明していきたいと思います。
1. 説明の例に使うヘキサゴナル構成
#最初に今回の記事は次に示す「教科書的なヘキサゴナル」なパッケージ構造をもつ Spring Boot のTODOアプリを例に行います。
com.example.todohex
├─ TodoHexApplication … @SpringBootApplication
│
├─ domain … ドメインモデル(純粋Java)
│ └─ Task.java
│
├─ application
│ ├─ port
│ │ ├─ in … 入力ポート(UseCase IF)
│ │ │ ├─ CreateTaskUseCase.java
│ │ │ └─ GetTaskUseCase.java
│ │ └─ out … 出力ポート(Repo/Gateway IF)
│ │ ├─ SaveTaskPort.java
│ │ └─ LoadTaskPort.java
│ └─ service … ユースケース実装
│ └─ TaskService.java
│
├─ adapter
│ ├─ in
│ │ └─ web … RESTアダプタ(入力側)
│ │ ├─ TaskController.java
│ │ ├─ TaskRequest.java
│ │ └─ TaskResponse.java
│ └─ out
│ └─ persistence … 永続化アダプタ(出力側)
│ ├─ TaskEntity.java
│ ├─ SpringDataTaskRepository.java
│ └─ TaskPersistenceAdapter.java
└─ ...
2. 依存が逆になところがあるけどいいの?
#では早速最初のモヤモヤへ。どこが「逆向きに見える」かですが、 教科書的に UseCase と Service を導出すると依存関係[1]が逆になります。例をもとに図にすると次のように赤線の依存関係が右から左になっています。
一方でヘキサゴナルアーキテクチャに対する世の中的な解説では「モジュールの依存は外側 → 内側」といった説明がたくさん出てきます。ここで「えっ、port と service の依存関係って外側から内側の反対に向いているけどこれっていいの?」というモヤモヤが沸いてきます。
そこで、いったん原典に立ち戻りヘキサゴナルアーキテクチャの提唱者であるアリスター・コバーン自身は何といっているかを振り返ると、彼が原典といえる元記事でいっていることは大ざっぱにいうと以下のようなことです。
- アプリケーションは Port を通して外部と会話する
- その Port のプロトコルは「アプリケーションの API という形をとる」
ここでいう「API」は、メソッド呼び出しでもいいし, HTTP でも, メッセージングのプロトコルでもなんでも良い、というかなり抽象的なレベルの話をしていて、少なくとも原典では、
- 「入力ポートを インタフェースと実装クラス に分けろ」
- 「モジュールの依存矢印は 必ず外→内に向けろ」
といった Java 的な「お作法レベル」のことはいっていません。また、最近の彼のスライド版では型付き言語向けに
- “required interface” を宣言しよう
- Port の宣言用フォルダを用意しよう
くらいはいっていますが、依存の矢印ルールそのものには踏み込んでいません。
アリスター・コバーンは依存の方向についてはなにもいっていません。むしろ、ポートにはインタフェースを出せといっているので、ポートとその実装の依存関係が逆になるのは自然です。この話しは「依存は外側のリングから内側のリングへだけ許可すべし」と明確にいっているクリーンアーキテクチャ[2]と同じコンテキストでヘキサゴナルアーキテクチャが語られることから生まれた都市伝説ではないかと思います。
ただ、port.in と service を 1 つの「アプリケーション・コアの塊」として見てしまえば、adapter.in → (port.in+service) → domain という「外→内」の構図となるので、いわゆる、クリーンアーキテクチャの1種とみても問題ないとも思います
3. アダプタってポートを実装しないの?
#次のモヤモヤはこれです。
- in 側の adapter(Controller 等)は port.in を実装していない(赤の依存)
- out 側の adapter(DB や外部API)は port.out を実装している(青の依存)
言葉だけではわかりづらいので図で表すと次のようになっています。
同じアダプタでもポートを実装したりしなかったり、そもそも左右で対称じゃなく、なんとなく気持ち悪いですよね。というか正直これってホントにあってるの?と思ったりしたのは私だけでしょうか?
疑問があったら原典ということでコバーンがなんといっているか再度みてみましょう。
彼は Hexagonal を Ports & Adapters とも呼びますが、ここでいう Port と Adapter は「役割の名前」になっています。
-
Port:
- 「何のための会話か」を表す論理的な接点
-
Adapter:
- その Port を、特定の技術(HTTP / CLI / DB / メール / ファイル…)に接続する変換器
そして彼のスライドではPort を
- Driving Ports(アプリケーションを「駆動する」側)
- Driven Ports(アプリケーションが「駆動される」側)
に分けて説明しています。
この観点で見ると、
- Driving Port 側
- Adapter(UI / REST / Batch …)は「Port の定義に従ってコールするクライアント」
- Driven Port 側
- Adapter(DB / メール / 外部API …)は「Port の定義を満たして処理するサーバ」
になるため、
- in 側の adapter が port を 実装していない
- out adapter が port を 実装 している
という非対称さは実は自然なものとなります。
Port / Adapter という名前は「入力=implements、出力=implements」という構文パターンのことではなく、「会話の目的を表す窓口」と「外界との変換器」という役割を指しているだけ。そう捉えると、実装有無の非対称さはそこまで気にならなくなります。
4. オニオンアーキテクチャと何が違うの?
#最後のモヤモヤはこれです。
結局、ヘキサゴナルとオニオンって、何がどう違うの?
では、その違いが分かるようにそれぞれの全体構造をみてみたいと思います。
まずオニオンアーキテクチャ
#オニオンアーキテクチャをラフに描くと次のような感じになります。
オニオンアーキテクチャの主眼は(今回の図からは分かりづらいですが、、)
- ドメインを中心に同心円状に層を作り
- 依存は外側 → 内側になるようにして
- ドメインを守る
といったところになります。
次にヘキサゴナルアーキテクチャの構造
#対してヘキサゴナル(Ports & Adapters)は、境界(Port)にフォーカスしたアーキテクチャといえます。
こうして2つを並べてみると、ヘキサゴナルアーキテクチャは構造的にオニオンアーキテクチャの application 部分とその境界まわりを port.in / port.out と adapter.in / adapter.out に細かく分解し、入出力の境界(どこから入って、どこへ出ていくか)を強調したものと見ることができます。
つまり一言でいうと:
- オニオンアーキテクチャ:層(Layer)で内側を守るアーキテクチャ
- ヘキサゴナルアーキテクチャ:ポートとアダプタで境界を強調するアーキテクチャ
で、目指しているゴール自体はどちらも
- ドメイン中心
- 外界(UI/DB/外部システム)からの独立
- テスタビリティ向上
といったところでかなり近いです。
構造的な観点では「ヘキサゴナル=オニオンの application+境界部分を、port と adapter に分解して“入出力の境界を強調したバージョン”」といえます。
ただし、オニオンアーキテクチャは外側から内側に向かって層を成していく構造をその特徴にしているのに対して、ヘキサゴナルアーキテクチャは図の青線の関係が表すように外→内→外となる構造にすることで「どこから入って、どこへ出ていくか」を構造として強調することをその特徴としています。よって、もともとのコンセプトは異なるものとなります。
5. おわりに
#ヘキサゴナルアーキテクチャにすることで確かにクリーンなアーキテクチャを実現することはできますが、それにはコストが掛かります。コバーン自身もスライドの中で次のことをいっています。
- 各 Port ごとにフィールドや DI 設定が増える
- 型付き言語では Port 用のインタフェースやフォルダ構成が必要
- Configurator(構成ルート)の設計が必要
つまり、ヘキサゴナルアーキテクチャはきれいさと引き換えにクラスやインタフェースが増えます。個人的にドメインを分離したいだけならオニオンアーキテクチャでも十分なことも多いと思っています。
良いものが常に良い訳ではありません。自分たちが必要としているものはなにか?を考え、それにフィットするアーキテクチャを選択するのがアーキテクチャ設計では重要になります。
