Шаблон архитектуры, который фокусируется на логических частях, находящихся внутри и вне приложения.

Это первая часть серии статей о шаблоне гексагональной архитектуры, принятом для разработки под iOS. Он охватывает в основном теоретические аспекты. В следующей части будут рассмотрены практические аспекты и пример проекта.
Когда мы говорим об архитектуре приложения для iOS, мы обычно подразумеваем UI как архитектурный подход, такой как MVC, MVVM, VIPER и т. Д. Но архитектура приложения - это не только UI. В каждом таком шаблоне есть часть, называемая «Модель» или «Сущность» в аббревиатуре, которая отвечает за домен или бизнес-логику, интеграцию сторонних зависимостей, взаимодействие системы и фреймворка и т. Д. Вот где гексагональная архитектура может помочь прояснить ситуацию.
Первоначально эта архитектура была объяснена Алистером Кокбурном как Архитектура портов и адаптеров из-за наименования основных компонентов. Термин гексагональная архитектура стал популярным из-за схематического представления того, как компоненты связаны.
Гексагональная архитектура - это архитектурный стиль, который перемещает фокус разработчика с концептуальных уровней на различие между внутренней и внешней частями приложения. Внутренняя часть - это домен или бизнес-логика. Внешняя часть - это пользовательский интерфейс, сеть, датчики, хранилище данных и т. Д. Связь между внутренней и внешней частью приложения осуществляется через порты и их аналоги реализации, называемые адаптерами .
Определения
Модель домена
Модель предметной области - это концептуальная модель, представляющая значимую логику, которая должна быть реализована в программном обеспечении. По сути, это бизнес-логика приложения в сочетании с моделями данных.
Порты
Порт - это независимая от потребителя точка входа и выхода в / из модели предметной области. Это среда, через которую осуществляется доступ к бизнес-логике. В Swift порты - это протоколы, соответствующие вариантам использования в приложении. Порты потребляются адаптерами.
Адаптеры
Адаптер - это мост между моделью предметной области и службой, необходимой приложению. Они действуют как слой, целью которого является обеспечение связи между различными внешними участниками и бизнес-логикой таким образом, чтобы оба оставались независимыми.
В гексагональной архитектуре все внешние субъекты взаимодействуют с портами через адаптеры. Один адаптер может быть службой BLE, отвечающей за взаимодействие с Core Bluetooth iOS framework. Другой может быть функция, подобная контроллеру, которая взаимодействует с удаленным хранилищем.
Типы портов и адаптеров
- Основные или управляющие адаптеры - это те, которые запускают какие-либо действия в модели предметной области. В MVP презентатор будет таким адаптером для взаимодействия с моделью предметной области, то есть с бизнес-логикой приложения. В MVVM это модель представления; в VIPER или RIB - интерактор. В данном случае порт играет роль протокола класса модели предметной области из ядра. Другими словами, эти адаптеры взаимодействуют с ним не напрямую, а через протокол.
- Вторичные или управляемые адаптеры представляют собой подключения к внутренним инструментам, таким как база данных, сетевой API, датчики и т. д. Они напрямую взаимодействуют с собственным или конкретным API и называются моделью предметной области через соответствующий порт. Примером вторичного порта является интерфейс для хранения объектов модели. Этот интерфейс просто объявляет методы для извлечения, обновления и удаления объекта (ов) из хранилища. Он ничего не говорит вам о том, как хранятся эти объекты.

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

Выгоды
- Независимость от внешних служб. Вы можете разработать внутреннее ядро приложения, прежде чем думать о том, какой тип хранилища данных вы собираетесь использовать. Определив порты и адаптеры для вашего хранилища, вы можете использовать любую реализацию технологии.
- Разделение проблем. Внешние слои обычно меняются чаще, чем бизнес-логика. Например, пользовательский интерфейс или сторонний API обычно развивается быстрее, чем бизнес-правила приложения. Это разделение позволяет быстро выполнять итерацию по внешним слоям, не касаясь внутреннего слоя, который должен оставаться согласованным.
- Возможность покрыть бизнес-логику модульными тестами. Теперь, когда модель предметной области вашего приложения, которая инкапсулирует бизнес-логику, не зависит от внешних зависимостей, ее гораздо проще протестировать, имитируя адаптеры.
- Адаптеры сменные. Цель адаптеров - абстрагироваться от фактической реализации внешних сервисов и зависимостей. Эта абстракция позволяет приложению получать запросы и отправлять ответы различным внешним технологиям без необходимости знать фактическую реализацию. Это позволяет заменить адаптер другой реализацией, соответствующей тому же интерфейсу.
- Хороший уровень ремонтопригодности. Ремонтопригодность - это отсутствие технического долга, то есть изменения в одной области приложения не влияют напрямую на другие. Добавление функций не требует больших изменений кода. Вышеупомянутые преимущества в совокупности обеспечивают высокий уровень ремонтопригодности.
Недостатки
- Единственным недостатком, на мой взгляд, является количество сущностей, таких как классы и протоколы, которые необходимо определить для реализации этой архитектуры в проекте. Очевидно, что в подходе без архитектуры контроллер представления может напрямую взаимодействовать с Realm, Firebase или Core Bluetooth. Однако эта конструкция не имеет ни одного из упомянутых выше преимуществ.
Скептически настроенный читатель может сказать, что презентатор, модель представления или интерактор - это на самом деле место для бизнес-логики приложения. И отчасти это правда, но обычно хороший инженер не помещает туда прямое взаимодействие со сторонними или собственными фреймворками. Лучше поместить его на другой уровень, какой-то сервис, чтобы повторно использовать его функциональность, например, сервис API, сервис Firebase, сервис Bluetooth и т. Д. И тогда эти сервисы становятся вместо модели предметной области, в то время как промежуточный уровень между ним и представлением становится переходником. Чтобы покрыть этот уровень модульными тестами, нужно было бы имитировать эти службы и, таким образом, подключаться к ним через протокол, то есть порт.
Эти службы могут напрямую взаимодействовать с внешним уровнем, но тогда они также становятся недоступными для тестирования, если эти соединения не абстрагированы и не могут быть имитированы. Наконец, добавление портов и адаптеров на этой стороне подводит нас к теме этой истории.
В заключение я хотел бы процитировать Роберта К. Мартина с его определением архитектуры из его книги Чистая архитектура:
«Архитектура программной системы - это форма, которую этой системе придают те, кто ее строит. Форма этой формы заключается в разделении этой системы на компоненты, расположении этих компонентов и способах, которыми эти компоненты взаимодействуют друг с другом ».