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

概要#

ヘキサゴナルアーキテクチャ(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

メリット#

  1. テスタビリティ

    • ビジネスロジックを外部依存なしでテスト可能

    • モックアダプターで簡単にテスト

  2. 柔軟性

    • アダプターの交換が容易(DB、UIなど)

    • 複数のアダプターを同時に使用可能

  3. 保守性

    • 明確な境界により変更の影響範囲が限定

    • ビジネスロジックが技術的詳細から独立

  4. 再利用性

    • コアロジックを異なるインターフェースで再利用

デメリット#

  • 複雑性: 小規模プロジェクトには過剰

  • 学習コスト: 概念の理解に時間がかかる

  • 初期開発コスト: 多くのインターフェースとアダプターが必要

  • パフォーマンス: 抽象化による若干のオーバーヘッド

クリーンアーキテクチャとの違い#

ヘキサゴナルがざっくり捨象した外部の詳細に踏み込むのがクリーン

項目

ヘキサゴナル

クリーン

層の数

2層(コア+アダプター)

4層(Entity、UseCase、Interface、Framework)

焦点

ポート&アダプターパターン

依存関係の方向

粒度

より粗い

より細かい

提唱者

Alistair Cockburn

Robert C. Martin

適用場面#

ヘキサゴナルアーキテクチャが適している場合:

  • 複数のインターフェース(Web、CLI、API、バッチ)が必要

  • 外部システムとの統合が多い

  • 技術スタックの変更が予想される

  • 高いテストカバレッジが必要

  • ビジネスロジックが複雑

参考#