Traffine I/O

日本語

2022-11-19

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

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

ヘキサゴナルアーキテクチャ(ポートとアダプター・パターンとも呼ばれる)は、アプリケーションを六角形として見なし、ポートとアダプターを介して外部世界とのやり取りを表現することで、関心事の分離を促進するソフトウェアアーキテクチャのパターンです。

ヘキサゴナルアーキテクチャは、アプリケーションの中核となるビジネスロジックを集中させ、周辺の関心事から分離し、外部要因が越えることのできない明確な境界を維持します。この分離により、アプリケーションの一部(例えば、データベースやユーザーインターフェース)の変更が他の部分と独立して発生し、アプリケーションの安定性が保たれ、テストやメンテナンスのプロセスが容易になります。

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

この章では、ヘキサゴナルアーキテクチャの基礎となる原則について詳しく説明します。アプリケーション、ポート、アダプター、および六角形の層というキーコンセプトを紹介します。

Hexagonal architecture
Hexagonal Architecture Explained

中心:アプリケーション

ヘキサゴナルアーキテクチャの中心には、アプリケーション(またはドメイン)があります。これにはビジネスルールとソフトウェアのコアロジックがカプセル化されています。アプリケーションは、データベース、ユーザーインターフェース、および第三者サービスなどの外部関心事に依存しません。この機能は重要であり、システムの一部を変更しても、コアアプリケーションに影響を与えずに変更できるようにします。

ポート:内部と外部

アプリケーションはポートを介して外部世界とやり取りします。ポートは、プライマリ(またはドライビング)ポートとセカンダリ(またはドリブン)ポートに分類されることがあります。

プライマリポートは、アプリケーションが外部世界に提供する操作を公開するインターフェースです。アプリケーションがサポートするユースケースを表し、アプリケーションが実行できる操作を定義します。通常、これらはアプリケーション開発者が設計および実装するAPIです。

一方、セカンダリポートは、アプリケーションが外部サービスやコンポーネントとやり取りするために使用するインターフェースです。これは、アプリケーションが機能するために外部世界から必要なものであり、永続化メカニズム、通知、または外部APIなどが該当します。

アダプター:仲介者

ポートはどのようなやり取りが可能かを定義しますが、アダプターがそのやり取りを処理します。アダプターは、アプリケーションと外部世界の間のリンクです。外部世界が使用する形式のデータをアプリケーションが理解できる形式に変換し、逆も行います。さまざまな種類のアダプターがあります。例えば、HTTPリクエストを処理するためのウェブアダプターやデータベースとのやり取りを行うためのデータベースアダプターなどがあります。

六角形の層

アーキテクチャの六角形の各辺はポートを表します。これらのポートとやり取りするアダプターは、六角形の辺に配置されます。この配置により、物理的なアーキテクチャの形状が六角形となり、このパターンに名前が付けられています。

ヘキサゴナルアーキテクチャは、従来の階層型アーキテクチャとは異なり、「上」と「下」の層は存在しません。代わりに、全ての入力と出力が同等に扱われ、六角形の辺に配置されます。これにより、中央のアプリケーションは外部の変更に対して孤立し、影響を受けません。

ヘキサゴナルアーキテクチャの実践的な実装

ヘキサゴナルアーキテクチャを採用したPythonベースのアプリケーションは、次のようなディレクトリ構造で構築することができます。

/bookstore
    /application_core
        __init__.py
        application_core.py
    /ports_and_adapters
        __init__.py
        ports_and_adapters.py
    /interfaces
        __init__.py
        interfaces.py
    /tests
        __init__.py
        tests.py
    main.py

アプリケーションコアの設計

アプリケーションコアは、システムのビジネスルールとロジックをカプセル化します。簡単な書店アプリケーションを考えてみます。BookエンティティとInventoryサービスを定義します。

/application_core/application_core.py

class Book:
    def __init__(self, id, title, author, price):
        self.id = id
        self.title = title
        self.author = author
        self.price = price

class Inventory:
    def __init__(self, repository):
        self.repository = repository

    def add_book(self, book):
        return self.repository.save(book)

ポートとアダプターの作成

ここでは、システムのためにポートとアダプターを作成します。BookRepositoryインターフェース(セカンダリポート)と、そのインターフェースの具体的な実装であるInMemoryBookRepository(アダプター)を定義します。

/ports_and_adapters/ports_and_adapters.py
from abc import ABC, abstractmethod
from application_core.application_core import Book

class BookRepository(ABC):

    @abstractmethod
    def save(self, book: Book):
        pass

class InMemoryBookRepository(BookRepository):

    def __init__(self):
        self.books = {}

    def save(self, book: Book):
        self.books[book.id] = book
        return book

インターフェースの開発

インターフェースは、ヘキサゴナルアーキテクチャにおけるプライマリポートを表します。これは、アプリケーションが外部世界に公開するAPIです。ここでは、書店アプリケーションとして、シンプルなコマンドラインインターフェースを定義します。

/interfaces/interfaces.py
from application_core.application_core import Book, Inventory
from ports_and_adapters.ports_and_adapters import InMemoryBookRepository

def command_line_interface():
    repository = InMemoryBookRepository()
    inventory = Inventory(repository)

    book_id = input("Enter book id: ")
    title = input("Enter book title: ")
    author = input("Enter book author: ")
    price = float(input("Enter book price: "))

    book = Book(book_id, title, author, price)

    inventory.add_book(book)
    print(f"Added book {title} to inventory.")

テストの実装

ヘキサゴナルアーキテクチャでは、依存関係をテストダブルで置き換えることができるため、テストの作成とメンテナンスが容易です。以下は、在庫に書籍を追加するための単純なテストケースの例です。

/tests/tests.py
import unittest
from application_core.application_core import Book, Inventory
from ports_and_adapters.ports_and_adapters import InMemoryBookRepository

class TestInventory(unittest.TestCase):

    def test_add_book(self):
        repository = InMemoryBookRepository()
        inventory = Inventory(repository)
        book = Book('1', 'Test Book', 'Test Author', 9.99)
        inventory.add_book(book)
        self.assertEqual(repository.books['1'], book)

if __name__ == '__main__':
    unittest.main()

ルートディレクトリには、次のようなmain.pyファイルを定義します。

main.py
from interfaces.interfaces import command_line_interface

if __name__ == '__main__':
    command_line_interface()

参考

https://www.arhohuttunen.com/hexagonal-architecture/
https://betterprogramming.pub/a-quick-and-practical-example-of-hexagonal-architecture-in-java-8d57c419250d

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!