Может ли Interactor объединять результаты нескольких источников данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Может ли Interactor объединять результаты нескольких источников данных?
Да, Interactor не только может, но и часто предназначен для объединения результатов из нескольких источников данных. Это одна из его ключевых задач в архитектуре Clean Architecture или VIPER, где Interactor (Use Case) отвечает за бизнес-логику и координацию данных.
Роль Interactor в обработке множественных источников
Interactor выступает как оркестратор бизнес-процессов. Его цель — абстрагировать Presenter/ViewModel от сложности получения и агрегации данных. Источники могут включать:
- Локальные базы данных (Room, SQLite, Realm)
- Сетевые API (REST, GraphQL, gRPC)
- Кэши (в памяти, на диске)
- Файловую систему
- Сторонние SDK (аналитика, платежи)
- Датчики устройства (GPS, Bluetooth)
Как Interactor объединяет данные: практические подходы
1. Последовательная агрегация
Когда данные из одного источника зависят от результата другого. Например, сначала получаем пользователя из сети, затем его заказы из базы данных.
class GetUserProfileInteractor(
private val userRepo: UserRepository,
private val ordersRepo: OrdersRepository
) {
suspend fun execute(userId: String): UserProfile {
// Последовательные вызовы
val user = userRepo.getUserFromNetwork(userId)
val orders = ordersRepo.getLocalOrders(userId)
// Объединение результатов
return UserProfile(
user = user,
orders = orders,
stats = calculateStats(user, orders) // Дополнительная логика
)
}
}
2. Параллельное получение с корутинами
Для оптимизации, когда источники независимы, используем async/await.
class GetDashboardDataInteractor(
private val newsRepo: NewsRepository,
private val weatherRepo: WeatherRepository,
private val stocksRepo: StocksRepository
) {
suspend fun execute(): DashboardData = coroutineScope {
// Параллельное выполнение
val newsDeferred = async { newsRepo.getLatestNews() }
val weatherDeferred = async { weatherRepo.getCurrentWeather() }
val stocksDeferred = async { stocksRepo.getStockPrices() }
// Ожидание всех результатов
DashboardData(
news = newsDeferred.await(),
weather = weatherDeferred.await(),
stocks = stocksDeferred.await()
)
}
}
3. Стратегии объединения и трансформации
Interactor часто содержит сложную логику обработки:
class GetProductDetailsInteractor(
private val remoteSource: ProductRemoteSource,
private val localSource: ProductLocalSource,
private val analyticsSource: AnalyticsSource
) {
suspend fun execute(productId: String): ProductDetails {
// Стратегия "сначала кэш, потом сеть"
val localProduct = localSource.getProduct(productId)
if (localProduct != null && !isDataExpired(localProduct)) {
analyticsSource.logCacheHit()
return localProduct
}
// Получение и объединение из сети
val remoteProduct = remoteSource.fetchProduct(productId)
val reviews = remoteSource.fetchReviews(productId)
val similar = remoteSource.fetchSimilarProducts(productId)
val enrichedProduct = remoteProduct.copy(
reviews = reviews,
similarProducts = similar,
lastUpdated = System.currentTimeMillis()
)
// Сохранение и логирование
localSource.saveProduct(enrichedProduct)
analyticsSource.logProductView(productId)
return enrichedProduct
}
}
Ключевые преимущества такого подхода
- Инкапсуляция сложности: Presenter не знает, откуда и как берутся данные.
- Тестируемость: Interactor легко тестировать в изоляции, мокируя репозитории.
- Гибкость: Можно менять источники данных, не затрагивая UI-слой.
- Повторное использование: Один Interactor могут использовать несколько Presenter'ов.
- Управление зависимостями: Четкое разделение ответственности между слоями.
Важные ограничения и best practices
- Не смешивайте уровни: Interactor должен работать с абстракциями (репозиториями), а не напрямую с API или БД.
- Избегайте логики отображения: Вся UI-специфичная обработка (форматирование дат, фильтрация для отображения) должна быть в Presenter/ViewModel.
- Обработка ошибок: Решайте на уровне Interactor, какие ошибки критичны, а какие можно игнорировать, подменяя данными из других источников.
- Управление потоком: Используйте корутины, RxJava или Callbacks для асинхронных операций, но сам Interactor не должен знать о деталях диспетчеризации потоков.
Пример реального сценария: Лента социальной сети
class GetFeedInteractor(
private val postRepo: PostRepository,
private val userRepo: UserRepository,
private val adRepo: AdRepository,
private val preferences: UserPreferences
) {
suspend fun execute(): List<FeedItem> {
// Параллельное получение разных типов данных
val postsDeferred = async { postRepo.getPosts(limit = 20) }
val usersDeferred = async { userRepo.getUsers() } // Для обогащения постов
val adsDeferred = async { adRepo.getRelevantAds() }
val posts = postsDeferred.await()
val users = usersDeferred.await().associateBy { it.id }
val ads = adsDeferred.await()
// Объединение и обогащение данных
val enrichedPosts = posts.map { post ->
val author = users[post.authorId]
PostFeedItem(
post = post,
authorName = author?.name ?: "Unknown",
authorAvatar = author?.avatarUrl
)
}
// Применение бизнес-правил (например, фильтрация по предпочтениям)
val filteredPosts = if (preferences.hideAdultContent) {
enrichedPosts.filter { !it.isAdult }
} else enrichedPosts
// Вставка рекламы по бизнес-логике (каждые 5 постов)
return mergePostsWithAds(filteredPosts, ads, frequency = 5)
}
}
Таким образом, Interactor является идеальным местом для консолидации данных из различных источников, превращая сырые данные в готовые бизнес-модели, которые может использовать UI-слой. Это соответствует принципу Single Responsibility Principle — каждый компонент архитектуры выполняет свою четкую задачу.