Слои, Луковицы, Гексогоны, Порты и Адаптеры — всё это об одном

Перевод статьи Mark Seemann о популярных архитектурах разработки ПО и о том, что между ними общего.

Один из моих читателей спросил меня:
Вернон, в своей книге «Implementing DDD» много говорит об архитектуре Порты и Адаптеры, как о более продвинутом уровне Слоистой Архитектуры. Хотелось бы услышать ваше мнение на этот счёт.
Если не вдаваться в детали, то в своей книге я описываю именно этот архитектурный паттерн, хотя никогда не называю его этим именем.

TL;DR Если применить принцип инверсии зависимостей к слоистой архитектуре, то в конечном счете получим Порты и Адаптеры.

Слоистая Архитектура


В книге я описываю типичные подводные камни, возникающие при работе со Слоистой Архитектурой. Например, популярная ошибка при её построении:

image

Стрелки показывают направление зависимостей, т.е User Interface зависит от Domain, который в свою очередь зависит от Data Access. Это грубое нарушение Принципа Инверсии Зависимостей, т.к Domain зависит от Data Access, но
«Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций»
— Agile Principles, Patterns, and Practices in C#
Зависимости в этой схеме нужно инвертировать следующим образом:

image

Схема выглядит почти так же как и предыдущая, однако, важно заметить, что направления зависимостей изменились, и теперь Data Access зависит от Domain, а не наоборот. А значит, схема соответствует Принципу Инверсии Зависимостей — детали(UI, Data Access) зависят от абстракций(Domain Model).

Луковая Архитектура


Предыдущий пример, довольно прост, так как включает в себя всего три компонента. Давайте представим более реальный проект, в котором был бы соблюден Принцип Инверсии Зависимостей:

image

Несмотря на большое число компонентов, все зависимости направлены внутрь. Если размышлять «слоями», то можно выделить и изобразить их так:

image

Эти слои действительно напоминают слои луковицы, неудивительно что Jeffrey Palermo назвал такую архитектуру Луковая Архитектура.

Принцип Инверсии Зависимостей всё еще соблюден, так как зависимости идут только в одном направлении. Однако, можно заметить, что UI(рыжие) и Data Access(синие) компоненты расположены в одном слое(туда же я добавил несколько желтых компонентов, которые, например, могут символизировать юнит-тесты). Кажется, что где-то здесь допущена ошибка, но на самом деле все верно, потому что все эти компоненты внешнего слоя находятся на границах приложения. Какие-то границы(UI, APIs) смотрят наружу, другие(базы данных, файловые системы) внутрь(операционная система, сервера баз данных).

Как видно из диаграммы, компоненты могут зависеть от других компонентов своего слоя, но значит ли это, что UI компоненты могут напрямую обращаться к компонентам Data Access?

Гексагональная Архитектура


Несмотря на то, что традиционная Слоистая Архитектура пережила пик своей популярности, это не значит что все её принципы потеряли актуальность. Идея позволить UI компонентам обращаться напрямую к Data Acess недопустима. Прямое взаимодействие между ними, может стать причиной изменения данных в обход важной бизнес логики и нарушить консистентность.
Возможно вы заметили, что я сгруппировал оранжевые, желтые и синие компоненты в отдельные группы. Это сделано так, чтобы UI компоненты не зависели и не общались напрямую с компонентами Data Access и наоборот. Давайте, разделим эти группы графически:

image

В итоге получилось ровно шесть разделов (три пустых). Пусть это будет хорошей подводкой к концепции Алистера Кокберна Гексагональная Архитектура(гексагон = шестигранник):

image

Можно подумать, что я сжульничал, создав именно 6 разделов, сделав диаграмму гексагональной, но в этом нет ничего страшного, так как гексагон не имеет ничего общего с Гексагональной Архитектурой(однако, в первоисточнике объясняется, связь между этими понятиями прим.переводчика). Имя паттерна, не отражает его суть. Поэтому мне нравится называть его Порты и Адаптеры.

Порты и Адаптеры


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

image

Компоненты внутри гексагона имеют лишь несколько(или вовсе не имеют) зависимостей друг от друга, тогда как компоненты за гексагоном работают как Адаптеры между внутренними компонентами и границами приложения — его Портами.

Подводя итоги


В своей книге, я не придумывал имени для описываемой мною архитектуры, но по факту получились Порты и Адаптеры. Кроме вышеописанных вариантов, там описаны и другие архитектуры которые следуют Принципу Инверсии Зависимостей, но основа книги «Dependency Injection in .NET» это безусловно Порты и Адаптеры.

Я не использовал названия Луковая Архитектура, Порты и Адаптеры в своей книге, потому что не знал о них в то время. Но сам того не осознавая, я описывал именно эти паттерны. Лишь позже, ознакомившись с ними, я узнал в них свою архитектуру. И это подтверждает, что Порты и Адаптеры — пример настоящего, канонического паттерна, потому что одним из признаков паттерна, является то, что он может проявляться в различных, независимых окружениях и ситуациях, после чего он и «открывается» как паттерн.
  • +10
  • 5,9k
  • 9
