ヘキサゴナルアーキテクチャとは、アプリケーション本体を外部のUI、DB、APIから分離し、ポートとアダプターを通じて接続する設計思想です。
一言でいうと
アプリケーションの中心を「外から呼ばれる口」と「外へ出ていく口」で囲む設計です。
「Webから呼ばれる」「CLIから呼ばれる」「DBに保存する」「外部APIへ問い合わせる」といった詳細を、アプリケーション本体から切り離します。
登場人物
| 名前 | 役割 | 例 |
|---|---|---|
| アプリケーションコア | 業務ルールとユースケース | 注文作成、在庫確認 |
| 入力ポート | 外部から使える操作の入口 | CreateOrderUseCase |
| 出力ポート | 外部へ依頼するための抽象 | OrderRepository |
| 入力アダプター | 外部入力をポートへ変換 | Controller、CLI、Queue Consumer |
| 出力アダプター | 出力ポートを実装 | DB実装、HTTP Client、メール送信 |
ポートは「約束」、アダプターは「接続部品」です。
処理の流れ
注文作成APIを例にします。
| 順番 | 処理 |
|---|---|
| 1 | ControllerがHTTPリクエストを受け取る |
| 2 | Controllerが入力データを整える |
| 3 | 入力ポートである注文作成ユースケースを呼ぶ |
| 4 | ユースケースが業務ルールを実行する |
| 5 | ユースケースが出力ポートを通じて保存を依頼する |
| 6 | DBアダプターが実際に保存する |
中心のユースケースは、HTTPで呼ばれたのか、CLIで呼ばれたのかを知りません。DBがPostgreSQLなのか、MongoDBなのかも知りません。
入力ポートと出力ポート
入力ポートは「このアプリで何ができるか」を表します。
interface CreateOrderUseCase {
execute(input: CreateOrderInput): Promise<CreateOrderOutput>;
}
出力ポートは「外部へ何を依頼したいか」を表します。
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
この2つを分けることで、アプリケーションの中心は外部技術に依存しにくくなります。
クリーンアーキテクチャとの違い
| 観点 | ヘキサゴナル | クリーンアーキテクチャ |
|---|---|---|
| 中心 | アプリケーションコア | エンティティ/ユースケース |
| 強調点 | ポートとアダプター | 依存方向と層 |
| 図のイメージ | 六角形の周囲に接続口 | 同心円 |
| 実践の入口 | 外部I/Oを分離する | 層ごとの責務を分ける |
両者は対立するものではありません。どちらも、ビジネスロジックを外部詳細から守るための考え方です。
よくある誤解
すべてにinterfaceが必要
1つしか実装がなく、差し替える予定もないものまで無理に抽象化すると読みにくくなります。外部I/Oやテストで差し替えたい部分から始めるのが現実的です。
Controllerがユースケースの代わりになる
Controllerは入力アダプターです。業務ルールをControllerに書き始めると、HTTP以外から再利用しにくくなります。
DBアダプターに業務ルールを書いてよい
DBアダプターの責務は保存と取得です。割引計算、状態遷移、権限判断などはコア側に置きます。
導入チェックリスト
- ユースケースがHTTPやDBの型を直接受け取っていないか
- 外部API呼び出しを出力ポートとして抽象化できているか
- Controllerに業務判断を書きすぎていないか
- DBなしでユースケースのテストを書けるか
- CLIやバッチから同じユースケースを呼べるか
まとめ
ヘキサゴナルアーキテクチャは、アプリケーションを外部技術から守るための境界設計です。
ポートはアプリの約束、アダプターは外部との接続部品です。この区別ができると、UI、DB、外部APIを変えても中心のユースケースを保ちやすくなります。
参考リソース
- Alistair Cockburn: Hexagonal Architecture
- Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern
- Microsoft: Architecture principles