В чем суть гексагональной архитектуры (порты и адаптеры)

Ответ

Гексагональная архитектура (Hexagonal Architecture), также известная как «Порты и Адаптеры», — это архитектурный паттерн, главная цель которого — изолировать доменную (бизнес-) логику приложения от внешних зависимостей, таких как UI, базы данных, сторонние API и фреймворки.

Ключевые компоненты:

  • Ядро (Core/Domain): Центральная часть приложения, содержащая чистую бизнес-логику. Она не имеет зависимостей от внешних технологий.
  • Порты (Ports): Это интерфейсы (абстрактные классы в Python), которые определяют контракты для взаимодействия с ядром. Делятся на два типа:
    • Входящие (Driving) порты: API, который ядро предоставляет внешнему миру (например, интерфейс сервиса для обработки запросов).
    • Исходящие (Driven) порты: Интерфейсы, которые ядро использует для получения данных извне (например, интерфейс репозитория для работы с БД).
  • Адаптеры (Adapters): Конкретные реализации портов, которые «переводят» запросы из внешнего мира на язык ядра и наоборот.
    • Входящие адаптеры: REST-контроллеры, gRPC-серверы, консольные команды.
    • Исходящие адаптеры: ORM-репозитории, клиенты для внешних API, файловые хранилища.

Пример на Python:

from abc import ABC, abstractmethod

# --- Исходящий Порт (Driven Port) ---
class UserRepository(ABC):
    @abstractmethod
    def save(self, user_name: str):
        pass

# --- Исходящий Адаптер (Driven Adapter) ---
class PostgresUserRepository(UserRepository):
    def save(self, user_name: str):
        # Логика сохранения в PostgreSQL
        print(f"Пользователь '{user_name}' сохранен в PostgreSQL.")

# --- Ядро Приложения (Core) ---
class UserService:
    # Ядро зависит только от абстракции (порта), а не от конкретной реализации
    def __init__(self, user_repo: UserRepository):
        self._user_repo = user_repo

    def register_user(self, user_name: str):
        # Бизнес-логика
        if len(user_name) < 3:
            raise ValueError("Имя пользователя слишком короткое")
        self._user_repo.save(user_name)

# --- Входящий Адаптер (Driving Adapter) - условный REST-контроллер ---
if __name__ == "__main__":
    # В точке входа мы собираем зависимости: ядро и конкретный адаптер
    db_adapter = PostgresUserRepository()
    user_service = UserService(user_repo=db_adapter)

    # Внешний мир (например, HTTP-запрос) вызывает сервис
    user_service.register_user("Alice")

Преимущества:

  • Высокая тестируемость: Ядро можно тестировать в полной изоляции, подменяя адаптеры моками.
  • Заменяемость технологий: Можно легко заменить базу данных (например, с PostgreSQL на MongoDB), просто написав новый адаптер, не трогая бизнес-логику.
  • Независимость от фреймворков: Бизнес-логика не привязана к конкретному веб-фреймворку или ORM.