Подтвердить что ты не робот

Порты и адаптеры/гексагональная архитектура - разъяснение условий и реализации

После прочтения различных источников об архитектуре Ports & Adapters, включая оригинальную статью Alistair Cockburn, я все еще не уверен в определенном значении терминов "порт" и "адаптер" - особенно когда речь идет о сопоставлении этих концепций с артефактами реализации.

Некоторые источники (например, этот пост) подразумевают, что порты в этом шаблоне архитектуры являются артефактами на самой внешней стороне, за которыми следуют адаптеры на промежуточном уровне, которые транслируют между портами и приложением, которое находится в основе.

Однако в оригинальной статье Cockburn порты отображаются как снаружи, так и внутри слоя адаптера в зависимости от направления связи:

  • Входящая связь: "Когда в порт поступают события из внешнего мира, адаптер для конкретной технологии преобразует его в полезный вызов процедуры или сообщение и передает его приложению".
  • Исходящая связь: "Когда приложению нужно что-то отправить, оно отправляет его через порт на адаптер, который создает соответствующие сигналы, необходимые для принимающей технологии (человеческой или автоматизированной)".

На самом деле для меня ни подход "все снаружи", ни подход "внутри и снаружи" не имеют смысла - я бы рассматривал порты как артефакты, которые всегда размещаются рядом с приложением независимо от направления связи. Imo это также будет соответствовать метафорам порта и адаптера: например, g. имея устройство с последовательным портом, чтобы подключить к нему другое устройство без последовательного порта, мне понадобится адаптер, который адаптирует входящую и исходящую связь с точки зрения моего устройства.

Переходя к реализации этой архитектуры, я бы воспринимал определение портов скорее как часть моего приложения, где я видел бы различные адаптеры как "вне" моего приложения. Например реализация одного порта может состоять из facade (который будет вызываться адаптерами для входящей связи) и interface (который будет реализован адаптерами для исходящей связи).

Каково правильное значение терминов порт и адаптер и как эти концепции могут быть сопоставлены с артефактами реализации?

ОБНОВИТЬ:

Нашел эту статью, которая напоминает мое понимание. Остается вопрос, существует ли какое-то общее соглашение.

4b9b3361

Ответ 1

inf3rno дал хороший ответ, который уточняет исходный вопрос, но может быть полезно выделить некоторые другие применения для портов и адаптеров.

По моему пониманию порт является выражением вашего интерфейса.

Порт:

  • Определяет экспозицию основной функциональности (для "входящих" портов)
  • Определяет основной вид внешнего мира (для исходящих портов)

Адаптер:

  • Располагается вне компонента (шестиугольник)
  • Используется для обеспечения того, чтобы транспортировка между портом и объектом происходила таким образом, который удовлетворяет контракту с интерфейсом порта.
  • Это то, что вы заменяете (используя инъекцию зависимостей) для проверки шестиугольника

Порт должен принять адаптер и убедиться, что адаптер реализует интерфейс. Затем он должен просто называть соответствующие методы/функции на адаптере.

Порт должен быть включен в тестирование связи. В этом случае то, что "издевается", это ядра двух соседних шестиугольников (или шестиугольник и сервис) и проверьте сборку порта/адаптера/адаптера/порта.

Для получения дополнительной информации вы можете посмотреть разговоры о шестиугольных микросервисах, которые Джеймс Гарднер и я дали в London Skillsmatter Microservices в июле 2014 года.

Ответ 2

Я думаю, что это довольно простая концепция. У вас есть ядро ​​приложения, которое не зависит от чего-либо вне его. Например, это не зависит от инфраструктур HTTP, драйверов баз данных, фреймворков рассылки и т.д. Это ядро ​​имеет очень специфический интерфейс в зависимости от вашего проблемного домена, поэтому код ядра приложения должен меняться, только если ваш проблемный домен изменяется.

Например, у вас есть записи в блогах, и вы хотите добавить к ним категории. Таким образом, категории должны проходить через всю систему, от HTTP-связи до баз данных путем записи и наоборот, читая.

