Найти в Дзене

Как в проекте, построенном на гексагональной архитектуре, осуществляется обработка событий, приходящих в адаптер?

Гексагональная архитектура, или Ports and Adapters, стала популярным подходом к построению гибких и тестируемых систем. Ее ключевая идея — изолировать основную бизнес-логику (домен) от внешних зависимостей (баз данных, пользовательских интерфейсов, очередей сообщений). Но как же данные, например, события из внешней системы, попадают в этот защищенный домен? Именно этот вопрос мы обсудили с командой специалистов: системным аналитиком, архитектором ПО, разработчиком, инженером по программному обеспечению и специалистом по DDD. Путь события: От адаптера к домену через порт Все начинается с адаптера. Адаптер — это компонент, который взаимодействует с внешним миром. Это может быть слушатель Kafka, контроллер REST API, обработчик сообщений из очереди и т.д. Когда адаптер получает событие, его первая задача — преобразовать его из внешнего формата во внутренний формат, понятный домену. Обычно для этого используются DTO (Data Transfer Objects). После преобразования адаптер не вызывает напрямую

Гексагональная архитектура, или Ports and Adapters, стала популярным подходом к построению гибких и тестируемых систем. Ее ключевая идея — изолировать основную бизнес-логику (домен) от внешних зависимостей (баз данных, пользовательских интерфейсов, очередей сообщений). Но как же данные, например, события из внешней системы, попадают в этот защищенный домен? Именно этот вопрос мы обсудили с командой специалистов: системным аналитиком, архитектором ПО, разработчиком, инженером по программному обеспечению и специалистом по DDD.

Путь события: От адаптера к домену через порт

Все начинается с адаптера. Адаптер — это компонент, который взаимодействует с внешним миром. Это может быть слушатель Kafka, контроллер REST API, обработчик сообщений из очереди и т.д. Когда адаптер получает событие, его первая задача — преобразовать его из внешнего формата во внутренний формат, понятный домену. Обычно для этого используются DTO (Data Transfer Objects).

После преобразования адаптер не вызывает напрямую доменную логику. Вместо этого он вызывает порт. Порт в гексагональной архитектуре — это интерфейс, который определяет контракт взаимодействия с доменом. Как отметил архитектор ПО, важно, чтобы этот контракт отражал бизнес-намерение ("processOrderEvent"), а не технические детали ("handleKafkaMessage").

Реализация этого порта находится уже в доменном слое. Это может быть:

  • Непосредственно агрегат (для простых событий).
  • Сценарий использования (use case), если обработка требует координации нескольких агрегатов.
  • Domain service, если логика обработки сложная.

Таким образом, адаптер знает только о порте (интерфейсе), а домен знает только о порте (интерфейсе). Ни адаптер, ни домен не знают о конкретной реализации друг друга. Это и есть ключевой принцип гексагональной архитектуры: сохранение границ и инверсия зависимостей.

Первичные и вторичные порты: Направление потока данных

В ходе обсуждения возник вопрос о типах портов. Специалист по DDD подтвердил, что в гексагональной архитектуре принято различать:

  • Первичные (входящие) порты: Используются для взаимодействия "извне в домен". Адаптеры вызывают эти порты, чтобы передать команды или запросы в домен. Пример: OrderProcessingPort.
  • Вторичные (исходящие) порты: Используются доменом для взаимодействия "из домена наружу". Домен вызывает эти порты, чтобы получить доступ к внешним сервисам, таким как базы данных или другие API. Пример: OrderRepositoryPort.

Направление зависимости здесь критично: для первичных портов зависимость идет от адаптера к домену (адаптер → порт → домен), а для вторичных — от домена к адаптеру (домен → порт → адаптер). Это гарантирует, что домен остается чистым и не зависит от конкретных внешних реализаций.

Важные аспекты реализации и масштабирования

Команда также обсудила ряд важных аспектов, поднятых ведущим:

  1. Терминология: Важно договориться о единой терминологии в команде. Хотя "первичные/вторичные" и "входящие/исходящие" часто используются как синонимы, специалист по DDD предложил более однозначные термины "driving ports" (для команд/запросов) и "driven ports" (для доступа к внешним сервисам).
  2. Масштабирование адаптеров: При большом количестве источников событий слой адаптеров может разрастаться. Решения включают группировку адаптеров по источникам или bounded context'ам, а также вынесение общей логики в shared-библиотеки.
  3. Тестируемость: Гексагональная архитектура значительно упрощает тестирование. Домен тестируется изолированно, мокая адаптеры через порты. Адаптеры тестируются отдельно на корректность преобразования данных. Интеграционные тесты проверяют связку адаптер-порт-домен.
  4. Производительность: Преобразование DTO обычно не является узким местом. Основные накладные расходы, как правило, связаны с доменной логикой. При необходимости можно оптимизировать маппинг или использовать более эффективные протоколы.
  5. Асинхронность: Обработка асинхронных событий в целом следует той же схеме. Важно, чтобы домен был потокобезопасным. Для long-running процессов можно использовать исходящие порты с коллбэками. Разработчик упомянул паттерн "получить-обработать-подтвердить" для надежной обработки сообщений из очередей.

Обработка ошибок: Разделение ответственности

Инженер по программному обеспечению подчеркнул важность правильной обработки ошибок. Адаптер должен ловить технические ошибки и преобразовывать доменные исключения в соответствующие технические ответы (например, HTTP-коды). Домен же должен бросать четкие бизнес-ориентированные исключения, не заботясь о том, как они будут обработаны снаружи. Это разделение ответственности помогает поддерживать чистоту архитектуры.

Заключение

Обработка событий в гексагональной архитектуре — это четко определенный процесс, основанный на принципах инверсии зависимостей и разделения ответственности. События попадают в домен через первичные порты, вызываемые адаптерами. Домен обрабатывает их, используя бизнес-логику, и при необходимости взаимодействует с внешним миром через вторичные порты. Такой подход обеспечивает высокую тестируемость, гибкость и масштабируемость системы, сохраняя доменное ядро чистым от технических деталей.