Научная статья на тему 'МНОГОМОДУЛЬНОСТЬ В ANDROID С ТОЧКИ ЗРЕНИЯ АРХИТЕКТУРЫ. ОТ А ДО Я. ЧАСТЬ ПЕРВАЯ'

МНОГОМОДУЛЬНОСТЬ В ANDROID С ТОЧКИ ЗРЕНИЯ АРХИТЕКТУРЫ. ОТ А ДО Я. ЧАСТЬ ПЕРВАЯ Текст научной статьи по специальности «Философия, этика, религиоведение»

CC BY
195
43
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
Android / Многомодульность / Dependency Injection / Dagger2 / Жизненный цикл / Java.

Аннотация научной статьи по философии, этике, религиоведению, автор научной работы — Мацюк Евгений Викторович

в статье анализируются проблемы, которые возникают при построении многомодульного Android-проекта, влияние этих проблем на Dependency Injection, и первые решения обозначенных проблем.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

Текст научной работы на тему «МНОГОМОДУЛЬНОСТЬ В ANDROID С ТОЧКИ ЗРЕНИЯ АРХИТЕКТУРЫ. ОТ А ДО Я. ЧАСТЬ ПЕРВАЯ»

МНОГОМОДУЛЬНОСТЬ В ANDROID С ТОЧКИ ЗРЕНИЯ АРХИТЕКТУРЫ. ОТ

А ДО Я. ЧАСТЬ ПЕРВАЯ Мацюк Е.В.

Мацюк Евгений Викторович - эксперт-разработчик, направление: Android, Google, г. Москва

Аннотация: в статье анализируются проблемы, которые возникают при построении многомодульного Android-проекта, влияние этих проблем на Dependency Injection, и первые решения обозначенных проблем.

Ключевые слова: Android, Многомодульность, Dependency Injection, Dagger2, Жизненный цикл, Java.

Не так давно мы с вами осознали, что мобильное приложение — это не просто тонкий клиент, а это действительно большое количество самой разной логики, которое нуждается в упорядочивании. Именно поэтому мы прониклись идеями Clean architecture, прочувствовали, что такое DI, научились использовать Dagger 2, и теперь с закрытыми глазами способны разбить любую фичу на слои.

Но мир не стоит на месте, и с решением старых проблем приходят новые. И имя этой новой проблемы — мономодульность. Обычно об этой проблеме узнаешь, когда время сборки улетает в космос. Именно так и начинаются многие доклады про переход на многомодульность ([1], [2]).

Но почему-то все при этом как-то забывают, что мономодульность сильно бьет не только по времени сборки, но и по вашей архитектуре. Вот ответьте на вопросы. На сколько у вас AppComponent большой? Не встречаете ли вы периодически в коде, что фича А зачем-то дергает репозиторий фичи Б, хотя вроде такого быть не должно, ну или оно должно быть как-то более верхнеуровнево? Вообще у фичи есть какой-то контракт? А как вы организовываете общение между фичами? Есть какие-то правила?

Вы чувствуете, что мы решили проблему со слоями, то есть вертикально все вроде хорошо, но вот горизонтально что-то идет не так? И просто разбиением на пакеты и контролем на ревью не решить проблему.

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

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

Отвечая на первый вопрос, на сколько у меня большой AppComponent, я могу признаться — большой, реально большой. И это меня постоянно терзало. Как так вышло? Прежде всего это из-за такой организации DI. Именно с DI мы и начнем.

Как я делал DI раньше

Думаю, у многих в голове сформировалась примерно такая схема зависимостей компонентов и соответствующих скоупов:

©Subcomponent @ScreenScope_1 ScreenComponent_1

©Subcomponent @ScreenScope_2 ScreenComponent_2

©Subcomponent @ScreenScope_3 ScreenComponent_3

©Subcomponent

@FeatureScope_1

FeatureComponent_1

©Subcomponent

@FeatureScope_2

FeatureComponent_2

©Component

@Singleton

AppComponent

Рис. 1. Старый подход к организации DI

Что мы тут имеем

AppComponent, который вбирал в себя абсолютно все зависимости со скоупом Singleton. Думаю, этот компонент есть практически у всех.

FeatureComponents. Каждая фича была со своим скоупом и являлась сабкомпонентом AppComponent или старшей фичи.

Давайте немного остановимся на фичах. Прежде всего, что такое фича? Постараюсь своими словами. Фича — это логически законченный, максимально независимый модуль программы, решающий конкретную пользовательскую проблему, с четко обозначенными внешними зависимостями, и который относительно легко использовать снова в другой программе. Фичи могут быть большими и маленькими. Фичи могут содержать другие фичи. А могут также использовать или запускать другие фичи через четко обозначенные внешние зависимости. Если взять наше приложение (Kaspersky Internet Security for Android), то фичами можно считать Антивирус, Антивор, и т.д.

ScreenComponents. Компонент для конкретного экрана, также со своим скоупом и также являющийся сабкомпонентом от соответствующего фиче-компонента.

Теперь список из «почему так»

Почему сабкомпоненты?

В component dependencies мне не нравилось прежде всего то, что компонент может зависеть сразу от нескольких компонентов, что, как мне казалось, могло в конечном счете привести к хаосу компонентов и их зависимостей. Когда же у тебя строгая зависимость вида «один ко многим» (компонент и его сабкомпоненты), то так безопаснее и очевиднее. Кроме того сабкомпоненту по умолчанию доступны все зависимости родителя, что также вроде удобнее.

