Pull to refresh

Comments 13

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

Тоже используем у себя более менее это дело. Но по факту оно в андроид разработке давно ушло в сторону как от оригинальной книжки от которой и пошло название, так и от здравого смысла. От той же многомодульности с разбиением на небольшие фичи через api/impl модули пользы куда больше чем от введения юз кейсов с одним методом, реализация которых все что делает - это вызывает репозиторий без всяких доп. действий (который за интерфейсом спрятан). Хотя даже с многомодульностью не так все гладко. Например, встречал когда в каждом фича модуле которому нужна бд - заводится своя база данных, плохой подход не несущий практически никаких плюсов, но убивающий преимущества реляционных баз. В итоге если нужны данные из нескольких фич - их приходится тянуть в память, и уже там связывать, считать и фильтровать. Хотя большую часть этого можно было бы сделать в одном sql запросе, который бы отработал куда быстрее, учитывая индексы и собственно заточенность на работу с данными.

По юз кейсам, частое ограничение не помещать в юз кейсы логику отображения - на мой взгляд сильно лишнее. Да, зависимостей от android sdk конечно не стоит в домен позволять вносить, но вот разгрузить вью модели/презентеры/etc можно отлично. Ведь по сути часть решений по тому что показывать пользователю, а что нет, а так же каким образом - оно вполне себе относится к домену приложения, имхо. К примеру, взять юзкейс/интерактор авторизации. Валидацию полей вполне логично в него поместить. Причем не только по нажатию на кнопку логина, но и просто при изменении полей. Банально отдельный метод в интеракторе за валидацию отвечающий (ибо не вижу смысла в логике валидации этих полей отдельно от процесса авторизации). И даже регистрацию с авторизацией по мне вполне нормально в один юз кейс совместить. То что юз кейсы в 95% случаев банально один метод который банально прокси к репозиторию - очень такое себе.

З.Ы. Вот эти все конвеншены про один метод на юз кейс и т.п. по моему имеют смысл только в командах разработки из десятков людей на проектах на миллионы строк. Когда ресурсов разработчиков реально много, и соответственно нужно максильное единообразие + минимизация конфликтов правок + а/б тесты, а то и транк девелопмент какой используется вовсе. А если вас всего человек 5 а кода пара-тройка сотен тысяч строк то эти ограничения скорее лишнее. Как раз объединение связанной бизнес логики в одном интеракторе будет вполне норм.

Вышло немного сумбурно, сообщение в фоне писал, отвлекаясь постоянно. Но просто наболело.

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

Проект делится на core, feature, domain модули.

Core - это net, db, design (общие вью, темы), resources (общие строки, иконки и т.п.) + utils модуль (всякие удобные экстеншен функции для контекста например - туда закидываем). Плюс возможно еще несколько модулей для общих вещей.

Domain - общие entities, мапперы, если надо + один или несколько модулей с общими юз кейсами, которые мы точно знаем что будут использоваться из больше чем одним фича модулем.

Feature - собственно отдельные модули для отделных фич приложения. Иногда привязанные к экрану, иногда выполняющие общую для разных экранов функциональность, иногда вовсе не связанные с экранами напрямую, сервисы те же, например. Каждый feature модуль делится на подмодули api/impl всегда. В api имеем два пакета (либо два api подмодуля вообще создаем). Requires и provides. Первый описывает то что этой фиче нужно для работы, второй - внешний интерфейс этой фичи который могут другие использовать. "Чище" было бы обойтись только первым, но на практике это не очень удобно. Api модули могут зависеть от entities, и других api модулей. Impl модуль может зависеть только от domain, от core и от api других feature модулей.

Таким образом мы получаем относительно низкое зацепление (coupling) и нормальную связность (cohesion). Плюс получаем снижение когнитивной нагрузки. От того что внешний интерфейс модуля достаточно беден - проще не запутаться в том как его использовать можно и нужно.

При этом нам абсолютно не важно что там внутри конкретного feature модуля (но желательно их делать поменьше, несколько тысяч строк, не больше). Пусть там хоть напрямую вью модель репозитории дергает, хоть там mvi а в соседнем модуле mvvm - разницы никакой, пока вся сложность инкапсулирована внутри небольшого модуля - много проблем она не доставит.

Можно конечно расширить и еще более "чисто" все сделать, например запретить зависеть от core (за исключением, например, дизайна), а получать данные через описание в requires интерфейса репозитория нужного фиче, который бы реализовывался в core data модуле (который уже от api модулей db и net зависел бы). Но как по мне это уже перебор. Редко когда нужно иметь возможность переиспользовать фича модули в других проектах, так что в зависимости на core (на api core модулей, если быть точным) плохого не вижу особо.

З.Ы. Ну и не забывать про главное, про здравый смысл. Например, тот же view/design модуль - хоть и core - но к нему api городить перебор.

В чём прикол делить модуль на подмодули api/impl?

Основная фишка модуля в том, что он собирается отдельно, за счёт этого ускоряется сборка Gradle при изменениях. Сложно представить сценарий, когда меняешь api, но не меняешь impl. Наоборот, конечно, частый случай, но ты серьезно собираешься экономить на пересборке нескольких интерфейсов?

