Ответ
Гексагональная архитектура (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.
Ответ 18+ 🔞
А, слушай, про эту вашу гексагональную архитектуру, блядь! Это ж просто пиздец какой-то умный способ сказать: «Отделите вашу ебучую бизнес-логику от всего остального говна, которое её окружает». Ну, типа, чтобы твой код не превращался в спагетти, где база данных, веб-сервер и твоя голова уже на одном проводе висят.
Представь себе, блядь, шестиугольник. В центре — святая святых, ядро. Это твоя бизнес-логика, чистая, как слеза младенца. Она нихуя не знает про то, что там за бортом творится. Никаких «импортов от фласка» или «сессий от алхимии» внутрь не пролезет. Абсолютная девственность, ёпта!
А вокруг этого ядра — порты. Это как дырки в стене, блядь. Через них ядро общается с внешним миром, но только по своим, чётко прописанным правилам. Их два вида:
- Входящие порты: Это когда внешний мир (какой-нибудь долбоёб с HTTP-запросом) стучится к тебе в дверь и говорит: «Эй, зарегистрируй пользователя!». Ядро ему выкидывает контракт: «Ладно, мудила, но только вот таким методом и с такими аргументами».
- Исходящие порты: А это когда само ядро, выполняя свою умную работу, понимает: «Бля, мне надо пользователя в базу сохранить». Но вместо того чтобы тупо вызвать
session.add(), оно орёт в дыру в стене: «Эй, кто там с базой работает? Сохрани-ка мне эту сущность!». И ждёт, что кто-то откликнется по заранее оговорённому интерфейсу.
Ну а адаптеры — это те самые уроды, которые стоят по ту сторону стены и переводят с языка улиц на язык дворца. Входящий адаптер — это твой REST-контроллер, который взял JSON от какого-то левого запроса, разобрал его и красиво постучал во входящий порт. А исходящий адаптер — это твой репозиторий на SQLAlchemy, который, услышав крик ядра из исходящего порта, бежит и делает INSERT INTO users ....
Смотри, как это выглядит в коде, блядь (код не трогаю, он святой):
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")
Видишь? UserService (ядро) нихуя не знает про PostgreSQL. Ему похуй. Ему подсунули какую-то хрень, которая умеет .save(), и он её использует. А завтра, если начальство скажет «переезжаем на MongoDB, блядь», ты просто создашь новый класс MongoUserRepository, который тоже implements этот порт UserRepository, и подсунешь его в сервис. И ядро даже не чихнёт! Оно продолжит работать, как ни в чём не бывало. Волшебство, ёпта!
В чём, сука, профит?
- Тестируемость — овердохуищная. Хочешь протестировать бизнес-логику? Подсовываешь ядру вместо реального репозитория какую-нибудь заглушку (mock), которая нихуя не делает, и проверяешь, правильно ли оно там валидирует и орёт. Никаких поднятий баз данных, никаких фреймворков — чистая логика.
- Заменяемость технологий — пиздец. Надоел PostgreSQL? Заебал Django? В рот их чих-пых! Выкидываешь старые адаптеры и пишешь новые. Ядро, твоя самая ценная часть, остаётся нетронутой, как музейный экспонат.
- Независимость от фреймворков. Твоя бизнес-логика не становится заложником очередного модного веб-фреймворка, который через два года выйдет из моды. Она живёт в своём уютном мирке, а все эти шлюхи-адаптеры бегают вокруг и обслуживают её.
Короче, идея в том, чтобы твоё приложение не превращалось в один большой ком говна, где всё зависит от всего. А чтобы было как у взрослых: чёткие границы, контракты и возможность менять одну хуйню на другую, не перелопачивая половину кодовой базы. Умно, блядь. Хитрая жопа этот ваш шестиугольник.