Теперь, если вы хотите заменить свою базу данных MySQL, например, MongoDB, потому что почему бы и нет. Это не должно влиять на ядро, потому что приложение все еще имеет то же самое: оно хранит ваши сообщения в блогах и cc. их категории и возвращает их по требованию. Итак, в этом случае вам нужен только адаптер MondogDB, который вы можете использовать вместо своего адаптера MySQL.

Что, если вы хотите, например, REST API, а не просто простую HTML-страницу? Он по-прежнему не влияет на ваше ядро ​​приложения, поэтому вам нужен еще один адаптер для связи REST.

Итак, на мой взгляд, ваши адаптеры должны иметь определенные интерфейсы, определенные в вашем ядре приложения, а порты должны взаимодействовать с ядром приложения, используя эти адаптеры. Поэтому, по-моему, порты не нужны как классы, просто адаптеры и их интерфейс. Эта концепция имеет смысл, потому что ваше ядро ​​приложения не будет тесно связано с портами, которые вы хотите использовать, только для интерфейсов адаптеров, которые вы определяете. (Между прочим, есть несколько подобных архитектур. Как чистая архитектура, луковая архитектура, использующая ту же концепцию с другим словарем.)

Ответ 3

Кто-то на моей работе сделал большую внутреннюю презентацию по этой архитектуре. В конце, во время вопроса, другой коллега спросил:

Разве это не просто многоуровневая архитектура с другим именем и рисуется по-разному?

И, честно говоря, это правда в значительной степени. Для многих приложений гексагональная архитектура будет структурирована идентично многоуровневой архитектуре с несколькими конкретными деталями:

  • Повышенная дисциплина в определении интерфейсов между каждым слоем (портами), а не вызов impl для impl.
  • Основное внимание уделяется "основной" (бизнес-логике) как наиболее важному слою, причем все остальные слои (адаптеры) рассматриваются как несколько подчиненные.
  • Фокусировка на определении интерфейсов с точки зрения ядра предотвращает утечку языка адаптеров в ядро. Например, если вы переносите свою постоянство (например, Hibernate) в адаптер, тогда у вас не должно быть классов @Entity в вашем ядре.

Вы можете видеть, что даже при выполнении всех этих задач это все еще просто многоуровневая архитектура, только если границы между слоями довольно строги и сосредоточены на центральном уровне.

Итак, чтобы конкретно ответить на этот вопрос, вы можете понимать порты и адаптеры, признавая, что порты являются интерфейсами в и из ядра, а адаптеры - это только уровни реализации, которые не являются ядром.

Ответ 4

С моей точки зрения, после прочтения оригинальной статьи и просмотра некоторых выступлений Алистера Кокурна ("Алистер в гексагоне"), я думаю, что правильный подход - это то, что вы бы назвали "все внутри", то есть как во входящем, так и в исходящем общении, порты находятся "внутри" адаптеров. Адаптеры находятся между внешними субъектами, которые взаимодействуют с приложением, и портами. Порты принадлежат приложению.

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

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

Зависимости должны быть:

Актер водителя → Адаптер водителя → Шестигранник <- Управляемый адаптер <- Управляемый актер

Порты относится к шестиугольникам:

Порты драйверов - это API, предлагаемый адаптерами Hexagon-Driver.

Управляемые порты - это SPI, необходимый для Hexagon, реализованный управляемыми адаптерами.

Я тоже много боролся с этим предложением, которое вы упомянули, которое появляется в оригинальной статье:

"Когда в порт поступают события из внешнего мира, специальный технологический адаптер преобразует его в полезный вызов процедуры или сообщение и передает его приложению".

Это говорит о том, что порты драйверов являются "внешними" адаптерами драйверов. Но читая всю статью и наблюдая за разговорами, я думаю, что это не так. То, что в предложении называется "порт", - это просто взаимодействие между субъектом внешнего драйвера и адаптером. "Порт" должен быть взаимодействием между адаптером и приложением ("... передает его приложению").