Почему гексагональная архитектура называется архитектурой портов и адаптеров?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Гексагональная архитектура: почему «порты и адаптеры»?
Гексагональная архитектура (Hexagonal Architecture), предложенная Алистером Кокберном, действительно более точно описывается альтернативным названием — «Архитектура портов и адаптеров» (Ports and Adapters). Это название лучше отражает её суть и механику, в то время как «гексагональная» — это лишь метафора визуального представления. Давайте разберем, почему термины «порты» и «адаптеры» являются ключевыми.
Суть концепции: Изоляция ядра приложения
Основная идея архитектуры — изолировать ядро бизнес-логики (Domain или Application Core) от внешних деталей: пользовательских интерфейсов, баз данных, внешних API, сторонних сервисов и т.д. Это достигается за счет двух ключевых абстракций: портов (Ports) и адаптеров (Adapters).
Что такое порты (Ports)?
Порт — это интерфейс, который определяет контракт взаимодействия между ядром приложения и внешним миром. Это «дверь» или «точка входа/выхода» для ядра. Порты бывают двух типов:
- Первичные (входящие) порты (Driving Ports): Определяют, как внешние акторы (пользователи, системы) могут взаимодействовать с ядром. Обычно это интерфейсы сервисов приложения (Use Cases). Например:
// TypeScript: Первичный порт для работы с заказами interface OrderService { placeOrder(orderData: OrderRequest): Promise<OrderConfirmation>; cancelOrder(orderId: string): Promise<void>; getOrderStatus(orderId: string): Promise<OrderStatus>; }
Ядро реализует этот интерфейс, содержа бизнес-логику.
- Вторичные (исходящие) порты (Driven Ports): Определяют, какие операции ядру требуются от внешних систем (например, для сохранения данных или отправки уведомления). Это интерфейсы репозиториев, шлюзов, клиентов. Например:
// TypeScript: Вторичный порт для хранения данных о заказах interface OrderRepository { save(order: Order): Promise<void>; findById(id: string): Promise<Order | null>; findByUserId(userId: string): Promise<Order[]>; }
Ядро зависит от этого интерфейса, но не от его конкретной реализации.
Что такое адаптеры (Adapters)?
Адаптер — это конкретная реализация, которая «адаптирует» внешнее воздействие или технологию к ожиданиям порта. Адаптер подключается к порту, как штекер в розетку. Соответственно, адаптеры тоже бывают двух видов:
-
Первичные (входящие) адаптеры (Driving Adapters): Внешние акторы обращаются к ядру через них. Они преобразуют внешний запрос (HTTP, CLI-команду, сообщение из очереди) в вызов метода порта ядра.
// TypeScript: HTTP-адаптер (контроллер) для первичного порта OrderService import { Request, Response } from 'express'; class OrderController { constructor(private orderService: OrderService) {} async createOrder(req: Request, res: Response) { const orderData = req.body; // Адаптер преобразует HTTP-запрос в вызов порта ядра const confirmation = await this.orderService.placeOrder(orderData); res.status(201).json(confirmation); } } -
Вторичные (исходящие) адаптеры (Driven Adapters): Реализуют интерфейсы вторичных портов, адаптируя вызовы ядра к конкретной технологии (БД, внешнему API, почтовому сервису).
// TypeScript: Адаптер репозитория для MongoDB, реализующий порт OrderRepository import { MongoClient, Collection } from 'mongodb'; import { OrderRepository, Order } from '../core/domain'; class MongoOrderRepository implements OrderRepository { private orders: Collection; constructor(client: MongoClient) { this.orders = client.db('shop').collection('orders'); } async save(order: Order): Promise<void> { // Адаптер преобразует вызов save() в запрос к MongoDB await this.orders.insertOne(this.toPersistence(order)); } private toPersistence(order: Order) { /*...*/ } private toDomain(doc: any) { /*...*/ } }
Почему это название точнее, чем «гексагональная»?
-
Описывает суть, а не форму: «Гексагональная» — лишь визуальная метафора (ядро в центре, адаптеры на сторонах шестиугольника, символизирующего «разные стороны» подключения). Шестиугольник выбран, чтобы избежать ассоциаций с «верхом» и «низом» (как в слоистой архитектуре). Количество сторон не имеет значения. Название «порты и адаптеры» прямо указывает на архитектурные паттерны, которые используются.
-
Подчеркивает механизм инверсии зависимостей (Dependency Inversion Principle): Зависимости направлены внутрь, к ядру. Внешние слои (адаптеры) зависят от интерфейсов ядра (портов), а не наоборот. Это ключевой принцип, который обеспечивает тестируемость (ядро можно тестировать с mock-адаптерами) и сменяемость технологий (чтобы сменить БД, нужно лишь написать новый адаптер, не трогая ядро).
-
Аналогия с компьютерными портами: Порт USB на ноутбуке — это четкий интерфейс (контракт на напряжение, форму, протокол). К нему можно подключить мышь, флешку или принтер через соответствующие адаптеры (сами устройства). Аналогично, порт в архитектуре — это контракт, а адаптер — его реализация для конкретной «периферии».
Преимущества подхода «Портов и Адаптеров»
- Фокус на бизнес-логике: Разработка ядра ведется в изоляции от инфраструктурных проблем.
- Гибкость и сопровождаемость: Замена внешних компонентов (миграция с REST на GraphQL, с MongoDB на PostgreSQL) требует минимальных изменений.
- Тестируемость: Ядро можно покрыть юнит-тестами, подменяя адаптеры на заглушки (stubs/mocks).
- Параллельная разработка: Команды могут работать над ядром и адаптерами относительно независимо.
Таким образом, «Архитектура портов и адаптеров» — это не просто альтернативное название, а семантически точное описание её фундаментального принципа: ядро системы объявляет порты (интерфейсы), а всё внешнее подключается к ним через адаптеры (реализации). Это делает систему гибкой, устойчивой к изменениям во внешнем мире и сфокусированной на основной ценности — бизнес-логике.