- PVSM.RU - https://www.pvsm.ru -
20 декабря 2016 года ребята из Uber Engineering опубликовали статью [1] про новую архитектуру (вот перевод [2] этой статьи на хабре). Представляю вашему вниманию перевод основной части документации.
RIBs — кроссплатформенный архитектурный фреймворк от Uber. Он был разработан для больших мобильных приложений с большим количеством вложенных состояний.
При разработке этой структуры инженеры Uber придерживались следующих принципов:
Если вы ранее работали с архитектурой VIPER [6], тогда классы, которые входят в состав RIB, будут выглядеть вам знакомыми. RIB обычно состоят из следующих элементов, каждый из которых реализован в своем классе:

Interactor содержит бизнес-логику. В этом классе происходит подписка на Rx уведомления, принимаются решения об изменении состояния, хранении данных и прикреплении дочерних RIB.
Все операции, выполняемые в Interactor'е, должны быть ограничены его жизненным циклом. В Uber создали инструментарий для обеспечения того, чтобы бизнес-логика выполнялась только при активном взаимодействии. Это предотвращает дезактивацию Interactor'ов, но Rx подписки по-прежнему срабатывают и вызывают нежелательные обновления бизнес-логики или состояния пользовательского интерфейса.
Router отслеживает события от Interactor'а и преобразует эти события в прикрепление и открепление дочерних RIB. Router существует по трем простым причинам:
Builder нужен для того, чтобы создать экземпляры для всех классов, входящих в RIB, а также создать экземпляры Builder'ов для дочерних RIB.
Выделение логики создания классов в Builder добавляет поддержку возможности создания заглушек в iOS и делает остальную часть кода RIB нечувствительной к деталями реализации DI. Builder является единственной частью RIB, которая должна быть осведомлена о системе DI, используемой в проекте. Внедряя другой Builder, можно повторно использовать остальную часть кода RIB в проекте с использованием другого механизма DI.
Presenter это класс без состояния, который транслирует бизнес-модель в модель представления и наоборот. Он может использоваться для облегчения тестирования преобразований модели представления. Однако часто этот перевод настолько тривиален, что он не оправдывает создание отдельного класса Presenter. Если Presenter не сделан, то трансляция моделей представления становится обязанностью View (Controller) или Interactor'а.
View создает и обновляет пользовательский интерфейс. Он включает в себя создание и расположение компонентов интерфейса, обработку взаимодействия с пользователем, заполнение компонентов пользовательского интерфейса данными и анимацию. View предназначена для того, чтобы быть настолько «тупой»(пассивной), насколько это возможно. Они просто отображают информацию. В общем и целом, они не содержат никакого кода, для которого должны быть написаны unit тесты.
Component используется для управления зависимостями RIB. Он помогает Builder'у создавать экземпляры других классов, из которых состоит RIB. Component обеспечивает доступ к внешним зависимостям, необходимым для создания RIB, а также к собственным зависимостям, созданными самим RIB, и контролируют доступ к ним из других RIB. Component родительского RIB обычно внедряется в дочерний RIB-Builder, чтобы предоставить дочернему RIB доступ к зависимостям родительского RIB.
Состояние приложения, в основном, управляется и представлено RIBs, которые в настоящее время подключены к дереву RIB. Например, по мере того, как пользователь переходит через разные состояния в упрощенном приложении для совместных поездок, приложение присоединяет и отделяет следующие RIBs:

RIBs только принимают решения о состоянии в пределах своей компетенции. Например, LoggedIn RIB только принимает решение для перехода между такими состояниями, как Request и OnTrip. Он не принимает никаких решений о том, каким должно быть поведение системы когда мы находимся на экране OnTrip.
Не все состояния могут быть сохранены путем добавления или удаления RIB. Например, когда настройки профиля пользователя изменяются, RIB не привязывается или не отсоединяется. Как правило, мы сохраняем это состояние внутри потоков неизменяемых моделей, которые заново отправляют значения при изменении деталей. Например, имя пользователя может быть сохранено в файле ProfileDataStream, который находится в компетенции LoggedIn. Только сетевые ответы имеют доступ на запись к этому потоку. Мы передаем интерфейс, который обеспечивает доступ на чтение к этим потокам вниз по DI графу.
В RIBs нет ничего такого, что являлось бы истиной в последней инстанции для состояния RIB. Это контрастирует с тем, что более своевольные фреймворки, такие как React, уже предоставляют из коробки. В контексте каждого RIB вы можете выбрать шаблоны, которые способствуют однонаправленному потоку данных, или вы можете позволить состоянию бизнес-логики и состоянию представления временно отклоняться от нормы, чтобы использовать преимущества эффективных фреймворков анимации для платформы.
Когда Interactor принимает решение, связанное с бизнес-логикой, ему может потребоваться сообщить другому RIB о событиях, например о завершении и отправке данных. RIB фреймворк не включает в себя какой-то единственный способ передачи данных между RIB. Тем не менее, этот способ создан для того, чтобы облегчить некоторые общие паттерны.
Как правило, если связь идет вниз к дочернему RIB, то мы передаем эту информацию как события в Rx потоке. Или данные могут быть включены как параметр в метод build() дочернего RIB, и в этом случае этот параметр становится инвариантом для времени жизни дочернего элемента.