Вот и выходит, что профита 0, а сложность проекта на ровном месте растёт. Проще по пакетам разложить нормально.

При изменении модуля пересобираются все зависимые от него модули. Как раз деление api/impl сборку ускоряет, поскольку impl меняется чаще, а api, как правило, стабилен. В итоге получаем что в случае изменения impl пересобирается только он и app модуль. Даже если от api этого модуля зависят другие feature модули. А если не делить - то пересобираться будет и этот feature модуль, и все от него зависящие.

Про деление core модулей даже не вспоминаю. Если к примеру core.net модуль у нас единый, без деления на api/impl, то при правках в нем будет пересобираться почти целиком все приложение. Если же разделен на api/impl, и правка была только в impl (к примеру баг поправили), то пересоберутся только два модуля, core.net.impl и app.

Ну и по мне снижение coupling куда важнее чем ускорение сборки. Простым делением на пакеты этого добиться сложнее, проще когда тебя по рукам бьет система сборки/компилятор.

Прикол в том, чтобы не пересобирать модули, которые зависят только от api фичи. Ведь в случае изменения impl, api пересобираться не будет, а значит и модули, которые от него зависят.

А вообще есть ещё подход, когда фичу на три модуля разбивают:

  • api – только публичный интерфейс

  • impl – реализация

  • factory – то от чего зависят модули, которым нужна фича, предоставляет реализацию, через публичные интерфейсы

Смысл такого подхода в том, что если меняем impl, то пересобирается только impl и factory, а пересборка зависящих модулей не происходит, т.к. factory по сути не изменился

Если factory пересобирается, значит он поменялся, не в коде, так в ссылках на методы/классы, R класс, а значит будут пересобраны все модули, котоыре от него зависят. Что-то не понял, в чем прикол выделения factory.

Можете пример привести?

А какие есть книги чтобы получшк изучить архитектуру в андроид? Или обучающие материалы от гугл?

Выглядит как большой не читаемый ккал. Почему в андройд разработке у вас логика не делится на 3 больших типа - 1) бизнес сущности 2) фичи 3) виджеты (сборке первых 2). Это же очевидно нет? Возможно в андрод разработке есть друшие паттерны о которых я незнаю, но у нас на фронте все читаемо и интуитивно понятно что куда

Андроид, iOS или Web разницы нет. Какие у приложения реальные задачи?

  • Загрузить данные с сервера и отобразить на экране. Возможно добавить фильтрацию на стороне клиента.

  • Отобразить состояние загрузки / ошибки, алерты, модалки, менюшки.

  • Валидация ввода пользователя, временная фиксация в памяти, отправка ввода на сервер.

  • Хранение относительно долгосрочного состояния "сессии": всякие токены, настройки темы, профили и тд.

  • В редких случаях манипуляция данными на стороне приложения: обработка фото, видео, работа с файловой системой.

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

Так что на мой взгляд у фронта должен свой Clean Architecture, и так как задача приложения задача это презентация, то и фокус смещается соответствующим образом. Юз кейсом становится не сценарий манипуляции данными: "вот мои входные данные, вот мой контекст, вот мои выходные данные", а сценарий взаимодействия с пользователем: "вот такие данные пользователь ввел, и вот так они отправляются на сервер" & "вот такие данные сервер возвращает, вот так они должны быть отображены пользователю". И ваша жизнь станет проще, а качество решения не изменится никак, разве что будет достигнуто путем приложения меньших усилий.

А если приложение сочетает как презентацию, так и манипуляцию данными, то у каждой из задач будет своя Clean Architecture. Выносим логику обработки данных (фото, видео, файлы, да хоть и логику ишры) на стороне клиента в отдельные классы / пакеты и потом подключаем их как сервисы в слой презентации. Без избыточных абстракций.

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

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

З.Ы.: Почему не со всем согласен, часто, но далеко не всегда нужно строгое разделение и множество абстракций. Команда должна это отслеживать и держать необходимый для них самих уровень абстракции. В общем как всегда - суть в балансе, который индивидуален для команды/проекта/направления продукта. Повидал не мало кода, где как раз таки чистую архитектуру воспринимали более важной, по сравнению с самой логикой, в итоге через год невозможно было распутать даже простые обращения к серверу или БД

Мне первый вариант архитектуры понравился больше. Проще, никакого графоманства, все по делу, компактно, легче в поддержке. Все эти модули в андроидах - хайп от лукавого. Что такое модуль - по сути библиотека. Зачем библиотека - чтобы подключать к проектам. Если он один, то смысла выносить код в модуль нет. Молодеж городит какие-то громоздкие конструкции абсолютно безосновано, прикрывая это ¦Чистая архитектура решает проблемы, связанные с зависимостями, переиспользованием кода и тестированием приложения.¦

Зависимости? пишите грамотно в монолите с соблюдением зависимостей

Переиспользование? нет его, одна программа.

Тестирование? да сколько угодно если грамотно соблюдать зависимости см. пункт 1.

Sign up to leave a comment.

Articles