Почему на каждую фичу свой скоуп?

Потому что тогда исходил из соображений, что каждая фича — это какой-то свой ЖЦ, который не такой, как у других, поэтому логично создать свой скоуп. Есть еще один момент за много скупов, о котором упомяну ниже.

Так как говорим мы про Dagger 2 в разрезе Clean, то упомяну и про момент, как доставлялись зависимости. В Презентеры, Интеракторы, Репозитории и прочие вспомогательные классы зависимости поставлялись через конструктор. В тестах мы тогда через конструктор подставляем стабы или моки и спокойно тестируем наш класс.

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

Итак, вроде все логично. Но как всегда жизнь вносит свои коррективы.

Жизненные проблемы

Задача-пример

Давайте рассмотрим простой пример из нашего приложения. У нас есть фича Сканирования (Scanner) и фича Антивора (Antitheft). В обеих фичах есть заветная кнопка «Купить». Причем «Купить» — это не просто послать запрос, а еще очень много всякой разной логики, связанной с процессом покупки. Это чисто бизнес-логика с некоторыми диалогами для непосредственной покупки. То есть налицо вполне себе отдельная фича — Покупка (Purchase). Таким образом, в двух фичах нам нужно задействовать третью фичу.

С точки зрения ui и навигации имеем следующую картину. Запускается главный экран, на котором две кнопки:

Й 0 0 0 о а 10:32

dagger_arch

Рмс. 2. Главный экран тестового приложения

По нажатию на эти кнопки мы попадаем на фичу Сканера или Антивора. Рассмотрим фичу Сканера:

а е © ® □ VA 0

dagger_arch

Scanner screen

START ANTIVIRUS SCANNING

BUY ME!

HELP

Рис. 3. Экран Сканера тестового приложения

По нажатию на «Start antivirus scanning» выполняется какая-то работа по сканированию, по нажатию на «Buy me» мы как раз хотим купить, то есть дергаем фичу Покупки, ну а по «Help» — попадаем на простой экран с хэлпом.

Фича Антивора выглядит практически аналогично. Потенциальные решения

Как нам реализовать данный пример с точки зрения DI? Есть несколько вариантов. Первый вариант

Фичу покупки выделить в независимый компонент, зависящий только от AppComponent.

Рис. 4. Вариант с независимым компонентом

Но тогда мы сталкиваемся с проблемой: как в один класс заинжектить зависимости сразу от двух разных графов (компонентов)? Только через грязные костыли, что, конечно, такое себе. Второй вариант

Фичу покупки выделяем в сабкомпонент, зависящий от AppComponent. А компоненты Сканера и Антивора сделать сабкомпонентами уже от компонента Покупки.

©Subcomponent ScannerFeature

©Subcomponent AntitheftFeature

©Subcomponent PurchaseFeature

©Component AppComponent

Рис. 5. Вариант с сабкомпонентом

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

Третий вариант

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

Первый путь

Огавим всем зависимостям фичи Покупки скоуп Singleton и подключаем к AppComponent.

©Subcomponent

@ScannerScope

ScannerFeature

©Subcomponent @AntitheftScope AntitheftFeature

г ©Component ч ©Singleton PurchaseModule

@Singleton

AppComponent

Рис. 6. Вариант с отдельным модулем

Вариант популярный, но влечет к раздуванию AppComponent. В итоге он раздувается в размерах, содержит в себе все классы приложения, и вся суть использования Dagger сводится только к более удобной доставке зависимостей в классы — через поля или конструктор, а не через синглтоны. В принципе, это же и есть DI, но мы упускаем архитектурные моменты, и получается, что все знают обо всех.

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

Второй путь

Это сведение всех фичей к единому скоупу, например PerFeature.

Рис. 7. Вариант с единым ск оупом

Тогда мы сможем даггеровский модуль Покупки подключать к необходимым компонентам легко и просто.

Вроде удобно. Но архитектурно получается не изолированно. Фичи Сканера и Антивора знают абсолютно все о фиче Покупки, все ее потроха. По неосторожности что-то может быть задействовано. То есть у фичи Покупки отсутствует четкий API, граница между фичами размытая, отсутствует четкий контракт. Это плохо. Ну и в многомодульность гредловую будет тяжело потом. Архитектурная боль

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

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

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

• Удобный механизм DI, позволяющий использовать фичи в рамках других фичей (в нашем примере мы хотим в рамках Сканера и Антивора использовать фичу Покупок), без костылизации и боли.

• Тончайший AppComponent.

• Фичи не должны знать об имплементациях других фичей.

• Фичи не должны быть доступны по умолчанию кому угодно, хочется иметь какой-то строгий механизм контроля.

• Возможно отдать фичу в другое приложение с минимальным количеством телодвижений.

• Логичный переход на многомодульность и лучшие практики по этому переходу.

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

Список литературы

1. Marvin Ramin. Modularizing Android Applications. [Электронный ресурс], 2018. Режим доступа: https://www.youtube.com/wateh?v=TWLkswxjSr0/ (дата обращения: 25.10.2021).

2. Тагаков Владимир. Многомодульность и Dagger 2. [Электронный ресурс], 2018. Режим доступа: https://www.youtube.com/watch?v=pMEAD6jjbaI/ (дата обращения: 25.10.2021).

3. Документация по Dagger 2. [Электронный ресурс], 2021. Режим доступа: https://dagger.dev/ (дата обращения: 25.10.2021).

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.