СОДЕРЖАНИЕ: 00:00:00 - про меня ;) 00:01:21 - обзор проекта с Clean Architecture 00:03:18 - чистая архитектура на диаграмме 00:05:47 - создаем Storage компонент 00:14:14 - Storage компонент на диаграмме 00:16:54 - про раздельные модели на диаграмме 00:21:08 - делаем раздельные модели в коде на Android 00:28:54 - чистим и улучшаем код 00:32:40 - подводим итого
private val userRepository by lazy {UserRepositoryImpl(userStorage = SharedPrefUserStorage(context = applicationContext))} в активити. На следующем уроке скажут.
Посмотрел 3 видео, очень понятно, за пол часа полность переделал свое приложение по клину. У меня уже было какое то подобие, mvvm+repository, но вынес логику из вьюмодели в usecase. Теперь приятно глазу и понимаю, что читать и маштабировать приложение намного легче.Спасибо!
Спасибо большое за материал. Уже пишу свои приложения, но эти знания прямо в тему. В идеале немного поработать над звуком. Если честно, немного отвлекают посторонние звучки вроде причмокивания, сглатывания и тд. Уж извините за эту откровенность. Сам материал очень понравился. Еще раз спасибо! )
Видео - огонь!!!) Так детально и доходчиво пожалуй в ру сегменте мало кто рассказывает. Жаль про мапперы до конца тут не рассмотрено. А не лучше ли мапперы располагать в отдельном пакете на уровне пакетов слоев (дата, домен, презентейшн)? Ведь по факту они особо к конкретному слою не принадлежат и связаны с несколькими слоями.
Нет. Мапперы связаны только с одним слоем. Например, вы мапите из дата в домен. В этом случае домен не должен ничего знать про маперы, он просто получает нужные данные и все. То есть маперы все лежат в дата. Если вынести в отдельный модуль, то есть риск, что это начнуть неверно переиспользовать и все пойдет боком.
Огромное Вам спасибо за контент! В данный момент уже занимаюсь на курсах по андроид разработке. Дошёл до "MVVM", "Dagger-2", "Hilt". Преподаватели стараются, объясняют, но, видимо, я туповат - приходится искать уроки с более простой подачей. Недавно открыл для себя Ваш канал. Теперь жалею, что занимаюсь не на Ваших курсах.
Вы говорите, что слой domain ни от кого не зависит. Но при этом когда описываете setName 28:46 вы импортируете data.storage.User. Получается если вы измените например имя поля в data.storage.User нужно будет все равно лезть в domain исправлять.
Вы наверное, что-то не досмотрели. Domain модуль не имеет зависимости Data. То есть даже, если бы мы очень захотели использовать data.storage.User в домене, то не смогли бы. Возможно вы спрашиваете про то, что мы использует модель storage в реализации репозитория. Тут проблем нет, так как Storage ни о ком не знает, и те кто его использует, естественно получает Storage модели, и дальше уже маппит или как-то еще использует. Storage это часть более низкого уровня чем репозиторий, поэтому репозиторий и имеет доступ к Storage моделям.
@@ohjelmistokehittaja4446 Спасибо огромное за Ваш труд. Можно один глупый вопрос? Обязательно создавать переменную - userStorage: SharedPrefUserStorage? Или можно сразу инстанс передать? - private val userRepository by lazy { UserRepositoryImplementation(SharedPrefsUserStorage(context = applicationContext)) }
В конце видео вы анонсировали про мапперы, какие варианты есть с их достоинствами и недостатками. Не могу найти видео на эту тему, его нет? Это очень интересная тема, потому-что моделек уже 2 (хотя для UI тоже бы завести) и иной раз не понятно где хранить маппер который из одного слоя в другой мапит.
Да, отдельного видео по этой теме не было. Я рекомендую использовать kotlin extensions и хранить мапперы либо в отдельном пакете, например mappers, либо рядом с самими модельками.
Тимофей, спасибо за подробные и качественные разборы Clean Architecture! Есть вопрос насчет ответственности репозитория. Вы сказали, что именно репозиторий решает, в какие хранилища сходить - в интернет, в базу данных, в Shared Preferences или во все одновременно. Но представьте ситуацию, что в рамках одной сущности (например, Фильм) есть два разных сценария: - На экране А нужно скачать фильмы из сети, сложить в базу данных и отобразить на экране. - На экране Б нужно скачать фильмы из сети и сразу отобразить на экране, без складывания в базу данных. В таком случае, необходимо сделать два разных метода в репозитории? Тогда как их именовать? Не логичнее было бы сделать несколько репозиториев, ответственных за конкретное хранилище, а на стороне конкретного UseCase-а обращаться к нужным репозиториям?
В данном случае я бы все же сделал 2 разных юз кейса и разные методы в репозитории. Так в разы понятнее, какой экран, что делает и логика в разы проще и линейнее. Да и это же 2 разных варианта использования вашего приложения, которые заложены логикой, поэтому 2 юз кейса вполне логично. Возможность подкидывать разные реализации в основном нужна для того, что бы если меняется приложение/требования/источники данных, можно было сделать новые реализации по минимум затрагивая текущий код. То есть, по сути, это делается для более гибкого изменения и расширения кода, а не для того, что бы строить вокруг этого логику.
@@TimofeyKovalenko спасибо большое за ответ! Да, насчет 2х юз кейсов сомнений не было, с ними всё предельно понятно, 2 сценария = 2 юз кейса. А вот с репозиториями не до конца понятно. Представьте, что сценарий номер 1 уже реализован, а теперь нужно реализовать сценарий 2. Вроде бы, всё обращение к данным у нас уже реализовано - в сеть ходить умеем, в базу складывать умеем, но почему-то требуется дописать код в слое data, добавив новый метод в репозиторий 🤔 А вот если разбить репозитории по источнику данных, например, FilmNetworkRepository, FilmStorageRepository, тогда юз кейсы смогут по собственному усмотрению пользоваться разными репозиториями в разных ситуциях, и для реализации 2го сценария не пришлось бы редактировать слой data 🤔 Может быть, я не до конца понимаю ответственность репозитория, но мне кажется, что в случае двух разных методов репозитория его реализация и набор методов косвенно становятся зависимы от бизнес-логики, что может привести к его бесконечному разрастанию 🤔
Если я правильно понял, то ваш класс FilmNetworkRepository будет не только в сеть ходить, но и сохранять данные в базу, правильно? Если да, то такое разделение очень запутанно и странно выглядит. Для локальной базы у вас есть FilmStorageRepository. С такой логикой получается, что вы в юз кейсе должны получить данные из FilmNetworkRepository, а затем сохранить их, используя FilmStorageRepository. А в юз кейсе, где нужно только локальные данные читать, обратиться только к FilmStorageRepository. Но, это не совсем правильно, так как данными должен управлять репозиторий, а не UseCase. Тоесть если делаете такое разделение, то не нужно мешать логику внутри. По поводу бесконечного разрастания репозитория, это не проблема, если для разных сущностей вы используете разные репозитория. Очень сомневаюсь, что у вас будет так много вариаций работы с данными, что это заставить писать десятки методов. К тому же очень вряд ли, что методы работы с локальной базой, и методы работы с нетворк у вас будут одинаковые, я имею ввиду их количество и функционал. В реальном проекте, как правило так не бывает. Поэтому я бы рекомендовал все же иметь два метода или один с параметром, который задает конфиг работы с данными. Плюс, я бы сделал интерфейс Storage и от него 2 наследника NetworkStorage и LocalStorage. Эти объекты можно использовать в репозитории (подавать в конструктор естественно), и тем самым вынести работу с чтением/сохранением, и в репозитории оставить только логику работы с данными и манипулировать источниками.
Благодарю вас, Тимофей, за вашу работу! Редкость получать столь уникальные вещи! Ребят, старайтесь побольше практиковаться, а именно на фоне писать какое-то другое свое приложение с использованием каких-то вещей, о которых было рассказано в самом видео. Не стоит код переписывать!
Ну так если ты в одной модели поменяешь поля, то придётся менять и в другой модели? Где тогда тут независимость? А если была одна общая модель, то поменял бы в одном месте и всё. Другой пример, когда я хочу добавить ещё одно поле к User, например, адрес. Если у нас есть две модели, то я добавляю в одну, добавляю в другую, потом дописываю маппинг и дописываю сохранение. В случае если у меня одна общая модель, то я добавляю в одной модели и дописываю сохранение - всё. Ради только "независимости" не вижу смысла это делать.
Вы рассматриваете вариант небольшого приложение, где модели, которые пришли, в том же виде выводятся на экран, в этом случае такой маппинг действительно не нужен. Но как только у вас появятся поля, которые есть например в базе данных но их нет в domain модели, то как будите с таким работать? Скорей всего такие модели со временем превратятся в свалку, где невозможно будет разобраться, какие поля и где используются. В более больших проектах, такое разделение, да, требует больше кода, но по итогу защищает код от ошибок, делает код более простым и предсказуемым. Дело далеко не только в независимости слоев, дело в том, что-бы пользовательский код сделать максимально простым, без необходимости думать о том какие поля и как необходимо их использовать.
С точки зрения клин архитектуры - это одно и тоже. Но если почитать умные книжки, то есть какие-то очень тонкие отличия. Так же часто считается, что Use Case содержит только один публичный метод, а Interactor может иметь несколько. Тут нужно понимать, что клин архитектуру можно сделать вообще без юз кейсов или репозиториев, используя другие паттерны и принципы. То есть это все наполнение, а не основа основ клина.
Тимофей, привет! Подскажи пожалуйста, у меня в классе App, который наследует application и идет в name в манифесте, есть логика по изменению языка всего приложения и ночной темы, в рамках клин архитектуры и архитектуры в целом мне нужно эту логику из App куда-то переносить? Или пусть лежит себе в App, она не очень мудреная, спасибо за ответ
Тимофей, у меня такой вопрос. Если у нас в Дата слое имеется база данных со связью один ко многим и нам нужно работать с дочерней сущностью, то как быть со внешним ключом? Мы его тоже должны передавать в Домен? Я слышал такую версию, что в Домен слое в модели мы храним только ссылку на родительский объект (который тоже является моделью) А уже в дата слое мы храним внешний ключ, как в базе данных? Так ли это?
Не совсем понял ваш вопрос. База и разные модули - это совсем не верный вопрос. Все, что связано с базой должно лежать рядом в базе, тоесть в дата слое. Домен ничего не знает про базу и не может иметь никаких моделей, которые связаны с базой данных.
Есть вопрос - а как с помощью такого подхода реализовать получение данных из сети? Ведь работа с сетью будет в модуле дата, но методом get в usecase мы вернуть данные из сети не сможет. Дата про presentation нечего не знает чтобы передать туда ответ..
Потому, что это очень частая ошибка делать одинаковые модели. Например, модель User может содержать id, но при добавлении новых данных у вас еще нет id, и выходит, что вы вынуждены делать поле id nullable, поэтому сразу показал развернутый вариант. Если все поля один в один совпадают, то делать вторую модель не нужно.
@@TimofeyKovalenko Есть еще вопрос по viewModelfactory если вас не затруднит Есть такой вариант передачи параметра в конструктор viewModel ________________________________________ class WeatherViewModel(val repository: WeatherRepository) : ViewModel() { constructor() : this(WeatherRepository(ApiClient().getClient().create(ApiServices::class.java))) __________________________________________ вторичный конструктор который сробатывает после первичтоно Настоклько это хуже фабрики и хуже ли вообще?
@@TimofeyKovalenko извините, я что-то спутал. У меня есть интерфейс DataProvider, у которого есть имплементации для CSV, XML и JDBC. Я инициализирую для каждой модели Storage свой DataProvider. Затем получается я должен передавать это в отдельный Repository для каждой конкретной модели в Domain, и передать в UseCase, всё верно?
Если логика поменялась, понятное дело зависимости тоже нужно менять. Независимость достигается тем, что каждая часть имеет свой API(свои методы и ентити) и их реализация не зависит от хотелок того, кто будет использовать эту логику. То есть domain не зависит от потребителей, а потребитель всегда подчиняется правилам API. К тому же, если у вас что-то поменялось в домене, вам это что-то требуется использовать - показать на экране или еще как то - тут никакая независимость не поможет))). В этом случае правильнее не изменять текущую логику, а добавить новую.
Спасибо за уроки! А данный код не скинете например на гитхаб? Также не понял название методов с приставкой map. Логичнее например userToUserName(), а не mapToDomain() ?
В котлине, RX и в многих других фреймворках/языках есть стандартные методы, которые как раз называются map и делают конвертацию данных. Также это просто общеупотребительное слово для подобных операций и такое название будет понятно большинству программистов. userToUserName как раз таки не говорит о том, что будет делаться, вообще названия методов лучше начинать с глагола.
По поводу кода нет, никогда не скидываю студентам код на котором показываю примеры, иначе толку от лекции нет. Понять можно только если самому написать.
То есть получается дата и домен работают с отдельными своими типами объектов, но эти типы связаны логикой приложения. А маперы в репозитории приводят один тип к другому для передачи между слоями?
Привіт В мене є наступне питання Слой Data як я розумію відповідає суто за роботу з даними, чи то локальні чи на сервері.Значить якщо мені треба зробити певні операції з цими даними то їх я вже виконую в UseCase класах? І кидаю готові дані після певних операції на ViewModel?
получается если я сделаю RoomUserStorage у которого свои entity с аннотациями, я должен внутри storage скоупа мапить из UserEntity(RoomUserStorage) -> User(UserStorage) и прокинуть в Repository который смапит в UserName(Domain)?
Storage не должен знать ни о чем вокруг него, маппинг делаете за пределами storage. То есть репозиторий получит entity из Storage и далее должен будет их замапить.
@@TimofeyKovalenko нет смотрите , UserStorage - интерфейс, и репозиторий не знает ничего о реализации ,там может быть NetworkUserStorage , RoomUserStorage , SharedPrefUserStorage - и у каждого будут свои сущности с аннотациями, может и нет), также могут присутствовать лишние поля которые не нужны дальше. Вы говорите "репозиторий получит entity из Storage и далее должен будет их замапить." - это же означает, что теперь репозиторий хоть работает с интерфейсам, но все же жестко зависит от Room имплементации, из за мапинга. Поэтому может каждая имлементация внутри Storage скоупа должна смапить к обобщенной модельки которая будет независимой(без аннотации итд) , и прокидать в репозиторий который будет мапить Storage модельку в Domain модель - и тут репо теперь не знает о реализации надеюсь смог объяснить :)
Если модели разные, то это будут совершенно разные интерфейсы. Репозиторий просто будет работать с каждым по отдельности. Вы пытаетесь сделать так, что-бы Storage подстроился под требования репозитория, но так делать не стоит. Да и выглядит так, что вы выносите логику репозитория в Storage. На практике, практически никогда не делают один интерфейс для нетворка и локального хранилища, это не удобно и сильно ограничивает вас в дальнейшем,
@@TimofeyKovalenko да вы правы) , согласен что не делают один интерфейс для нетворка и локального хранилища вот последний вопрос, извиняюсь). У нас есть условно 4 репозитория которые зависят от LocalDataStorage и у него была RealmLocalDataStorageImpl или какой то другая impl не важно, через некоторое время мы мигрировали на RoomLocalDataStorageImpl и это все внутренности LocalDataStorage о котором не должны знать клиенты - то есть те которые зависят от этого стораджа. подскажите какую модельку должен предоставить LocalDataStorage чтобы каждый клиент не ломался от нашей миграции?
Спасибо за урок. Но у меня есть сомнений. В слое domain есть модел UsernameModel и при добавление еще одно поле в конструктор данного класса в Storage появится ошибка. Это правильно или нет?
Да, все тут верно. Просто домена модель это один API, storage это совсем другой API. Если задача подразумевает и там и там менять, то да, нужно поменять в двух местах. А если вы будите одну и туже модель использовать для UI и для Storage, то во первых модель может обрасти специфическими данными, которые нужны только кому-то одному, а во вторых при изменении модели вы сразу измените совершенно разные компоненты. Архитектура это не про то, что-бы быстро накидать код, а про удобство на длинной дистанции.
@@TimofeyKovalenko )) такие схемы и показываются на UML) ! не нестоит ,просто в начале видео покажи стрелки зависемости,наследование, реализаций ,и это на 100 хватит в этом видео
Да, назначение даты как раз и заниматься хранением данных, поэтому в большинстве случаев нет необходимости его выносить. Но если у вас модуль массивный либо используется разными системами, то конечно можно вынести.
А кто мне доступно объяснит зачем везде пихают мапперы, и почему дата у всех реализует домен? Если мы говорим про разделение, то давайте четко и разделять, без всяких интерфейсов, просто классами все прекрасно разделяется и работает, а интерфейсы - это про другое. Сначала говорим, что Data слой должен быть максимально простой и без логики, а потом туда запихиваем всю логику приложения (реализация domain - она почему-то в data оказывается и никого это не смущает). Entity можно использовать везде (для отображения, обычно еще нужно что-то отформатировать, но так никто не мешает работать с 1 набором моделей и без всяких маперов), clean - не про них вообще. Давайте не писать код ради кода и усложнять этот самый код, затягивая сроки написания чистого и красивого кода.
Мапперы нужны, что-бы развязать логику и дают возможность каждому компоненту работать со своей моделью, и соответственно делать с ней все, что будет нужно не заботясь об остальных. А добавлять или не добавлять маперы, это уже дело конкретного проекта. Исходя из моего опыта, на длинной дистанции это всегда оправдано. По поводу "почему дата у всех реализует домен" - просто это наиболее частый случай. Реализацию можно писать и в других модулях, да и доменов и дат может быть много в приложении. Насчет логики домена в дате, не понял вас, какая логика из домена у нас оказалась в дате? В нашем случае логики, как таковой, нет вообще, поэтому и юз кейсы простые. Нужно понимать, что это простой проект, просто для примера ;), запихать сюда кучу логики и взорвать мозг студентам не цель данного видео))))).
СОДЕРЖАНИЕ:
00:00:00 - про меня ;)
00:01:21 - обзор проекта с Clean Architecture
00:03:18 - чистая архитектура на диаграмме
00:05:47 - создаем Storage компонент
00:14:14 - Storage компонент на диаграмме
00:16:54 - про раздельные модели на диаграмме
00:21:08 - делаем раздельные модели в коде на Android
00:28:54 - чистим и улучшаем код
00:32:40 - подводим итого
Так понятно даже в универе не объясняли насчёт архитектуры и UML, туман рассеивается, благодарю!
Ваш курс и уровень доступности ваших объяснений - это бомба петарда ракета 💣🧨🚀🔥 Огромное спасибо вам!
Спасибо)
Да, не простой урок, но очень интересный. Великолепно. Большое спасибо за науку Тимофей.
боже, слов благодарностей таких не существует которыми бы хотелось вас обложить, спасибо большое
Спасибо)
Очень доступно подается материал. Жалею только, что я раньше не нашел этот канал)
Спасибо!! Хотелось бы про правильную/красивую организацию мапперов больше :)
Будет отдельное видео, где поговорим о том что можно улучшить в этом приложении, какие варианты есть и тд. И там будем говорить про мапперы.
Спасибо, полезное видео. Жду следующее 👍
Следующее видео: ua-cam.com/video/rCkyU5lPAT8/v-deo.html
Тимофей, с твоим талантом объяснять, тебе нужно записать видео про асинхронщину, RX, корутины. Было бы здорово!
😎, еще бы время найти на все))).
Очень качественный контент! Материал подан простым и доступным языком, все последовательно и систематично 💪
private val userRepository by lazy {UserRepositoryImpl(userStorage = SharedPrefUserStorage(context = applicationContext))} в активити. На следующем уроке скажут.
Посмотрел 3 видео, очень понятно, за пол часа полность переделал свое приложение по клину. У меня уже было какое то подобие, mvvm+repository, но вынес логику из вьюмодели в usecase. Теперь приятно глазу и понимаю, что читать и маштабировать приложение намного легче.Спасибо!
Спасибо, суперский урок и курс!!!
Посмотрел этот и другие ролики на канале - снимаю шляпу, я в восторге от качества видео и доступности объяснения!
Спасибо большое за материал. Уже пишу свои приложения, но эти знания прямо в тему. В идеале немного поработать над звуком. Если честно, немного отвлекают посторонние звучки вроде причмокивания, сглатывания и тд. Уж извините за эту откровенность. Сам материал очень понравился. Еще раз спасибо! )
Мужик, спасибо. Круто!
😀
Видео - огонь!!!) Так детально и доходчиво пожалуй в ру сегменте мало кто рассказывает. Жаль про мапперы до конца тут не рассмотрено.
А не лучше ли мапперы располагать в отдельном пакете на уровне пакетов слоев (дата, домен, презентейшн)? Ведь по факту они особо к конкретному слою не принадлежат и связаны с несколькими слоями.
Нет. Мапперы связаны только с одним слоем. Например, вы мапите из дата в домен. В этом случае домен не должен ничего знать про маперы, он просто получает нужные данные и все. То есть маперы все лежат в дата.
Если вынести в отдельный модуль, то есть риск, что это начнуть неверно переиспользовать и все пойдет боком.
Спасибо за ваши видео! Они были тем что помогло размешать кашу в моей голове.
Спасибо Тимофей у вас лучшие уроки 👍
Шикароный урок!!!
Безумно интересно, спасибо!!
Огромное Вам спасибо за контент! В данный момент уже занимаюсь на курсах по андроид разработке. Дошёл до "MVVM", "Dagger-2", "Hilt". Преподаватели стараются, объясняют, но, видимо, я туповат - приходится искать уроки с более простой подачей. Недавно открыл для себя Ваш канал. Теперь жалею, что занимаюсь не на Ваших курсах.
Если это первый ваш опыт разработки, то это абсолютно нормально - процесс не быстрый)
Благодарю, Тимофей! Очень полезное видео!
Спасибо огромное за материал. Очень доступное объяснение!
Очеень полезное видео, спасибо большое!=)
очень подробное и крутое объяснение! Я бы сказал, что лучшее на всём UA-cam
Отличный курс) спасибо большое
Спасибо за видео.Коммент в поддержку!
Спасибо)
Отличные материалы. Спасибо!
Красота.
Вы говорите, что слой domain ни от кого не зависит. Но при этом когда описываете setName 28:46 вы импортируете data.storage.User. Получается если вы измените например имя поля в data.storage.User нужно будет все равно лезть в domain исправлять.
Вы наверное, что-то не досмотрели. Domain модуль не имеет зависимости Data. То есть даже, если бы мы очень захотели использовать data.storage.User в домене, то не смогли бы. Возможно вы спрашиваете про то, что мы использует модель storage в реализации репозитория. Тут проблем нет, так как Storage ни о ком не знает, и те кто его использует, естественно получает Storage модели, и дальше уже маппит или как-то еще использует. Storage это часть более низкого уровня чем репозиторий, поэтому репозиторий и имеет доступ к Storage моделям.
Спасибо за уроки, может проглядел, но по моему mainActivity нужно подправить, там где контекст передается в UserRepositoryImpl
Да, все верно, просто в видео сфокусировался на дата слое и совсем забыл про activity.
UserRepositoryImpl(SharedPrefUserStorage(applicationContext))
В следующем видео "Модули в Android Clean Architecture на практике" на 16.01
А юзерсторадж где создавать?
private val userRepository by lazy {UserRepositoryImpl(userStorage = SharedPrefUserStorage(context = applicationContext))}
@@ohjelmistokehittaja4446 Спасибо огромное за Ваш труд. Можно один глупый вопрос? Обязательно создавать переменную - userStorage: SharedPrefUserStorage? Или можно сразу инстанс передать? - private val userRepository by lazy { UserRepositoryImplementation(SharedPrefsUserStorage(context = applicationContext)) }
Спасибо!
Качественный контент!!!
Спасибо большое за уроки, очень информативные, жалко только то, что вы ссылку на гх не выложили😪
Сломался слой Presentation) Но в целом очень, очень здорово, спасибо за труд
Спасибо за урок.
оч крутое и понятное видео, большое спасибо
Для меня открывается новая вселенная!!! Спасибо!
Реквестирую гайд по корутинам :)
Не в ближайшее время, но обязательно сделаю.
В конце видео вы анонсировали про мапперы, какие варианты есть с их достоинствами и недостатками. Не могу найти видео на эту тему, его нет? Это очень интересная тема, потому-что моделек уже 2 (хотя для UI тоже бы завести) и иной раз не понятно где хранить маппер который из одного слоя в другой мапит.
Да, отдельного видео по этой теме не было. Я рекомендую использовать kotlin extensions и хранить мапперы либо в отдельном пакете, например mappers, либо рядом с самими модельками.
Спасибо
Супер!
Тимофей, спасибо за подробные и качественные разборы Clean Architecture! Есть вопрос насчет ответственности репозитория. Вы сказали, что именно репозиторий решает, в какие хранилища сходить - в интернет, в базу данных, в Shared Preferences или во все одновременно. Но представьте ситуацию, что в рамках одной сущности (например, Фильм) есть два разных сценария:
- На экране А нужно скачать фильмы из сети, сложить в базу данных и отобразить на экране.
- На экране Б нужно скачать фильмы из сети и сразу отобразить на экране, без складывания в базу данных.
В таком случае, необходимо сделать два разных метода в репозитории? Тогда как их именовать? Не логичнее было бы сделать несколько репозиториев, ответственных за конкретное хранилище, а на стороне конкретного UseCase-а обращаться к нужным репозиториям?
В данном случае я бы все же сделал 2 разных юз кейса и разные методы в репозитории. Так в разы понятнее, какой экран, что делает и логика в разы проще и линейнее. Да и это же 2 разных варианта использования вашего приложения, которые заложены логикой, поэтому 2 юз кейса вполне логично.
Возможность подкидывать разные реализации в основном нужна для того, что бы если меняется приложение/требования/источники данных, можно было сделать новые реализации по минимум затрагивая текущий код. То есть, по сути, это делается для более гибкого изменения и расширения кода, а не для того, что бы строить вокруг этого логику.
@@TimofeyKovalenko спасибо большое за ответ! Да, насчет 2х юз кейсов сомнений не было, с ними всё предельно понятно, 2 сценария = 2 юз кейса. А вот с репозиториями не до конца понятно. Представьте, что сценарий номер 1 уже реализован, а теперь нужно реализовать сценарий 2. Вроде бы, всё обращение к данным у нас уже реализовано - в сеть ходить умеем, в базу складывать умеем, но почему-то требуется дописать код в слое data, добавив новый метод в репозиторий 🤔 А вот если разбить репозитории по источнику данных, например, FilmNetworkRepository, FilmStorageRepository, тогда юз кейсы смогут по собственному усмотрению пользоваться разными репозиториями в разных ситуциях, и для реализации 2го сценария не пришлось бы редактировать слой data 🤔 Может быть, я не до конца понимаю ответственность репозитория, но мне кажется, что в случае двух разных методов репозитория его реализация и набор методов косвенно становятся зависимы от бизнес-логики, что может привести к его бесконечному разрастанию 🤔
Если я правильно понял, то ваш класс FilmNetworkRepository будет не только в сеть ходить, но и сохранять данные в базу, правильно? Если да, то такое разделение очень запутанно и странно выглядит. Для локальной базы у вас есть FilmStorageRepository. С такой логикой получается, что вы в юз кейсе должны получить данные из FilmNetworkRepository, а затем сохранить их, используя FilmStorageRepository. А в юз кейсе, где нужно только локальные данные читать, обратиться только к FilmStorageRepository.
Но, это не совсем правильно, так как данными должен управлять репозиторий, а не UseCase. Тоесть если делаете такое разделение, то не нужно мешать логику внутри.
По поводу бесконечного разрастания репозитория, это не проблема, если для разных сущностей вы используете разные репозитория. Очень сомневаюсь, что у вас будет так много вариаций работы с данными, что это заставить писать десятки методов.
К тому же очень вряд ли, что методы работы с локальной базой, и методы работы с нетворк у вас будут одинаковые, я имею ввиду их количество и функционал. В реальном проекте, как правило так не бывает.
Поэтому я бы рекомендовал все же иметь два метода или один с параметром, который задает конфиг работы с данными. Плюс, я бы сделал интерфейс Storage и от него 2 наследника NetworkStorage и LocalStorage. Эти объекты можно использовать в репозитории (подавать в конструктор естественно), и тем самым вынести работу с чтением/сохранением, и в репозитории оставить только логику работы с данными и манипулировать источниками.
Благодарю вас, Тимофей, за вашу работу! Редкость получать столь уникальные вещи! Ребят, старайтесь побольше практиковаться, а именно на фоне писать какое-то другое свое приложение с использованием каких-то вещей, о которых было рассказано в самом видео. Не стоит код переписывать!
Не сказать что я чтото понял по клину)) Но понимать где я нахожусь и не терять нить я смог с 4 раза(не подряд естественно)
Приветствую, а почему вы не используете data классы для создания моделей?
А зачем?) Когда будет необходимость именно в дата классе, тогда и сделаем.
От вас будут еще уроки по Андроид ? Мне интересно вас слушать и смотреть...
конечно будут), прямо сейчас заливаю новое видео, завтра после обеда(по москве) будет доступно.
@@TimofeyKovalenko Спасибо. Жду... ))
Следующее видео: ua-cam.com/video/rCkyU5lPAT8/v-deo.html
I'm watching it with passion it's a very interesting video.
Thank for the cute video.
Ну так если ты в одной модели поменяешь поля, то придётся менять и в другой модели? Где тогда тут независимость? А если была одна общая модель, то поменял бы в одном месте и всё.
Другой пример, когда я хочу добавить ещё одно поле к User, например, адрес. Если у нас есть две модели, то я добавляю в одну, добавляю в другую, потом дописываю маппинг и дописываю сохранение. В случае если у меня одна общая модель, то я добавляю в одной модели и дописываю сохранение - всё. Ради только "независимости" не вижу смысла это делать.
Вы рассматриваете вариант небольшого приложение, где модели, которые пришли, в том же виде выводятся на экран, в этом случае такой маппинг действительно не нужен. Но как только у вас появятся поля, которые есть например в базе данных но их нет в domain модели, то как будите с таким работать? Скорей всего такие модели со временем превратятся в свалку, где невозможно будет разобраться, какие поля и где используются.
В более больших проектах, такое разделение, да, требует больше кода, но по итогу защищает код от ошибок, делает код более простым и предсказуемым. Дело далеко не только в независимости слоев, дело в том, что-бы пользовательский код сделать максимально простым, без необходимости думать о том какие поля и как необходимо их использовать.
Тимофей, спасибо большое. Так а в дата слое, в репозитории, может быть логика между загрузкой из сети и базой?
Конечно может.
С implementation не поняла. Мы до этого ведь тоже связали UserRepositoryImpl с User Model из domain.
привет, хороший курс посмотрел все видео класс . хотел бы послушать про mapper- и и про экстеншены. может у тебя есть про это видео?
спасибо ❤
Про мапперы будет в ближайших видео.
👍👍👍👍👍👍👍👍👍👍👍
У меня вопрос: чем отличаются use cases от interactor'а? И, если его ещё нет, будет ли разбор interactor'а?
Кажется что ничем, это просто другое название юзкейсов.
С точки зрения клин архитектуры - это одно и тоже. Но если почитать умные книжки, то есть какие-то очень тонкие отличия. Так же часто считается, что Use Case содержит только один публичный метод, а Interactor может иметь несколько.
Тут нужно понимать, что клин архитектуру можно сделать вообще без юз кейсов или репозиториев, используя другие паттерны и принципы. То есть это все наполнение, а не основа основ клина.
@@TimofeyKovalenko Понял, спасибо)
Круто . А в main activity не нужны исправления ?
Тимофей, привет! Подскажи пожалуйста, у меня в классе App, который наследует application и идет в name в манифесте, есть логика по изменению языка всего приложения и ночной темы, в рамках клин архитектуры и архитектуры в целом мне нужно эту логику из App куда-то переносить? Или пусть лежит себе в App, она не очень мудреная, спасибо за ответ
Без кода, сложно оценить. Но держать это в App не сильно хорошее решение
Тимофей, у меня такой вопрос.
Если у нас в Дата слое имеется база данных со связью один ко многим
и нам нужно работать с дочерней сущностью, то как быть со внешним ключом?
Мы его тоже должны передавать в Домен?
Я слышал такую версию, что в Домен слое в модели мы храним
только ссылку на родительский объект (который тоже является моделью)
А уже в дата слое мы храним внешний ключ, как в базе данных?
Так ли это?
Не совсем понял ваш вопрос. База и разные модули - это совсем не верный вопрос. Все, что связано с базой должно лежать рядом в базе, тоесть в дата слое. Домен ничего не знает про базу и не может иметь никаких моделей, которые связаны с базой данных.
Я считаю, что в домене не обязательно должен быть стораж интерфейс, если мы собираемся работать с данными по интерфейсу репозитория
Да, они там не нужны. Дальше по видео мы их и не размещаем в домене, все это размещаем в data.
У нас получается полное дублирование модели User в domain и data?
В данном случае да, но в реальном приложении много различий может быть.
Есть вопрос - а как с помощью такого подхода реализовать получение данных из сети? Ведь работа с сетью будет в модуле дата, но методом get в usecase мы вернуть данные из сети не сможет. Дата про presentation нечего не знает чтобы передать туда ответ..
Не понял вас))), берете корутины и вперед.
@@TimofeyKovalenko Понял ). Спасибо!
добрый день. Я не пойме зачем на вхож используется одна модель saveuserparam на выхо другая useerName
Потому, что это очень частая ошибка делать одинаковые модели. Например, модель User может содержать id, но при добавлении новых данных у вас еще нет id, и выходит, что вы вынуждены делать поле id nullable, поэтому сразу показал развернутый вариант.
Если все поля один в один совпадают, то делать вторую модель не нужно.
@@TimofeyKovalenko Есть еще вопрос по viewModelfactory
если вас не затруднит
Есть такой вариант передачи параметра в конструктор viewModel
________________________________________
class WeatherViewModel(val repository: WeatherRepository) : ViewModel() {
constructor() : this(WeatherRepository(ApiClient().getClient().create(ApiServices::class.java)))
__________________________________________
вторичный конструктор который сробатывает после первичтоно
Настоклько это хуже фабрики и хуже ли вообще?
класс sharedpref нигде не исп-ся, объявленные классы в mainactivity не изменены с прошлого видео
Почему перекидыванием занимается use case, а не Repository в data слое? Domain ведь не должен знать о data.
Мы имеете ввиду маперы между разными entity? Мы их в Repository как раз и делаем.
@@TimofeyKovalenko извините, я что-то спутал. У меня есть интерфейс DataProvider, у которого есть имплементации для CSV, XML и JDBC. Я инициализирую для каждой модели Storage свой DataProvider. Затем получается я должен передавать это в отдельный Repository для каждой конкретной модели в Domain, и передать в UseCase, всё верно?
@@TimofeyKovalenkoа ещё мне кажется модели Storage лучше называть Entity, чтобы не было путаницы
А если в модели в domain что-то изменится, то маппере тоже надо вносить изменения. не получается полной независимости.
Если логика поменялась, понятное дело зависимости тоже нужно менять. Независимость достигается тем, что каждая часть имеет свой API(свои методы и ентити) и их реализация не зависит от хотелок того, кто будет использовать эту логику. То есть domain не зависит от потребителей, а потребитель всегда подчиняется правилам API.
К тому же, если у вас что-то поменялось в домене, вам это что-то требуется использовать - показать на экране или еще как то - тут никакая независимость не поможет))).
В этом случае правильнее не изменять текущую логику, а добавить новую.
Спасибо за уроки! А данный код не скинете например на гитхаб? Также не понял название методов с приставкой map. Логичнее например userToUserName(), а не mapToDomain() ?
В котлине, RX и в многих других фреймворках/языках есть стандартные методы, которые как раз называются map и делают конвертацию данных. Также это просто общеупотребительное слово для подобных операций и такое название будет понятно большинству программистов.
userToUserName как раз таки не говорит о том, что будет делаться, вообще названия методов лучше начинать с глагола.
По поводу кода нет, никогда не скидываю студентам код на котором показываю примеры, иначе толку от лекции нет. Понять можно только если самому написать.
Привет! Большое спасибо! Круто, весьма круто и на русском языке для джунов!
Спасибо за контент, вы лучший. Но в данном случае приложение не запускается! Ошибка в MainActivity с by lazy {}
Где можно задонатить вам ,где ссылка?Хочу отблагодарить ,но не знаю как.
То есть получается дата и домен работают с отдельными своими типами объектов, но эти типы связаны логикой приложения. А маперы в репозитории приводят один тип к другому для передачи между слоями?
Да. Каждый слой делает свою логику использую только свои модели, а если нужно связаться с другим слоем, то маппим.
идеальное объяснение, но все равно сложно понять от такого количества классов
Привіт
В мене є наступне питання
Слой Data як я розумію відповідає суто за роботу з даними, чи то локальні чи на сервері.Значить якщо мені треба зробити певні операції з цими даними то їх я вже виконую в UseCase класах? І кидаю готові дані після певних операції на ViewModel?
Да, все верно
Вы бойлерплейтом в продакшене не давитесь при таком щепетильном следовании догматам Чистой Архитектуры?
Нет). Если проработаете в ентерпрайзах от 3-4 лет в одном проекте, поймете почему.
получается если я сделаю RoomUserStorage у которого свои entity с аннотациями, я должен внутри storage скоупа мапить из
UserEntity(RoomUserStorage) -> User(UserStorage) и прокинуть в Repository который смапит в UserName(Domain)?
Storage не должен знать ни о чем вокруг него, маппинг делаете за пределами storage. То есть репозиторий получит entity из Storage и далее должен будет их замапить.
@@TimofeyKovalenko нет смотрите , UserStorage - интерфейс, и репозиторий не знает ничего о реализации ,там может быть NetworkUserStorage , RoomUserStorage , SharedPrefUserStorage - и у каждого будут свои сущности с аннотациями, может и нет), также могут присутствовать лишние поля которые не нужны дальше.
Вы говорите "репозиторий получит entity из Storage и далее должен будет их замапить." - это же означает, что теперь репозиторий хоть работает с интерфейсам, но все же жестко зависит от Room имплементации, из за мапинга.
Поэтому может каждая имлементация внутри Storage скоупа должна смапить к обобщенной модельки которая будет независимой(без аннотации итд) , и прокидать в репозиторий который будет мапить Storage модельку в Domain модель - и тут репо теперь не знает о реализации
надеюсь смог объяснить :)
Если модели разные, то это будут совершенно разные интерфейсы. Репозиторий просто будет работать с каждым по отдельности. Вы пытаетесь сделать так, что-бы Storage подстроился под требования репозитория, но так делать не стоит. Да и выглядит так, что вы выносите логику репозитория в Storage.
На практике, практически никогда не делают один интерфейс для нетворка и локального хранилища, это не удобно и сильно ограничивает вас в дальнейшем,
@@TimofeyKovalenko да вы правы) , согласен что не делают один интерфейс для нетворка и локального хранилища
вот последний вопрос, извиняюсь). У нас есть условно 4 репозитория которые зависят от LocalDataStorage и у него была RealmLocalDataStorageImpl или какой то другая impl не важно, через некоторое время мы мигрировали на RoomLocalDataStorageImpl и это все внутренности LocalDataStorage о котором не должны знать клиенты - то есть те которые зависят от этого стораджа.
подскажите какую модельку должен предоставить LocalDataStorage чтобы каждый клиент не ломался от нашей миграции?
Спасибо за урок. Но у меня есть сомнений. В слое domain есть модел UsernameModel и при добавление еще одно поле в конструктор данного класса в Storage появится ошибка. Это правильно или нет?
Да, все тут верно. Просто домена модель это один API, storage это совсем другой API. Если задача подразумевает и там и там менять, то да, нужно поменять в двух местах.
А если вы будите одну и туже модель использовать для UI и для Storage, то во первых модель может обрасти специфическими данными, которые нужны только кому-то одному, а во вторых при изменении модели вы сразу измените совершенно разные компоненты. Архитектура это не про то, что-бы быстро накидать код, а про удобство на длинной дистанции.
привет Тимофей спасибо за контент ! советую c UML )
Нет смысла показывать такие схемы по UML), иначе придется еще и курс по UML делать предварительно)))).
@@TimofeyKovalenko )) такие схемы и показываются на UML) !
не нестоит ,просто в начале видео покажи стрелки зависемости,наследование, реализаций ,и это на 100 хватит в этом видео
разве domain не стал зависить от data из-за класса User?
Нет, каким образом? Data не подключена к domain, даже если очень захотеть, этого не получится).
STORAGE внутри DATA?
может правильнее отдельно?
Да, назначение даты как раз и заниматься хранением данных, поэтому в большинстве случаев нет необходимости его выносить. Но если у вас модуль массивный либо используется разными системами, то конечно можно вынести.
А кто мне доступно объяснит зачем везде пихают мапперы, и почему дата у всех реализует домен?
Если мы говорим про разделение, то давайте четко и разделять, без всяких интерфейсов, просто классами все прекрасно разделяется и работает, а интерфейсы - это про другое. Сначала говорим, что Data слой должен быть максимально простой и без логики, а потом туда запихиваем всю логику приложения (реализация domain - она почему-то в data оказывается и никого это не смущает). Entity можно использовать везде (для отображения, обычно еще нужно что-то отформатировать, но так никто не мешает работать с 1 набором моделей и без всяких маперов), clean - не про них вообще.
Давайте не писать код ради кода и усложнять этот самый код, затягивая сроки написания чистого и красивого кода.
Мапперы нужны, что-бы развязать логику и дают возможность каждому компоненту работать со своей моделью, и соответственно делать с ней все, что будет нужно не заботясь об остальных. А добавлять или не добавлять маперы, это уже дело конкретного проекта. Исходя из моего опыта, на длинной дистанции это всегда оправдано.
По поводу "почему дата у всех реализует домен" - просто это наиболее частый случай. Реализацию можно писать и в других модулях, да и доменов и дат может быть много в приложении.
Насчет логики домена в дате, не понял вас, какая логика из домена у нас оказалась в дате? В нашем случае логики, как таковой, нет вообще, поэтому и юз кейсы простые.
Нужно понимать, что это простой проект, просто для примера ;), запихать сюда кучу логики и взорвать мозг студентам не цель данного видео))))).
Пипец путаницу устрол, жесть