common-hexagonal-architecture について
ポートとアダプターでドメインロジックとインフラを分離する、六角形アーキテクチャの適用を支援するスキルです。テスト可能なコアや外部連携、明確な依存関係を設計する開発者向けです。
主な機能
- 01入力ポートと出力ポートの定義
- 02アプリケーションコアへの業務ロジック集約
- 03アダプターによるインフラ接続
- 04依存性注入によるコンポーネント構成
- 05アーキテクチャに沿ったテスト
- 06境界の検証と維持
活用シーン
- 01サービスの設計やモジュールの再構成
- 02インフラなしでの業務ロジックのテスト
- 03データベース、API、キューの追加
- 04レイヤー化されたコードの依存関係確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ xip-online-applications/ skills --skill common-hexagonal-architectureインストール後、Claude Code で/common-hexagonal-architectureを入力して読み込まれたか確認します。
詳細
common-hexagonal-architecture
ヘキサゴナルアーキテクチャ(ポート&アダプターとも呼ばれます)を適用し、単一のサービス境界内でドメインロジックをフレームワーク、データベース、メッセージングシステム、UI から独立させます。
使用するタイミング
次の場合にこのスキルを使用します。
- サービスやモジュールの内部アーキテクチャを設計、再構成するとき
- インフラストラクチャへの依存なしにドメイン/ビジネスロジックをテストできるようにするとき
- 新しい外部連携(データベース、API、キュー、ファイルシステム)を導入するとき
- インフラストラクチャ上の関心事がコアに漏れ出していないことを確認するためにコードをレビューするとき
- レイヤー化されたコードベースや「スパゲッティ」状態のコードベースを、より明確な境界に向けてリファクタリングするとき
コアコンセプト
ヘキサゴン(アプリケーションコア)
ヘキサゴンには、すべてのビジネスロジックとドメインルールが含まれます。外部の世界については何も知りません。HTTP、SQL、ファイル I/O は扱いません。ポートを通じてのみ通信します。
ポート
ポートとは、アプリケーションコアが外部の世界とどのようにやり取りするかを定義するインターフェース(契約)です。
| ポートの種類 | 方向 | 目的 |
|---|---|---|
| ドライビングポート(プライマリ) | インバウンド | 外部の世界からコアで呼び出せるユースケースを定義します。 |
| ドリブンポート(セカンダリ) | アウトバウンド | コアが外部システムに必要とするものを定義します。 |
- ドライビングポートは、アプリケーションコア(ユースケース/アプリケーションサービス)によって実装されます。
- ドリブンポートは、インフラストラクチャレイヤーのアダプターによって実装されます。
アダプター
アダプターは、外部の世界とポートの間を変換します。
| アダプターの種類 | 接続先 | 例 |
|---|---|---|
| ドライビングアダプター(プライマリ) | ドライビングポート | REST コントローラー、CLI ハンドラー、gRPC サーバー、メッセージコンシューマー |
| ドリブンアダプター(セカンダリ) | ドリブンポート | SQL リポジトリ、HTTP クライアント、SMTP メーラー、ファイルストア |
依存関係のルール
依存関係は常に内側を向きます。アダプターはポートに依存し、ポートはコアに属し、コアは外部の何にも依存しません。
[Driving Adapter] --> [Driving Port (interface)]
|
[Application Core]
|
[Driven Port (interface)] <-- [Driven Adapter]
手順
-
駆動ポートをユースケースのインターフェースとして定義します。
- 1 つのユースケース、または一貫性のあるユースケース群ごとに 1 つのインターフェースを定義します。
PlaceOrder、RegisterUser、GenerateReportのように、ドメインのアクションにちなんだ名前を付けます。- ドメインオブジェクトまたは単純な DTO のみを受け取り、返します。フレームワーク固有の型は使いません。
-
アプリケーションサービスで駆動ポートを実装します。
- アプリケーションサービスはヘキサゴンの内側に置きます。
- ドメインオブジェクトを連携させ、駆動ポートを呼び出します。
- インフラストラクチャのロジック(SQL、HTTP 呼び出し、ファイル I/O)は含めません。
-
被駆動ポートをリポジトリ/ゲートウェイのインターフェースとして定義します。
- 外部依存の概念ごとに 1 つのインターフェースを定義します。例:
OrderRepository、PaymentGateway、NotificationSender。 - メソッドのシグネチャは技術ではなくドメインのニーズに合わせます(例:
executeQuery(sql)ではなくfindByCustomerId(id))。 - 被駆動ポートのインターフェースは、ドメインと並ぶアプリケーションコアの中に置きます。
- 外部依存の概念ごとに 1 つのインターフェースを定義します。例:
-
インフラストラクチャで被駆動アダプターを実装します。
- 各被駆動アダプターは、被駆動ポートのインターフェースを 1 つだけ実装します。
- 技術固有のコード(ORM の呼び出し、HTTP クライアント、SDK の使用)をすべて含めます。
- コアに手を加えることなく差し替えられます(例:PostgreSQL から DynamoDB に切り替える)。
-
境界に駆動アダプターを実装します。
- 駆動アダプターは、外部入力(HTTP リクエスト、CLI 引数、キューメッセージ)を受け取ります。
- 入力を駆動ポートへの呼び出しに変換します。
- ポートの応答を外部向けの応答(JSON、終了コード、ACK)に変換します。
- 駆動アダプターにビジネスロジックを含めてはいけません。マッピングと委譲のみを行います。
-
インジェクションで依存関係を接続します。
- コンストラクターインジェクション(またはコンポジションルート/DI コンテナー)を使って、被駆動アダプターのインスタンスをアプリケーションサービスに渡します。
- コンポジションルートは、具象アダプターの実装を認識する唯一の場所です。
- テストでは、同じポートを通じてフェイク/モック/インメモリーのアダプターを注入します。
-
フォルダー/モジュール構成。 ヘキサゴンを明確に反映するようにサービスを整理します。
src/ <service-or-module>/ core/ domain/ model/ # Entities, value objects, aggregates event/ # Domain events service/ # Domain services (pure logic) port/ in/ # Driving port interfaces (use cases) out/ # Driven port interfaces (repositories, gateways) application/ service/ # Application services implementing driving ports dto/ # Input/output DTOs for ports adapter/ in/ web/ # REST/GraphQL controllers cli/ # CLI command handlers messaging/ # Message/event consumers out/ persistence/ # Database repositories http/ # External API clients messaging/ # Event publishers, queue producers filesystem/ # File storage adapters config/ # Composition root, DI wiring, framework bootstrap -
ヘキサゴンに沿ったテスト戦略。
- ユニットテスト:インメモリー/フェイクの被駆動アダプターを使って、ドメインモデルとアプリケーションサービスをテストします。実際の I/O は行いません。
- 統合テスト:実際のインフラストラクチャ(データベース、testcontainers またはサンドボックスを使った外部 API)に対して被駆動アダプターをテストします。
- 受け入れテスト:駆動ポート(または駆動アダプター)をエンドツーエンドで実行し、実際のアダプターまたは実際に近いアダプターを使って、ユースケース全体を検証します。
- テストの大半はコアを対象とするユニットテストにします。高速で決定論的に実行でき、内部のモックは不要です。
-
境界を守ります。
- コアから
adapter/パッケージをインポートしてはいけません。 - アダプターがインポートできるのは
core/port/(および型定義のためのcore/domain/model/)のみです。 - モジュール境界(lint ルール、パッケージの可視性、ArchUnit/Deptrac/dependency-cruiser などのアーキテクチャテスト)でこれを徹底します。
- コアから
-
アーキテクチャを検証します。
- すべての被駆動ポートにフェイクを注入して、任意のユースケースをテストできますか? できない場合は、依存関係が漏れています。
- コアを変更せずに被駆動アダプターを差し替えられますか(例:Postgres をインメモリーストアに切り替える)? できない場合は、ポートの結合度が高すぎます。
- 駆動アダプターに条件分岐を伴うビジネスロジックが含まれていますか? 含まれている場合は、駆動ポートの背後にあるコアへ移します。
- 具象アダプタークラスを参照しているファイルは、コンポジションルートだけですか? そうでない場合は、配線が漏れています。