Перевод статьи Mark Seemann о популярных архитектурах разработки ПО и о том, что между ними общего.
Один из моих читателей спросил меня:
Вернон, в своей книге «Implementing DDD» много говорит об архитектуре Порты и Адаптеры, как о более продвинутом уровне Слоистой Архитектуры. Хотелось бы услышать ваше мнение на этот счёт.
Если не вдаваться в детали, то в своей книге я описываю именно этот архитектурный паттерн, хотя никогда не называю его этим именем.
TL;DR Если применить принцип инверсии зависимостей к слоистой архитектуре, то в конечном счете получим Порты и Адаптеры.
Слоистая Архитектура
В книге я описываю типичные подводные камни, возникающие при работе со Слоистой Архитектурой. Например, популярная ошибка при её построении:
Стрелки показывают направление зависимостей, т.е User Interface зависит от Domain, который в свою очередь зависит от Data Access. Это грубое нарушение Принципа Инверсии Зависимостей, т.к Domain зависит от Data Access, но
«Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций»
— Agile Principles, Patterns, and Practices in C#
Зависимости в этой схеме нужно инвертировать следующим образом:
Схема выглядит почти так же как и предыдущая, однако, важно заметить, что направления зависимостей изменились, и теперь Data Access зависит от Domain, а не наоборот. А значит, схема соответствует Принципу Инверсии Зависимостей — детали(UI, Data Access) зависят от абстракций(Domain Model).
Луковая Архитектура
Предыдущий пример, довольно прост, так как включает в себя всего три компонента. Давайте представим более реальный проект, в котором был бы соблюден Принцип Инверсии Зависимостей:
Несмотря на большое число компонентов, все зависимости направлены внутрь. Если размышлять «слоями», то можно выделить и изобразить их так:
Эти слои действительно напоминают слои луковицы, неудивительно что Jeffrey Palermo назвал такую архитектуру Луковая Архитектура.
Принцип Инверсии Зависимостей всё еще соблюден, так как зависимости идут только в одном направлении. Однако, можно заметить, что UI(рыжие) и Data Access(синие) компоненты расположены в одном слое(туда же я добавил несколько желтых компонентов, которые, например, могут символизировать юнит-тесты). Кажется, что где-то здесь допущена ошибка, но на самом деле все верно, потому что все эти компоненты внешнего слоя находятся на границах приложения. Какие-то границы(UI, APIs) смотрят наружу, другие(базы данных, файловые системы) внутрь(операционная система, сервера баз данных).
Как видно из диаграммы, компоненты могут зависеть от других компонентов своего слоя, но значит ли это, что UI компоненты могут напрямую обращаться к компонентам Data Access?
Гексагональная Архитектура
Несмотря на то, что традиционная Слоистая Архитектура пережила пик своей популярности, это не значит что все её принципы потеряли актуальность. Идея позволить UI компонентам обращаться напрямую к Data Acess недопустима. Прямое взаимодействие между ними, может стать причиной изменения данных в обход важной бизнес логики и нарушить консистентность.
Возможно вы заметили, что я сгруппировал оранжевые, желтые и синие компоненты в отдельные группы. Это сделано так, чтобы UI компоненты не зависели и не общались напрямую с компонентами Data Access и наоборот. Давайте, разделим эти группы графически:
В итоге получилось ровно шесть разделов (три пустых). Пусть это будет хорошей подводкой к концепции Алистера Кокберна Гексагональная Архитектура(гексагон = шестигранник):
Можно подумать, что я сжульничал, создав именно 6 разделов, сделав диаграмму гексагональной, но в этом нет ничего страшного, так как гексагон не имеет ничего общего с Гексагональной Архитектурой(однако, в первоисточнике объясняется, связь между этими понятиями прим.переводчика). Имя паттерна, не отражает его суть. Поэтому мне нравится называть его Порты и Адаптеры.
Порты и Адаптеры
В диаграмме выше, мы видим слишком глубокую иерархию зависимостей. Когда диаграмма состояла из четко определенных кругов, у нас было 3 «луковых» слоя.Схема гексагональной зависимости, все еще имеет эти промежуточные (серые) компоненты, но, как я ранее пытался объяснить, плоская иерархия зависимостей, лучше вложенной. Попробуем сделать её максимально плоской внутри гексагона:
Компоненты внутри гексагона имеют лишь несколько(или вовсе не имеют) зависимостей друг от друга, тогда как компоненты за гексагоном работают как Адаптеры между внутренними компонентами и границами приложения — его Портами.
Подводя итоги
В своей книге, я не придумывал имени для описываемой мною архитектуры, но по факту получились Порты и Адаптеры. Кроме вышеописанных вариантов, там описаны и другие архитектуры которые следуют Принципу Инверсии Зависимостей, но основа книги «Dependency Injection in .NET» это безусловно Порты и Адаптеры.
Я не использовал названия Луковая Архитектура, Порты и Адаптеры в своей книге, потому что не знал о них в то время. Но сам того не осознавая, я описывал именно эти паттерны. Лишь позже, ознакомившись с ними, я узнал в них свою архитектуру. И это подтверждает, что Порты и Адаптеры — пример настоящего, канонического паттерна, потому что одним из признаков паттерна, является то, что он может проявляться в различных, независимых окружениях и ситуациях, после чего он и «открывается» как паттерн.
P.S Буду балгодарен за указание на ошибки в переводе, орфографии и пунктуации в ЛС.
Автор: Артур Пантелеев