[Анализ и проектирование систем, Разработка под Android] «Оливье в каждой семьей свой»: или как мы придумали ещё одну многомодульную архитектуру

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
24-Июн-2021 14:33

Сегодня я хочу познакомить вас с вариантом построения многомодульной архитектуры под Android. Но сначала обязательно вспомним про понятие Clean Architecture и для чего вообще надо задумываться об архитектуре вашего кода.Зачем нужна архитектура?Какую цель преследует тот, кто когда-либо писал или читал чьи-то доклады по разработке ПО? Обычно это одно из двух: 
  • либо человек хочет решить какую-то конкретную прикладную проблему,
  • либо он хочет, чтобы его продукт был лучше. 
Так вот что же это такое «лучше»? Последнее время этот вопрос всё чаще всплывает в контексте архитектуры программного продукта. Ведь мощная базовая архитектура — важный показатель для масштабируемости приложения. Внесение любых изменений в проект может потребовать переписать приложение практически полностью. В таких случаях код тесно связан. Использование Чистой архитектуры (Clean architecture) помогает решить эту проблему. 
Это одно из самых популярных и часто используемых решений для крупных приложений с большим количеством функций и SOLID-принципами. Подход был предложен Робертом С. Мартином (известным как Дядя Боб) в блоге «Чистый код» в 2012 году.Всем известная схема, которую также иногда называют «Луковицей». 
Источник: https://blog.cleancoder.com/uncle-bob/2012/08/13/th...cture.htmlМногие распространённые архитектуры, такие как MVC, MVP, MVVM, MVI по сути являются вариантами подхода Сlean architecture. Они могут выглядеть запутанно, но разобравшись однажды со всей внутрянкой чистой архитектуры, в дальнейшем у вас не возникнет проблем.Итак, зачем нужен чистый подход?Принципы чистой архитектуры
В основе чистой архитектуры лежит идея разделения кода на слои. Давайте бросим взгляд них.  
Domain-слой: Запускает независимую от других уровней бизнес-логику. В идеале — это чистый пакет Kotlin без android-зависимостей.Data-слой: Отправляет необходимые для приложения данные в domain-слой, реализуя предоставляемый доменом интерфейс.Presentation-слой: Включает в себя как domain-, так и data-слои, а также является специфическим для android и выполняет UI-логику.Исходя из данных принципов, как правило, в проектах делают так: выделяют какие-то общие модули, а остальное организуют одним из двух вариантов:
  • Код содержат в общем модуле application, при этом внутри делят по слоям на пакеты: Domain — Data — Ui.
  • Заводят модуль application и какое-то количество модулей feature, внутри каждого из которых также имеются пакеты с тремя слоями. 
Давайте также обозначим, что здесь имеется в виду под «feature» — это логически законченный, максимально независимый модуль программы, решающий конкретную пользовательскую проблему, с чётко обозначенными внешними зависимостями, и который относительно легко переиспользовать в другой программе. Одно из ключевых выражений в определении фичи — это «с чётко обозначенными внешними зависимостями». Поэтому давайте всё, что мы хотим от внешнего мира для фичи, будем описывать в специальном интерфейсе, например:
Здесь мы говорим о некой фиче, связанной с процессом авторизации. В данном случае мы через интерфейс LoginDataDependencies обозначаем внешние зависимости для модуля фичи. Другая важная составляющая «чистой» фичи — это наличие чёткого API, по которому внешний мир может обращаться к фиче.
Интерфейс  доменного слоя предоставляет для остальных модулей чёткое API — репозиторий со своей функциональностью.  Ниже приведён пример включения данного компонента в контексте Dagger2. 
Здесь я буду приводить примеры кода с использованием Dagger2. Конечно, эти подходы и принципы не исчерпывающие. Ни один принцип не может быть универсальным и подходить для решения любой задачи. Плюс всегда хочется улучшать решения, либо просто поэкспериментировать. Таким образом рождаются новые решения: какие-то подходят для узких кейсов, а какие-то выходят более универсальными. И когда человек знаком с различными вариантами решений — это уже половина успеха, ведь он знает, как другие люди решали подобные задачи и всегда может воспользоваться готовым решением, адаптировав его под себя. Я хотела бы познакомить вас с нашим решением, которое лично для меня стало чем-то новым. До этого меня жизнь не сталкивала и с Dagger 2. Но основным инсайтом для меня стал принцип, который работает совместно с этим фреймворком — я говорю о варианте многомодульной архитектуры приложения Андроид.Рецепт Оливье
Суть идеи, которая отличает её от тех решений, что уже известны мне на текущий момент, в том, что каждую feature нужно делить на три модуля: «Data — Domain — Presentation». Таким образом, если в проекте, допустим, 10 feature, то у нас будет минимум 30 модулей. Почему минимум? Потому что наверняка у каждого в проекте есть какие-то common модули, utils или разделяемые модули, которые мы используем в нескольких проектах. 
Минусы подхода 
После озвучивания варианта многомодульной архитектуры, сразу видны два основных недостатка данного подхода. Для каких-то проектов они будут неприемлемы, но в нашем случае их серьёзность нивелирована плюсами при использовании.Теперь давайте более подробно рассмотрим, о чём же идёт речь, с примерами, картинками и т.д. Но сначала задумайтесь:
Первое, что мне приходит в голову — это логин, и дальнейшая работа с токеном авторизации во всем проекте. Я уверена, что каждый из вас может придумать миллион подобных кейсов, и на любом проекте такая ситуация вряд ли будет единичной. При этом если у вас всё делится по фичам, то, скорее всего, на примере логина вам пришлось бы тянуть весь модуль логина вместе с его UI туда, где просто нужна была бы API/репозиторий логина в какой-то другой модуль. И тогда встаёт вопрос — а зачем нам тянуть это всё, и реализацию с мапперами, ретрофитом, конвертором, который вы используете для json, если нам нужны лишь пара доменных моделей и API? Именно такой кейс и стал основной причиной разделения каждой фичи на три части. Другим примером целесообразности деления можно считать скорость инкрементальной сборки при внесении изменений: представьте, что у вас поменялся endpoint у API, а вам нужно будет пересобирать весь модуль фичи, с UI и прочими штуками? А если у вас там есть какие-то тяжеловесные компоненты? Но если разделить на три части каждую фичу, то при внесении изменений пересобрать придётся только один из трёх модулей! Это же круто! А если у вас при этом модуль ещё тянулся в других фичах, они тоже должны пересобираться. В общем, по цепочке может пойти всё довольно далеко. Да, при этом у нас множится количество модулей — их становится в три раза больше. Но скорость сборки проекта у нас при этом не сильно страдает. Цифрами я тут вас, к сожалению, не порадую, но лид проекта клялся на Котлине, что на предыдущем проекте имел более сотни модулей и сборка не улетала в космос. Поверим ему. Содержимое модулейДалее пробежимся по краткому содержанию каждого из модулей и иллюстрациями соответствующих пакетов в проекте.
В доменном слое у нас находятся абстракции: 
  • корневые/базовые модели (модели бизнес-логики, которые никак не связаны с сетью и отображением на Ui) — data /sealed classes;
  • интерфейс репозитория.
