Skip to content

Commit

Permalink
feat: Added caches to business logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Lastaapps committed Jan 19, 2025
1 parent 42417a3 commit 32cebe1
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2025, Petr Laštovička as Lasta apps, All rights reserved
*
* This file is part of Menza.
*
* Menza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Menza is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Menza. If not, see <https://www.gnu.org/licenses/>.
*/

package cz.lastaapps.api.core.domain

import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.CoroutineContext

// Now I would really love to have traits
class FlowParametrizedCache<T, Param>(
private val coroutineContext: CoroutineContext = Dispatchers.Default,
) {
private var cacheLastParam: Option<Triple<Param, Flow<T>, CoroutineScope>> = None
private val cacheMutex: Mutex = Mutex()

suspend operator fun invoke(
params: Param,
block: suspend (param: Param) -> Flow<T>,
): Flow<T> =
cacheMutex.withLock {
when (val param = cacheLastParam) {
is Some if (param.value.first == params) -> param.value.second
else -> {
param.getOrNull()?.third?.cancel()
val scope = CoroutineScope(coroutineContext)
block(params)
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
.also { data ->
cacheLastParam = Some(Triple(params, data, scope))
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, Petr Laštovička as Lasta apps, All rights reserved
* Copyright 2025, Petr Laštovička as Lasta apps, All rights reserved
*
* This file is part of Menza.
*
Expand Down Expand Up @@ -36,6 +36,7 @@ import cz.lastaapps.api.main.domain.usecase.GetImportantRequestParams
import cz.lastaapps.api.main.domain.usecase.GetInfoUC
import cz.lastaapps.api.main.domain.usecase.GetMenzaListUC
import cz.lastaapps.api.main.domain.usecase.GetTodayDishListUC
import cz.lastaapps.api.main.domain.usecase.GetTodayRawDishListUC
import cz.lastaapps.api.main.domain.usecase.GetWeekDishListUC
import cz.lastaapps.api.main.domain.usecase.OpenMenuUC
import cz.lastaapps.api.main.domain.usecase.SyncAllInfoUC
Expand Down Expand Up @@ -74,7 +75,7 @@ val apiModule =
factoryOf(::GetInfoUC)
factoryOf(::SyncInfoUC)
factoryOf(::SyncAllInfoUC)
factoryOf(::GetTodayDishListUC)
singleOf(::GetTodayDishListUC)
factoryOf(::SyncTodayDishListUC)
factoryOf(::GetWeekDishListUC)
factoryOf(::SyncWeekDishListUC)
Expand All @@ -84,7 +85,8 @@ val apiModule =
factoryOf(::WalletLogoutUC)
factoryOf(::WalletRefreshUC)
factoryOf(::GetImportantRequestParams)
factoryOf(::GetDishUC)
singleOf(::GetDishUC)
singleOf(::GetTodayRawDishListUC)

registerMenzaType<MenzaType.Testing.Kocourkov>(
menzaRepo = { KocourkovRepoImpl(get()) },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, Petr Laštovička as Lasta apps, All rights reserved
* Copyright 2025, Petr Laštovička as Lasta apps, All rights reserved
*
* This file is part of Menza.
*
Expand All @@ -21,21 +21,17 @@ package cz.lastaapps.api.main.domain.usecase

import cz.lastaapps.api.core.domain.model.DishOriginDescriptor
import cz.lastaapps.api.core.domain.model.dish.Dish
import cz.lastaapps.api.core.domain.repo.TodayDishRepo
import cz.lastaapps.api.core.domain.sync.getData
import cz.lastaapps.api.rating.domain.usecase.GetDishRatingsUC
import cz.lastaapps.core.domain.UCContext
import cz.lastaapps.core.domain.UseCase
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.parameter.parametersOf

class GetDishUC internal constructor(
context: UCContext,
private val getRequestParamsUC: GetRequestParamsUC,
private val getRawDishList: GetTodayRawDishListUC,
private val getDishRatingsUC: GetDishRatingsUC,
) : UseCase(context),
KoinComponent {
Expand All @@ -44,8 +40,7 @@ class GetDishUC internal constructor(
suspend operator fun invoke(dishOrigin: DishOriginDescriptor) =
launch {
val dishFlow =
get<TodayDishRepo> { parametersOf(dishOrigin.menza) }
.getData(getRequestParamsUC())
getRawDishList(dishOrigin.menza)
.map { categories ->
categories.forEach { category ->
category.dishList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@

package cz.lastaapps.api.main.domain.usecase

import cz.lastaapps.api.core.domain.FlowParametrizedCache
import cz.lastaapps.api.core.domain.model.Menza
import cz.lastaapps.api.core.domain.model.MenzaType
import cz.lastaapps.api.core.domain.model.dish.DishCategory
import cz.lastaapps.api.core.domain.repo.TodayDishRepo
import cz.lastaapps.api.core.domain.sync.getData
import cz.lastaapps.api.rating.domain.usecase.GetDishRatingsUC
import cz.lastaapps.core.domain.UCContext
import cz.lastaapps.core.domain.UseCase
Expand All @@ -34,23 +33,23 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.parameter.parametersOf

class GetTodayDishListUC(
class GetTodayDishListUC internal constructor(
context: UCContext,
private val getRequestParamsUC: GetRequestParamsUC,
private val getRawDishList: GetTodayRawDishListUC,
private val getDishRatingsUC: GetDishRatingsUC,
) : UseCase(context),
KoinComponent {
private val cache =
FlowParametrizedCache<ImmutableList<DishCategory>, MenzaType>()

suspend operator fun invoke(menza: Menza): Flow<ImmutableList<DishCategory>> = invoke(menza.type)

suspend operator fun invoke(menza: MenzaType): Flow<ImmutableList<DishCategory>> =
launch {
val dishFlow =
get<TodayDishRepo> { parametersOf(menza) }
.getData(getRequestParamsUC())
.map {
cache(menza) { menza ->
val dishFlow =
getRawDishList(menza).map {
it
.map { category ->
val newDishList = category.dishList.filter { dish -> dish.isActive }
Expand All @@ -63,24 +62,25 @@ class GetTodayDishListUC(
}.toImmutableList()
}

val ratingsFlow = getDishRatingsUC(menza)
val ratingsFlow = getDishRatingsUC(menza)

combine(
dishFlow.distinctUntilChanged(),
ratingsFlow.distinctUntilChanged(),
) { dishList, ratings ->
dishList
.map { category ->
category.copy(
dishList =
category.dishList
.map { dish ->
ratings[dish.id]?.let {
dish.copy(rating = it)
} ?: dish
}.toImmutableList(),
)
}.toImmutableList()
combine(
dishFlow.distinctUntilChanged(),
ratingsFlow.distinctUntilChanged(),
) { dishList, ratings ->
dishList
.map { category ->
category.copy(
dishList =
category.dishList
.map { dish ->
ratings[dish.id]?.let {
dish.copy(rating = it)
} ?: dish
}.toImmutableList(),
)
}.toImmutableList()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2025, Petr Laštovička as Lasta apps, All rights reserved
*
* This file is part of Menza.
*
* Menza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Menza is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Menza. If not, see <https://www.gnu.org/licenses/>.
*/

package cz.lastaapps.api.main.domain.usecase

import cz.lastaapps.api.core.domain.FlowParametrizedCache
import cz.lastaapps.api.core.domain.model.MenzaType
import cz.lastaapps.api.core.domain.model.dish.DishCategory
import cz.lastaapps.api.core.domain.repo.TodayDishRepo
import cz.lastaapps.api.core.domain.sync.getData
import cz.lastaapps.core.domain.UCContext
import cz.lastaapps.core.domain.UseCase
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.parameter.parametersOf

internal class GetTodayRawDishListUC internal constructor(
context: UCContext,
private val getRequestParamsUC: GetRequestParamsUC,
) : UseCase(context),
KoinComponent {
private val cache = FlowParametrizedCache<ImmutableList<DishCategory>, MenzaType>()

suspend operator fun invoke(menza: MenzaType): Flow<ImmutableList<DishCategory>> =
launch {
cache(menza) { menza ->
get<TodayDishRepo> { parametersOf(menza) }
.getData(getRequestParamsUC())
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, Petr Laštovička as Lasta apps, All rights reserved
* Copyright 2025, Petr Laštovička as Lasta apps, All rights reserved
*
* This file is part of Menza.
*
Expand Down Expand Up @@ -40,7 +40,7 @@ val apiRatingModule =
factoryOf(::RatingAPIImpl) bind RatingAPI::class
singleOf(::RatingRepositoryImpl) bind RatingRepository::class

factoryOf(::GetDishRatingsUC)
singleOf(::GetDishRatingsUC)
factoryOf(::RateDishUC)
factoryOf(::SyncDishRatingsUC)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, Petr Laštovička as Lasta apps, All rights reserved
* Copyright 2025, Petr Laštovička as Lasta apps, All rights reserved
*
* This file is part of Menza.
*
Expand All @@ -19,18 +19,27 @@

package cz.lastaapps.api.rating.domain.usecase

import cz.lastaapps.api.core.domain.FlowParametrizedCache
import cz.lastaapps.api.core.domain.model.MenzaType
import cz.lastaapps.api.core.domain.model.dish.DishID
import cz.lastaapps.api.core.domain.model.rating.Rating
import cz.lastaapps.api.rating.data.repo.RatingRepository
import cz.lastaapps.api.rating.data.repo.RatingRepository.Params
import cz.lastaapps.core.domain.UCContext
import cz.lastaapps.core.domain.UseCase
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.coroutines.flow.Flow

class GetDishRatingsUC internal constructor(
context: UCContext,
private val ratingRepository: RatingRepository,
) : UseCase(context) {
suspend operator fun invoke(menza: MenzaType) =
private val cache = FlowParametrizedCache<ImmutableMap<DishID, Rating>, MenzaType>()

suspend operator fun invoke(menza: MenzaType): Flow<ImmutableMap<DishID, Rating>> =
launch {
ratingRepository.getData(Params(menza))
cache(menza) { menza ->
ratingRepository.getData(Params(menza))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,14 @@ internal class DishListViewModel(
log.i { "Registered a new: $newMenza" }

updateState {
copy(
selectedMenza = newMenza.toOption(),
items = persistentListOf(),
)
if (selectedMenza?.getOrNull()?.type != newMenza?.type) {
copy(
selectedMenza = newMenza.toOption(),
items = persistentListOf(),
)
} else {
this
}
}
syncJob?.cancel()
if (newMenza != null) {
Expand Down

0 comments on commit 32cebe1

Please sign in to comment.