ヘキサゴナルアーキテクチャとは
ヘキサゴナルアーキテクチャ(ポートとアダプター・パターンとも呼ばれる)は、アプリケーションを六角形として見なし、ポートとアダプターを介して外部世界とのやり取りを表現することで、関心事の分離を促進するソフトウェアアーキテクチャのパターンです。
ヘキサゴナルアーキテクチャは、アプリケーションの中核となるビジネスロジックを集中させ、周辺の関心事から分離し、外部要因が越えることのできない明確な境界を維持します。この分離により、アプリケーションの一部(例えば、データベースやユーザーインターフェース)の変更が他の部分と独立して発生し、アプリケーションの安定性が保たれ、テストやメンテナンスのプロセスが容易になります。
ヘキサゴナルアーキテクチャの原則
この章では、ヘキサゴナルアーキテクチャの基礎となる原則について詳しく説明します。アプリケーション、ポート、アダプター、および六角形の層というキーコンセプトを紹介します。
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
サービスを定義します。
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
(アダプター)を定義します。
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です。ここでは、書店アプリケーションとして、シンプルなコマンドラインインターフェースを定義します。
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.")
テストの実装
ヘキサゴナルアーキテクチャでは、依存関係をテストダブルで置き換えることができるため、テストの作成とメンテナンスが容易です。以下は、在庫に書籍を追加するための単純なテストケースの例です。
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
ファイルを定義します。
from interfaces.interfaces import command_line_interface
if __name__ == '__main__':
command_line_interface()
参考