ヘキサゴナルアーキテクチャ(Ports and Adapters)は、アプリケーション本体と外部技術の接点をPortとして定義し、接続方法をAdapterへ分離する設計です。

PortとAdapter
| 要素 | 役割 | 例 |
|---|---|---|
| Application Core | ユースケースと業務ルール | 注文を作る |
| Input Port | 外部から実行できる操作 | PlaceOrder.execute |
| Driving Adapter | Input Portを呼ぶ | Web Controller、CLI |
| Output Port | Coreが外部へ求める機能 | 注文保存、決済 |
| Driven Adapter | Output Portを実装する | DB、外部API |
WebやCLIはアプリケーションを動かす側なのでDriving Adapter、DBや外部APIはアプリケーションから動かされる側なのでDriven Adapterです。
最小の形
type PlaceOrderInput = {
productId: string;
quantity: number;
};
interface OrderStore {
save(order: Order): Promise<void>;
}
class PlaceOrder {
constructor(private readonly orders: OrderStore) {}
async execute(input: PlaceOrderInput) {
const order = Order.place(input.productId, input.quantity);
await this.orders.save(order);
}
}
PlaceOrderがInput Port、OrderStoreがOutput Portです。HTTP Controllerは入力をPlaceOrderInputへ変換し、DB AdapterはOrderStoreを実装します。CoreはHTTPステータスやORMを知りません。
テストでは、DB Adapterの代わりにメモリ実装を渡せます。
Portを作る判断
すべてのclassにinterfaceを作る必要はありません。外部との境界で、次のどれかがある場所に置きます。
- DBや外部APIを別実装へ置き換える
- 外部技術を使わずにユースケースをテストしたい
- 外部形式をCoreへ漏らしたくない
Adapterには形式変換と通信処理を置き、業務ルールは置きません。
まとめ
- PortはApplication Coreと外部の契約
- Driving AdapterがInput Portを呼ぶ
- CoreはOutput Portを呼び、Driven Adapterが実装する
- interfaceは外部境界へ必要な分だけ置く
依存方向はオニオンアーキテクチャ、層の責務はレイヤードアーキテクチャを参照してください。