Поделиться публикацией
Похожие публикации
Ммм, длинные выходные!
Самое время просмотреть заказы на Фрилансим.
Мне повезёт!
Реклама
Комментарии 9
  • +1
    Вопрос конечно не к переводчику, но пожалуй, тут не хватает упоминания чистой архитектуры Р.Мартина.
    • +1

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


      Мне лично, не нравится этот способ представления. Как подметил автор:


      Однако, можно заметить, что UI и Data Access компоненты расположены в одном слое

      Кампоненты расположенные в одном слое должны иметь полный доступ друг к другу.
      То есть UI имеет полное право обращаться к Data Access, что не правильно само по себе. И ни одна из схем не противоречит этому.


      Понятно, что авторы подразумевали нечто другое, но это савсем не очевидно из схемы.


      А вот схема слоистой архитектуры достаточно наглядно даёт понять невозможность взаимодействия UI и Data Access компонентов.


      слоистая архитектуру

      • 0
        невозможность взаимодействия UI и Data Access компонентов.

        простите, но цель же разделить их а не запретить взаимодействовать. Я не вижу ничего плохого в том, что для некоторых UI задач не нужно затрагивать слой бизнес логики (это даже я бы сказал логично).

        • 0

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


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


          Тут наверное стоит упомянуть о CQRS, который вы видимо подразумевали.


          Да, в CQRS мы разделяем read и write потоки, но во первых это делается с целью оптимизации нагрузки и новички обычно этим не занимаются, а во вторых, все описанные архитектурные шаблоны (Слои, Луковицы, Гексогоны, Порты и Адаптеры) относятся к write потоку. То есть CQRS это скорей надстройка над архитектурой чем замена.


          CQRS


          Исходя из этого я и хочу сделать акцент на том, что write поток в Data Access из UI в обход Domain должен быть ограничен, а read поток может идти в обход, но это уже совсем другая история.


          И конечно, из любого правила бывают исключения. Бывает, что во write потоке, архитектура со всеми своими наворотами, просто не справляется с нагрузкой и тогда создают отдельный Front controller который без всяких фраймворков и прочего, тупо фигачит данные в хранилище (по сути микросервис).

          • 0
            начать писать SQL запросы в HTML шаблонах

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


            С другой стороны наивный читатель может вдруг вбить себе в голову идею что UI напрямую с persistence работать не должен и потому будет смешивать UI и бизнес логику. Ну то есть следует более простым языком тогда уж объяснять людям в чем смысл разделения ответственности и какие-то простые способы (пусть и не на 100%) их определения получить, а не слои рисовать.


            Тут наверное стоит упомянуть о CQRS, который вы видимо подразумевали.

            я ничего не подразумевал. Простой пример. У нас есть простое приложение гео-трекер. Когда приходит запрос "сохранить текущее положение" оно проходит через слой бизнес логики, делает разные штуки там, считает что ему надо и все. Но если приходит запрос "дай текущее положение" я не вижу смысла дергать для этого бизнес лэйер. Достаточно просто попросить слой хранилище предоставить координаты. Главное сохранить направления зависимостей и все.


            То есть CQRS это скорей надстройка над архитектурой чем замена.

            CQRS это паттерн, суть которого "на чтение и запись у нас есть два разных интерфейса". Там дальше про CQS можно вспомнить но в целом я повторюсь, я CQRS не имел ввиду.


            все описанные архитектурные шаблоны (Слои, Луковицы, Гексогоны, Порты и Адаптеры) относятся к write потоку

            Не совсем, они так же могут быть отнесены к read потоку. CQRS налагает только одно ограничение — читать и писать мы должны с помощью чуть разных штук. Но штуки эти могут иметь одинаковую структуру и разница будет только на уровне зависимостей.


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


            (по сути микросервис).

            больше базвордов богу базвордов. Раз речь зашла — рекомендую следующий видосик: Microservices and Rules Engines – a blast from the past — Udi Dahan

      • 0
        То есть UI имеет полное право обращаться к Data Access, что не правильно само по себе. И ни одна из схем не противоречит этому.

        По идее гексагональная схема как раз и противоречит, если разделять компоненты на каждой грани(что и сделано на одном из рисунков в статье).
        • 0

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


          Я сторонник "Явное лучше не явного" и слоистая архитектура на мой взгляд более явно демонстрирует разделение компонентов на слои.

          • 0
            разделение компонентов на слои.

            но мы же не только на слои дробим приложение, так ведь?

        • 0

          Спасибо за перевод!
          Особенно порадовало


          Я не использовал названия Луковая Архитектура, Порты и Адаптеры в своей книге, потому что не знал о них в то время.

          Не помню кто это сказал, но открытий было бы меньше, если бы читали больше

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.