ヘキサゴナルアーキテクチャ

英語名 Hexagonal Architecture
読み方 ヘキサゴナル アーキテクチャ
難易度
所要時間 設計に1〜2週間
提唱者 アリスター・コーバーン(2005年)
目次

ひとことで言うと
#

アプリケーションの中心に**ビジネスロジック(ドメイン)を置き、外部との接点をポート(インターフェース)とアダプター(実装)**で抽象化することで、DB・UI・外部APIなどの詳細を簡単に差し替えられるアーキテクチャ。別名「ポート&アダプター」。

押さえておきたい用語
#

押さえておきたい用語
ポート(Port)
ドメインが外部とやり取りするためのインターフェース(契約)。「何をするか」だけ定義し、実装の詳細は含まない。
アダプター(Adapter)
ポートの具体的な実装。REST Controller、PostgresRepository、InMemoryRepositoryなど、技術的な詳細を担う。
駆動側ポート(Primary Port)
外部からドメインを呼び出すためのインターフェース。ユースケースの入り口にあたる。
被駆動側ポート(Secondary Port)
ドメインが外部リソースを利用するためのインターフェース。DBやメール送信など出力側にあたる。
依存性注入(Dependency Injection)
アプリケーション起動時にポートに対してアダプターの実装を外部から渡す設計パターン。テスト時のモック差し替えを可能にする。

ヘキサゴナルアーキテクチャの全体像
#

ドメインを中心にポートとアダプターで外部依存を切り離す構造
ドメイン(ビジネスロジック)ビジネスルール・ユースケース外部に一切依存しない純粋なロジックポートを通じて外部と通信駆動側アダプターREST ControllerCLI / gRPCイベントハンドラー被駆動側アダプターPostgresRepositoryEmailSender外部API Gateway駆動側ポートUseCase Interfaceドメイン側に属する被駆動側ポートRepository Interfaceドメイン側に属するテスト時はアダプターをモックに差し替えるだけでドメインを単体テストできる
ヘキサゴナルアーキテクチャの設計フロー
1
ドメイン定義
純粋なビジネスロジックを中心に配置
2
ポート設計
外部接点のインターフェースを定義
3
アダプター実装
ポートの具体的な技術実装を作成
4
DI組み立て
依存性注入でポートとアダプターを接続

こんな悩みに効く
#

  • ビジネスロジックがフレームワークやDBに依存していて、テストが書きにくい
  • 外部サービスを別のものに差し替えたいが、コード全体に影響が出る
  • アプリケーションの構造が明確でなく、どこに何を書くべきか迷う

基本の使い方
#

ステップ1: ドメイン(ビジネスロジック)を定義する

外部に一切依存しない純粋なビジネスロジックを中心に配置する。

  • ドメインオブジェクト、ビジネスルール、ユースケースをここに書く
  • フレームワーク、DB、HTTPなどの技術的な関心事を排除する
  • プレーンなクラスやモジュールで構成する

ポイント: ここだけ見ればビジネスの振る舞いがわかる状態を目指す。

ステップ2: ポート(インターフェース)を定義する

ドメインが外部とやり取りするための**契約(インターフェース)**を定義する。

  • 駆動側ポート(Primary): 外部からドメインを呼び出すためのインターフェース(例: OrderService)
  • 被駆動側ポート(Secondary): ドメインが外部リソースを利用するためのインターフェース(例: OrderRepository)
  • ポートはドメイン側に属する

ポイント: ポートは「何をするか」だけ定義し、「どうやるか」は書かない。

ステップ3: アダプターを実装する

ポートの具体的な実装を外側に作る。

  • 駆動側アダプター: REST Controller、CLI、gRPCハンドラー等
  • 被駆動側アダプター: PostgresOrderRepository、StripePaymentGateway等
  • アダプターはいつでも差し替え可能

ポイント: テスト時はモックアダプターに差し替えるだけで、ドメインを単体テストできる。

ステップ4: 依存性注入で組み立てる

