Koin Dependency Injection переключение между локальным и удаленным источником данных

Итак, я пишу это приложение с удаленным источником данных. И я хотел добавить возможности локального хранилища БД. Я настроил архитектуру, в которой у меня есть интерфейс DataSource. Классы RemoteDataSource и LocalDataSource реализуют этот интерфейс. RemoteDataSource вводится с модернизацией ApiInterface, а LocalDataSource вводится с DAO.

Теперь есть этот интерфейс репозитория и реализация SomeDataRepository и SomeDataRepositoryImpl. Если я хочу, чтобы репозиторий мог получать данные из api и сохранять их в базе данных, как мне это сделать?

В настоящее время я внедрил классы RemoteDataSource и LocalDataSource в SomeDataRepositoryImpl для доступа к методам из разных источников данных. Таким образом я могу вызвать что-то вроде localDataSource.saveToDb() и / или remoteDatSource.fetchSomeData() int SomeRepositoryImpl class. Но я не знаю, подходит ли передача конкретных реализаций классу.

Но если я передам, скажем, один DataSource интерфейс для SomeDataRepository, мне нужно будет определить saveToDb() функцию в интерфейсе DataSource, а затем мне придется реализовать это и в RemoteDataSource, что не очень хорошо.

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

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


person ravi    schedule 17.05.2020    source источник
comment
Вы должны использовать 2 интерфейса вместо одного источника данных. Я рекомендую вам создать интерфейс RemoteDataSource, LocalDataSource и использовать RemoteDataSourceImpl и LocalDataSourceImpl. Внутри SomeDataRepositoryImpl передайте 2 экземпляра своего   -  person Công Hải    schedule 17.05.2020
comment
developer.android.com/jetpack/docs/guide#cache-data читайте здесь, WebService - это ваш RemoteDataSource, а UserCache - это LocalDataSource.   -  person Công Hải    schedule 17.05.2020
comment
@ CôngHải Привет Спасибо за ваше предложение. А как насчет того, чтобы применить некоторую логику, например, если запрос api выполнен успешно, сохранить в db, а затем получить из db. если это не удается, получить напрямую из базы данных. и если получить из базы данных не удается, то отображаются только ошибки. Можно ли это сделать внутри класса репозитория. Или это слишком для репозитория?   -  person ravi    schedule 17.05.2020
comment
По какой логике вы хотите решить, использовать ли локальный или удаленный источник данных?   -  person Matthew Pope    schedule 18.05.2020
comment
@MatthewPope Я хочу использовать локальный источник данных только в случае сбоя сетевого запроса. Это возможно ?   -  person ravi    schedule 18.05.2020


Ответы (2)


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

Предположим, это ваш интерфейс и два источника данных, которые у вас уже есть:

interface DataSource {
    fun getData(): Data
}

class RemoteDataSource : DataSource {
    // ...
}

class LocalDataSource : DataSource {
    // ...
}

Затем вы можете создать третью реализацию, подобную этой:

class CompositeDataSource(
    val remote: RemoteDataSource, 
    val local: LocalDataSource
) : DataSource {
    override fun getData() : Data {
        return try {
            remote.getData()
        } catch (e: Exception) {
            local.getData()
        }
    }
}

Чтобы определить все это, ваш модуль коина будет выглядеть примерно так

module {
    single { RemoteDataSource() }
    single { LocalDataSource() }
    single<DataSource> { CompositeDataSource(remote = get(), local = get()) }
}

Изменить: если на самом деле вам нужен кеш, вы можете использовать локальный источник данных в качестве кеша следующим образом:

class CompositeDataSource(
    val remote: RemoteDataSource, 
    val local: LocalDataSource
) : DataSource {
    override fun getData() : Data {
        return try {
            remote.getData().also { local.saveData(it) }
        } catch (e: Exception) {
            local.getData()
        }
    }
}
person Matthew Pope    schedule 18.05.2020
comment
Привет, это действительно интересное и красивое решение. Спасибо за ваш ответ. Я попробую это попробовать. Не могли бы вы также рассказать мне, как я могу управлять сохранением данных при успешном выполнении сетевых запросов. Например, где я могу поместить код, который говорит, что если сетевой запрос успешно сохранит данные, чтобы, если сетевой запрос не был успешным, составной источник данных мог возвращать данные из локальной базы данных. - person ravi; 23.05.2020
comment
Предполагая, что ваш API источника данных имеет метод записи, тогда в вашей реализации составного хранилища данных вы можете сохранить как удаленный, так и локальный источник данных. Но затем вам также нужно решить, что делать, если вам нужно что-то сохранить, а удаленное хранилище данных недоступно. Если вы еще не привязаны к определенному бэкэнду, вы можете попробовать использовать что-то вроде Couch DB или AWS AppSync, которые предназначены для обработки онлайн и офлайн вариантов использования. Они могут справиться с некоторыми из этих сложностей, упростив ваше приложение. - person Matthew Pope; 23.05.2020
comment
Мой вариант использования таков, что у меня нет возможности сохранять данные в удаленный источник. Мне просто нужно автономное хранилище, чтобы, если удаленный источник не дал мне то, что я ищу, локальная база данных могла предоставить данные, которые были сохранены последними, когда сетевой запрос был успешным. - person ravi; 23.05.2020
comment
Похоже, вам действительно нужен кеш перед удаленным хранилищем данных. Я обновлю свой ответ, чтобы объяснить и это. - person Matthew Pope; 23.05.2020
comment
Да спасибо. это именно то, что я искал. :) - person ravi; 23.05.2020

Вы можете попробовать следующий подход, он требует минимальных изменений и у меня работает:

Добавьте интерфейсы для удаленного и локального источника данных, он должен наследовать основной интерфейс DataSource

interface QuotesDataSource {

fun getQuotes(skip: Int = 0, force: Boolean = false): Flow<List<Quote>>

suspend fun updateQuotes(quotes: List<Quote>)
}

interface QuotesRemoteDataSource : QuotesDataSource

interface QuotesLocalDataSource : QuotesDataSource

Затем используйте эти интерфейсы для создания модуля коина

val repoModule = module {
  single { QuotesApi() }
  single<QuotesLocalDataSource> { QuotesDatabase(get()) }
  single<QuotesRemoteDataSource> { QuotesRemote(get()) }
  single<QuotesDataSource> {
      QuotesRepository(
          local = get<QuotesLocalDataSource>(),
          remote = get<QuotesRemoteDataSource>()
      )
  }
}
person Cube    schedule 26.10.2020