ヘキサゴナルアーキテクチャ#
概要#
ヘキサゴナルアーキテクチャ(Hexagonal Architecture)は、Alistair Cockburnによって提唱されたアーキテクチャパターン。ポート&アダプターアーキテクチャとも呼ばれる。
アプリケーションのコアロジックを外部の技術的詳細から分離し、テスタビリティと柔軟性を高めることを目的とする。
主要コンポーネント#
1. アプリケーションコア(Application Core)#
ビジネスロジックとドメインモデル
外部依存を持たない
ポートを通じてのみ外部とやり取り
# ドメインモデル
class Order:
def __init__(self, id, items, total):
self.id = id
self.items = items
self.total = total
def add_item(self, item):
self.items.append(item)
self.total += item.price
2. ポート(Ports)#
入力ポート(Primary/Driving Ports)#
アプリケーションが提供する機能のインターフェース
from abc import ABC, abstractmethod
class OrderService(ABC):
@abstractmethod
def create_order(self, customer_id, items):
pass
@abstractmethod
def get_order(self, order_id):
pass
出力ポート(Secondary/Driven Ports)#
アプリケーションが必要とする外部機能のインターフェース
class OrderRepository(ABC):
@abstractmethod
def save(self, order):
pass
@abstractmethod
def find_by_id(self, order_id):
pass
class PaymentGateway(ABC):
@abstractmethod
def process_payment(self, amount, card_info):
pass
3. アダプター(Adapters)#
入力アダプター(Primary/Driving Adapters)#
外部からのリクエストをポートに変換
# RESTコントローラー
class OrderController:
def __init__(self, order_service: OrderService):
self.order_service = order_service
def create_order_endpoint(self, request):
order = self.order_service.create_order(
request["customer_id"],
request["items"]
)
return {"order_id": order.id, "total": order.total}
出力アダプター(Secondary/Driven Adapters)#
ポートの実装を提供
# データベースアダプター
class PostgresOrderRepository(OrderRepository):
def __init__(self, db_connection):
self.db = db_connection
def save(self, order):
# PostgreSQL固有の実装
query = "INSERT INTO orders (id, items, total) VALUES (?, ?, ?)"
self.db.execute(query, order.id, order.items, order.total)
def find_by_id(self, order_id):
# PostgreSQL固有の実装
result = self.db.query("SELECT * FROM orders WHERE id = ?", order_id)
return Order(**result)
# 外部サービスアダプター
class StripePaymentGateway(PaymentGateway):
def process_payment(self, amount, card_info):
# Stripe API呼び出し
pass
完全な実装例#
# === アプリケーションコア ===
# ドメインモデル
class Order:
def __init__(self, id, customer_id, items, total):
self.id = id
self.customer_id = customer_id
self.items = items
self.total = total
# 出力ポート(インターフェース)
class OrderRepository(ABC):
@abstractmethod
def save(self, order): pass
@abstractmethod
def find_by_id(self, order_id): pass
# 入力ポートの実装(ユースケース)
class OrderServiceImpl(OrderService):
def __init__(self, order_repository: OrderRepository):
self.order_repository = order_repository
def create_order(self, customer_id, items):
total = sum(item.price for item in items)
order = Order(
id=generate_id(),
customer_id=customer_id,
items=items,
total=total
)
self.order_repository.save(order)
return order
def get_order(self, order_id):
return self.order_repository.find_by_id(order_id)
# === アダプター ===
# 入力アダプター(REST API)
class RestOrderController:
def __init__(self, order_service: OrderService):
self.order_service = order_service
def post_order(self, request):
order = self.order_service.create_order(
request["customer_id"],
request["items"]
)
return {"order_id": order.id}
# 出力アダプター(データベース)
class MongoOrderRepository(OrderRepository):
def __init__(self, mongo_client):
self.db = mongo_client.orders
def save(self, order):
self.db.insert_one({
"id": order.id,
"customer_id": order.customer_id,
"items": order.items,
"total": order.total
})
def find_by_id(self, order_id):
result = self.db.find_one({"id": order_id})
return Order(**result) if result else None
メリット#
テスタビリティ
ビジネスロジックを外部依存なしでテスト可能
モックアダプターで簡単にテスト
柔軟性
アダプターの交換が容易(DB、UIなど)
複数のアダプターを同時に使用可能
保守性
明確な境界により変更の影響範囲が限定
ビジネスロジックが技術的詳細から独立
再利用性
コアロジックを異なるインターフェースで再利用
デメリット#
複雑性: 小規模プロジェクトには過剰
学習コスト: 概念の理解に時間がかかる
初期開発コスト: 多くのインターフェースとアダプターが必要
パフォーマンス: 抽象化による若干のオーバーヘッド
クリーンアーキテクチャとの違い#
ヘキサゴナルがざっくり捨象した外部の詳細に踏み込むのがクリーン
項目 |
ヘキサゴナル |
クリーン |
|---|---|---|
層の数 |
2層(コア+アダプター) |
4層(Entity、UseCase、Interface、Framework) |
焦点 |
ポート&アダプターパターン |
依存関係の方向 |
粒度 |
より粗い |
より細かい |
提唱者 |
Alistair Cockburn |
Robert C. Martin |
適用場面#
ヘキサゴナルアーキテクチャが適している場合:
複数のインターフェース(Web、CLI、API、バッチ)が必要
外部システムとの統合が多い
技術スタックの変更が予想される
高いテストカバレッジが必要
ビジネスロジックが複雑