アプリケーションの起動時に、どのアダプターをどのポートに接続するかを設定する。

  • DIコンテナやファクトリーで組み立てる
  • 環境によってアダプターを切り替える(本番: PostgreSQL、テスト: InMemory)
  • 設定は1箇所に集約する

ポイント: 組み立て(Configuration)はアプリのエントリーポイントで行う。ドメインは自分が何に繋がれるか知らない。

具体例
#

例1:通知サービスをヘキサゴナルアーキテクチャで設計する

ドメイン: NotificationService がビジネスルールを持つ。「VIPユーザーには即座に通知、一般ユーザーには1日1回のバッチ通知」のルール。

ポート(駆動側): SendNotificationUseCase インターフェース。外部から通知送信を依頼するための窓口。

ポート(被駆動側): NotificationSender インターフェース、UserRepository インターフェース。

アダプター(駆動側): NotificationController(REST API)、NotificationEventHandler(Kafkaコンシューマー)。

アダプター(被駆動側): EmailNotificationSenderSlackNotificationSenderPostgresUserRepository

テスト時: InMemoryNotificationSenderInMemoryUserRepository に差し替え。DB不要でテスト実行時間が12秒から0.3秒に短縮

後日「LINE通知も追加したい」という要件が来た時、LineNotificationSender アダプターを追加するだけで対応完了。ドメインのコードは一切変更なし。

例2:決済モジュールで外部APIを差し替え可能にする

状況: ECサイトの決済をStripeで処理していたが、手数料削減のためにGMOペイメントへの切り替えを検討。しかし決済ロジックがStripe SDKに密結合しており、移行見積りは3人月。

ヘキサゴナル適用:

  • ポート: PaymentGateway インターフェースを定義(charge()refund()getStatus()
  • アダプター: StripePaymentGateway(既存)とGmoPaymentGateway(新規)を実装
  • DI: 環境変数PAYMENT_PROVIDERで切り替え可能に

結果: GMOアダプターの実装は1人で2週間で完了。ドメインの決済ロジック(金額計算、割引適用、税計算)は一切変更なし。移行見積りが3人月から0.5人月に削減。

例3:レガシーDB移行をゼロダウンタイムで実現する

状況: MySQL 5.7からPostgreSQL 16への移行が必要。全テーブル42本、移行中もサービスは止められない。

ヘキサゴナル設計の活用:

  • 被駆動側ポート OrderRepository に対して、MySqlOrderRepositoryPostgresOrderRepository の2つのアダプターを用意
  • DualWriteOrderRepository を作成:書き込みは両方に行い、読み取りはMySQLから
  • 整合性確認後、読み取りもPostgreSQLに切り替え
  • 最後にMySQLアダプターを削除

結果: ダウンタイムゼロで42テーブルの移行を3週間で完了。問題発生時はDIの設定変更だけでMySQL読み取りに即座にフォールバック可能だったため、チームの心理的安全性も高かった。

やりがちな失敗パターン
#

  1. ドメインに技術的な関心事が漏れ込む — ドメイン層でHTTPステータスコードやSQL文を扱ってしまうと、アーキテクチャの意味がなくなる。ドメインは純粋なビジネスロジックだけにすること
  2. ポートを作りすぎる — すべてのメソッドに個別のインターフェースを定義すると、ファイル数が爆発する。関連する操作はまとめて1つのポートにすること
  3. 小さなプロジェクトにフル適用する — CRUD中心の小規模アプリには過剰設計になる。ビジネスロジックが複雑で、外部依存の差し替えが求められる場合に適用する
  4. テストでのみモックを使い、本番ではDIを使わない — 本番コードでnewしてしまうと、差し替え可能性が失われる。本番でもDIコンテナを通じてアダプターを注入することを徹底する

まとめ
#

ヘキサゴナルアーキテクチャは 「ビジネスロジックを外部依存から守る」 ための設計手法。ポートで契約を定義し、アダプターで具体的な技術を差し込む。テスタビリティと柔軟性が大幅に向上するが、小規模なプロジェクトでは過剰設計になりうる。適用すべきかどうかは、プロジェクトの複雑さに合わせて判断しよう