Pull to refresh

Comments 24

UFO landed and left these words here
Во первых это слишком простой пример чтобы называться чистой архитектурой, во вторых — присутствуют ошибки: 1) BaseViewModel знает об деталях работы с сетевыми запросами. 2) Модели из «сетевого слоя» используются на стороне UI
UFO landed and left these words here
Потому что при изменениях на стороне бэкенда (обновили API) придется вносить изменения в presentation (view, UI) слой. То есть, работать-то это будет, даже в реальном проекте, но называться чистой архитектурой не может.
UFO landed and left these words here

Просто предложенный топикстартером вариант нарушает многие заветы Clean Architecture — поэтому неправильно называть все это "чистой арзитектурой". Но что тогда остается — просто yet another велосипед, коих и так уже не сосчитать. Чтобы разобраться в чистой архитектуре — рекомендую видео одного из ее отцов :) Это будет хорошим введением, но придется еще почитать статей для углубления.

UFO landed and left these words here
Про модели — верно. Каждому слою свои модели, часто обмазанные своими аннотациями (например, для room-энтитей, или для моделей, которые будут сериализовываться gson/moshi/kotlinx-serialization). Между слоями модели гоняются через мапперы. К тому же в моделях респонсов сервера желательно все поля сделать nullable — нельзя доверять тому, что приходит извне.
Для этого есть целый список причин и некоторые не достаточно очевидны из-за скудности примера:
1. В модели из сети данные приходят в одном формате, а на стороне UI зачастую приходится работать с данными в другом формате (даты, суммы, id из справочников и т.д.)
2. Если в этот пример нужно будет добавить работу с БД — UI слой придется переписать. Кстати, сюда же можно и отнести первый пункт — в БД данные удобно хранить в других структурах и форматах и частенько они не совпадают с тем, что приходит из сети.
3. Ну и конечно, никто не застрахован от того что сетевая модель останется неизменной (:

p.s. кучу абстракций городить не нужно, например — поля модели можно вынести в интерфейс.
UFO landed and left these words here

Значит всему свое время — сначала стоит просто разобраться с базовыми кубиками java-конструктора, а уже потом закапываться в архитектуру и подходы. Книжки вроде "чистого кода", кажется, заходят только через пару лет реального опыта в реальном проекте. Иначе это все пустые слова и воздушные замки

Пару замечаний:
— в методе requestWithCallback() в блоке try результат доставляется в main-потоке, а в блоке catch ошибка доставляется уже в io-потоке — потенциальный крэш во viewOneError()
— data-класс Event вместе с enum Status лучше и компактнее будет выглядеть в виде sealed-класса
— отдавать MutableLiveData наружу из ViewModel — плохая практика. наружу должна торчать LiveData (через backing-property или какой-нибудь get-метод)
UFO landed and left these words here
Со всем согласен, учту на будущее, большое спасибо

Позвольте немного критики решения.


Api-класс сделан синглтоном — тестировать (мокать) будет сложнее. Лучше бы это был обычный класс, зависимость на который вы передавали бы через DI-фреймворк.


Cоздание внешних зависимости внутри класса — уже плохо для тестирования и поддержки, а вы еще и синглтон используете в этой зависимости:


abstract class BaseViewModel : ViewModel() {

    var api: Api = NetworkService.retrofitService()

К тому же, такая жесткая зависимость создается в базовом классе, который в реальной жизни будут использовать все другие модели. Лучше бы взять принцип IoC, который активно используется в Clean Architecture — это избавит от жестких связей и позволит менять зависимость как угодно в рамках интерфейса. Опять же, полезно не только для тестов, но и для реальной жизни и непрогнозируемого развития приложения.


Все эти громоздкие конструкции с try-catch, на мой взгляд, только ухудшают читаемость кода… Почему бы не использовать Rx, с его идеальной логикой обработки ошибок и все теми же состояниями Complete, Error уже из коробки? Если уж использовать try-catch по старинке, то лучше вынести эти блоки в отдельные приватные методы — серьезно улучшите читаемость.


Если вам зашла лайв дата, можно использовать ее адаптер для ретрофита и получать сетевые модели без этих странных конвертаций на уровне ViewMode, которая вообще не должна этим заниматься (не ее зона ответственности):


liveData.postValue(Event.success(response.data))

Сложилось впечатление, что под «чистой» архитектурой вы понимаете что-то иное, не подход Clean Architecture. И это даже не цепляясь к преобразованиям модели между уровнями (которые не всегда нужны) и обращению к сети прямо из ViewModel (что тоже возможно в совсем маленьких приложениях).

Ключевое предложение в этом туториале:
Материал будет полезен тем, кто не очень хорошо знаком с mvvm-паттерном и котлиновскими корутинами.

Фишка в том, что, когда я изучал все эти используемые в статье компоненты, я постоянно натыкался на плохо раскрываемый для понимания материал. Поэтому постарался написать для людей «кто не очень хорошо знаком с mvvm-паттерном и котлиновскими корутинами», более развернуто и наглядно, как это все работает в связке. Статья и так получилась слишком длинная, для чего все это здесь? Я имею ввиду принцип IoC, преобразования моделей в разных слоях и пр. замечания из комментариев выше?

Это здесь потому, что вы назвали архитектуру Clean. Это налагает определенные нюансы

А ну да, здесь согласен. я ее переменную)
Это разве длинная статья?
О времена, о нравы!
Это как в универе было: один препод тебе рассказывает подробно, по порядку материал, и по нарастающей увеличивает сложность, и ты все понимаешь, а другой с самого начала вываливает кучу непонятной хрени, и ты только к концу семестра, если повезёт, врубишься во все
Универ вообще плохой пример :) Его сложно воспринимать всерьез.
Ваше негодование тоже понятно, но непонятную хрень вы ведь тоже добавили? Абстрактная модель, лайв дата, обертки и все вот это. Если уж хочется рассказать просто про MVVM — половину можно выкинуть без потери смысла.
Не, я наоборот, считаю все замечания максимально ценными. Теперь мне понятно в каком направлении двигаться. А вообще больше хотелось рассказать не про mvvm а именно про связку технологий с использованием корутин. Мне эта тема тяжело заходила.
Решение ивентов с enum, на мой взгляд, не самое эффективное. Лучше использовать sealed class
sealed class Event<T : Any> {
var isHandled: Booleand = false
data class Success<T>(val data: T) : Event<T>()
data class Error(val message: String, val error: Throwable?) : Event<Nothing>()
object Loading : Event<Nothing>()
}

это дает некую мутабельность ивентов при пересечении архитектурных слоев если использовать Clean Architecture. Например, DataLayer может работать только с
Event<DatabaseEntity>
, а PresentationLayer может принимать только
Event<DataUIModel>
, тогда мы можем более лучше разграничить какие объекты могут пересекать архитектурные слои.
Конвертер в данном случае будет простой функцией
inline fun <reified T : Any, reified R : Any> Event<T>.mutateTo(): Event<R> {
// ваша логика конвертации
}


“If you think that good architecture is expensive, try bad architecture.”
— Brian Foote and Joseph Yoder
С sealed class решение хорошее, сегодня испробую, но можете подробнее расписать про функцию конвертации? Можно пример как сконвертировать два дата объекта?
Sign up to leave a comment.

Articles