ひとことで言うと#
アプリケーションの中核ロジックを「ポート(インターフェース)」で囲み、外部との接続を「アダプター」で実装することで、DB・API・UIなどの外部依存を自由に差し替え可能にする設計パターン。別名ヘキサゴナルアーキテクチャ。
押さえておきたい用語#
- Port(ポート)
- アプリケーションの境界に定義するインターフェースのこと。外部とのやり取りの契約を規定する。
- Adapter(アダプター)
- ポートの実装。特定の技術でポートの契約を満たすコンポーネントを指す。
- Driving Adapter(駆動側アダプター)
- アプリケーションを呼び出す側のアダプター。HTTP Controller、CLIコマンド、メッセージコンシューマーなど。
- Driven Adapter(被駆動側アダプター)
- アプリケーションから呼び出される側のアダプター。DBリポジトリ、外部APIクライアント、メール送信など。
- Hexagonal Architecture(ヘキサゴナル アーキテクチャ)
- Ports & Adaptersの別名。六角形の図でアプリケーションの中核を囲む形で表現するためこう呼ばれる。
ポート&アダプターの全体像#
こんな悩みに効く#
- 外部APIの仕様変更でビジネスロジックまで修正が必要になる
- DBを変更したい(MySQL→PostgreSQL等)が影響範囲が大きすぎて踏み切れない
- ドメインロジックのテストに外部サービスのモック設定が大量に必要
基本の使い方#
具体例#
月間5億円のEC。決済プロバイダーの手数料値上げに伴い、Stripe→別プロバイダーへの移行が必要になった。しかし決済ロジックがコントローラーに直接書かれており、移行見積もりは 3ヶ月。
まずPorts & Adaptersで決済周りをリファクタリング。PaymentPortインターフェースを定義し、StripePaymentAdapterとして既存実装を切り出した。
新プロバイダーのNewPaymentAdapterを実装し、DIコンテナの設定を1行変更するだけで切り替え完了。テスト環境での検証を含め、切り替え作業は 1日 で完了。手数料削減効果は年間 1,800万円。
エンジニア40名のBtoB SaaS。MySQLの全文検索性能が限界に達し、PostgreSQLへの移行を検討。しかしSQLが全レイヤーに散在しており、影響範囲の特定に 2週間 かかる見込みだった。
段階的にPorts & Adaptersを導入。Repositoryインターフェース(Port)を定義し、既存のMySQL実装をAdapterとして整理。その後PostgreSQL Adapterを新規実装。
Feature Flagで一部テナントからPostgreSQLに切り替え、問題がないことを確認してから全テナントに展開。移行期間は 6週間 で、ビジネスロジックのコードは 1行も変更なし。
IoTプラットフォームを開発するエンジニア20名の企業。顧客ごとにAWS環境(SQS)とAzure環境(Service Bus)のどちらかにデプロイする必要があり、メッセージング部分のコードが2重管理されていた。
MessageBrokerPortを定義し、SQSAdapterとServiceBusAdapterを実装。デプロイ先の環境変数でアダプターを自動選択する設計にした。
コードの2重管理が解消され、新機能追加時の工数が 1.5倍 → 1倍 に。さらにGCP(Pub/Sub)対応も PubSubAdapter を追加するだけで実現。新規クラウド対応の工数が 3週間 → 3日 に短縮された。
やりがちな失敗パターン#
- すべての外部接続にポートを作る — ログライブラリや設定ファイル読み取りまでポート化するのはやりすぎ。差し替えの可能性がある依存だけにポートを定義する
- ポートの粒度が粗すぎる — 1つのポートにCRUD全操作を詰め込むと、テスト時のモックが複雑になる。ユースケース単位でポートを分ける
- アダプターにビジネスロジックが漏れる — DB Adapterの中で複雑なSQL条件分岐を書くと、ドメインロジックがAdapterに分散する。条件判定はCoreで行い、Adapterはデータの永続化に徹する
- テストでアダプターを差し替えない — Ports & Adaptersの最大のメリットはテスタビリティ。テストで実際にモック/スタブを活用しないと投資対効果が出ない
よくある質問#
Q: ポート(Port)とインターフェース(interface)は同じものですか? A: ポートはアーキテクチャ上の概念で、アプリケーションコアが外部と通信するための「窓口」を指します。実装上はインターフェース(JavaやTypeScriptのinterface、Goのinterface{})として表現されることが多いです。「ポート」はアーキテクチャ上の役割を、「インターフェース」はその実装手段を指すと区別して理解するとよいでしょう。
Q: テスト時にアダプターをどうやって差し替えますか? A: テスト環境ではDI(依存性注入)コンテナや手動でのコンストラクタ注入で、本番のDBアダプターの代わりにインメモリのモックアダプターを注入します。例えばUserRepositoryポートに対して、本番はPostgresAdapter、テストはInMemoryUserRepositoryを渡すことで、DBなしでドメインロジックをテストできます。
Q: レイヤードアーキテクチャとヘキサゴナルアーキテクチャの違いは何ですか? A: レイヤードアーキテクチャは上から下への一方向の依存(UI→Application→Domain→Infrastructure)を定義しますが、Infrastructureが最下層にあるため依存関係逆転を明示しにくいです。ヘキサゴナルは中心にCoreを置き外側をアダプターとして扱うことで、依存がすべてCoreに向かうことを構造的に強制します。DIPがより徹底されています。
Q: すべての外部依存にポートを作るべきですか? A: 必要ありません。差し替えの可能性がある依存(DBやメッセージキューなどのインフラ、外部サービスAPI)にポートを定義してください。ロギングライブラリや設定ファイル読み取りなど差し替えをほぼ想定しないものまでポート化すると過設計になります。「本番とテストで別物を使いたいか?」が判断軸になります。
まとめ#
Ports & Adapters(ヘキサゴナルアーキテクチャ)はアプリケーションの中核をポートで囲み、外部依存をアダプターとして実装する設計パターン。Driving(呼び出す側)とDriven(呼び出される側)の2種類のアダプターで外部接続を整理し、テスト時はアダプターの差し替えだけでドメインロジックを検証できる。DB移行・プロバイダー変更・マルチクラウド対応といった技術選定の変更に強いアーキテクチャを実現する。