Если связь идет вверх по дереву RIB к родительскому RIB Interactor'у, то эта связь сделана через интерфейс слушателя, так как родительский RIB может иметь более длинный жизненный цикл чем дочерний RIB. Родительский RIB, или некоторый объект на его DI графе, реализует интерфейс слушателя и помещает его на свой DI граф, чтобы его дочерние RIB могли его вызывать. Использование этого шаблона для передачи данных вверх вместо того, чтобы родительские RIBs напрямую подписались на Rx потоки своих дочерних RIBs, имеет несколько преимуществ. Он предотвращает утечку памяти, позволяет писать, тестировать и поддерживать родительские RIBs без знания того, какие дочерние RIBs к ним прикреплены, а также уменьшает количество возни, необходимой для прикрепления/отсоединения дочернего RIB. Rx потокам или слушателям не нужно отменять регистрацию или заново регистрироваться при таком методе прикрепления дочернего RIB.

Чтобы обеспечить плавное внедрение архитектуры RIB в приложениях, инженеры Uber создали инструментарий для упрощения использования RIB и использования инвариантов, созданных путем внедрения архитектуры RIB. Исходный код этого инструментария частично был открыт и упоминается в примерах [7] (см.правую часть — прим.пер.).
Инструментарий, который на данный момент имеет открытый исходный код, включает в себя:
Инструментарий, у которого Uber планирует открыть исходный код в будущем:
Нам в sports.ru [11] очень понравился подход инженеров Uber, т.к. мы много раз сталкивались со всеми архитектурными проблемами, которые описывала статья. Несмотря на продуманность, у RIB есть ряд недостатков, например достаточно высокий порог вхождения в архитектуру. Мы разберем более подробно плюсы и минусы архитектуры в следующих статьях, их планируется как минимум две — для iOS и для Android. Для тех, кто хочет погрузиться в RIB прямо сейчас, на странице вики справа есть колонка, в которой есть уроки на английском. От себя замечу, что архитектура явно рождалась в долгих технических дискуссиях и собрала в себе лучшие практики построения архитектур для мобильных приложений, которые есть на данный момент. Ну и напоследок немного пиара — мы в sports.ru [11] тоже любим технические дискуссии, часто проводим технические мастер-классы для коллег, регулярно изучаем новые технологии и в целом у нас классная атмосфера. Так что если вы хотите стать частью нашей команды — welcome [12]!
Автор: svyat_reshetnikov
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android-development/297111
Ссылки в тексте:
[1] статью: https://eng.uber.com/new-rider-app/
[2] перевод: https://habr.com/company/livetyping/blog/320452/
[3] SRP: https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%B5%D0%B4%D0%B8%D0%BD%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8
[4] ReactiveX: http://reactivex.io/
[5] DI: https://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D0%B4%D1%80%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8
[6] VIPER: https://mutualmobile.com/resources/meet-viper-fast-agile-non-lethal-ios-architecture-framework
[7] примерах: https://github.com/uber/RIBs/wiki
[8] Шаблоны кодогенерации iOS для Xcode: https://github.com/uber/RIBs/tree/master/ios/tooling
[9] Плагин кодогенерации для Android: https://github.com/uber/RIBs/tree/master/android/tooling/rib-intellij-plugin
[10] NullAway: https://github.com/uber/NullAway
[11] sports.ru: http://sports.ru
[12] welcome: http://careers.tribuna.digital/
[13] Источник: https://habr.com/post/424305/?utm_campaign=424305
Нажмите здесь для печати.