В данном модуле DI не нужен, а в остальных модулях — Data и Ui — у нас, естественно, будет Dagger. 
В дата слое у нас лежат:
  • DTO модели (те модели, которые нам будут приходить по сетевому слою);
  • API; 
  • реализации репозиториев;
  • мапперы из DTO в доменные модели, какие-то дополнительные мапперы, возможно;
  • DI из 3 частей: 
    • Module — для провайда тех сущностей, которые в данном слое будут использоваться;
    • Component, в котором указан наш модуль и Dependency;
    • Dependency модуля (как минимум, это baseUrl для API, скорее всего, различные common провайдеры — клиент Retrofit, парсера для Json вроде Moshi. Возможно, логин/токен providers — всё, что требуется в качестве внешних зависимостей для текущего модуля).

Что касается UI модуля: он у каждой фичи будет свой особенный, но некая общая структура у всех UI модулей, конечно, имеется. Давайте расскажу о ней подробнее. 
  • DI из 4 частей: 
    • Module; 
    • Component; 
    • (опционально) Dependency модуля; 
    • UiChildComponentProvider — если внутри фичи есть хотя бы один экран, который имеет свой DI, то вводим данный интерфейс, который поможет нам работать с «главным экраном» или с «хостовым экраном», о которых я расскажу подробнее чуть дальше. В любом случае он будет реализовывать функции:
fun provide(module: FeatureDataModule): FeatureDataComponent
  • у каждого экрана/фрагмента будет также свой набор DI — модуль, компонент и компонент-провайдер.
Чтобы сориентироваться, давайте бросим взгляд на иерархию зависимостей / вложенностей в рамках Dagger в данном случае:
И в качестве примера  представим, как может выглядеть экран по фрагментам / экранам:
  • Первый — это экран, открываемый из меню. На нём лежит «основной» фрагмент фичи, в котором есть несколько табов. В каждом табе —  дочерние фрагменты со списком элементов, по клику на которые мы переходим в некие «подробности элемента».
  • Второй экран — «подробности». Такой экран в моём примере организован как HostFragment: он представляет из себя единую точку входа для внутреннего контента,  на котором располагается/подменяется внутреннее содержимое. В качестве первого показываемого экрана (а вы помните, тут может быть и некий flow) — фрагмент с ещё одним набором табов (допустим, разделение по разным категориям информации об элементе).
ВыводИтак, зачем нужна модуляризация в приложении?
  • Масштабирование разработки. Подход позволяет горизонтально расширять отдел разработки без особых трудностей: новые сотрудники занимаются новыми модулями в изоляции. 
  • Экономия времени и ресурсов. Когда вам понадобится переиспользовать код в другом продукте, вы сразу оцените скорость, с которой можно это сделать.
  • Синергия между приложениями. Продуктовые запросы обогащают модули функциональностью, которая может быть использована во всех продуктах компании.
  • Качество кода. Как уже говорилось выше, когда модули специализированные, а их интерфейс прост, связность кода становится существенно ниже, как и порог вхождения в проект новых программистов. Также упрощаются поддержка и тестирование кода.
А если я всё ещё вас не убедила перейти в своём проекте на такой вариант архитектуры, то вы можете просто попробовать проектировать так те свои фичи, которые наверняка будут переиспользоваться в других модулях / фичах, чтобы снизить скорость сборки проекта при внесении изменений , применить переиспользование слоёв и устроить хорошее разделение ответственности.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_analiz_i_proektirovanie_sistem (Анализ и проектирование систем), #_razrabotka_pod_android (Разработка под Android), #_android, #_architecture, #_dagger_2, #_kotlin, #_blog_kompanii_epam (
Блог компании EPAM
)
, #_analiz_i_proektirovanie_sistem (
Анализ и проектирование систем
)
, #_razrabotka_pod_android (
Разработка под Android
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 21-Сен 22:21
Часовой пояс: UTC + 5