Skip to content

Commit

Permalink
cache api
Browse files Browse the repository at this point in the history
  • Loading branch information
kotlitecture committed Jun 24, 2024
1 parent ee4cf3f commit 5cd0cde
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 21 deletions.
2 changes: 1 addition & 1 deletion processor/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apply from: "${project.rootDir}/gradle/kotlin/processor.gradle"

group = 'com.kotlitecture.kotli'
version = '0.3.0'
version = '0.4.0'
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotli.engine.template.rule.RenamePackage
import kotli.engine.template.rule.ReplaceMarkedText
import kotli.template.multiplatform.compose.common.CommonProvider
import kotli.template.multiplatform.compose.dataflow.analytics.AnalyticsProvider
import kotli.template.multiplatform.compose.dataflow.cache.CacheProvider
import kotli.template.multiplatform.compose.dataflow.common.CommonDataFlowProvider
import kotli.template.multiplatform.compose.dataflow.config.ConfigProvider
import kotli.template.multiplatform.compose.dataflow.database.DatabaseProvider
Expand Down Expand Up @@ -85,6 +86,7 @@ object MultiplatformComposeTemplateProcessor : BaseTemplateProcessor() {
ConfigProvider,
DatabaseProvider,
KeyValueProvider,
CacheProvider,
HttpProvider,
PagingProvider,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ object Rules {
const val UserFlowNavigationRailProvider = "${UserFlowNavigationDir}/RailProvider.kt"
const val ShowcasesDir = "${CommonAppMainDir}/kotlin/kotli/app/showcases"
const val ShowcasesHttpDir = "${ShowcasesDir}/datasource/http"
const val ShowcasesCacheDir = "${ShowcasesDir}/datasource/cache"
const val ShowcasesPagingDir = "${ShowcasesDir}/datasource/paging"
const val ShowcasesKeyValueDir = "${ShowcasesDir}/datasource/keyvalue"
const val ShowcasesSqlDelightDir = "${ShowcasesDir}/datasource/sqldelight"
Expand All @@ -77,6 +78,7 @@ object Rules {
// dataflow
const val AppConfigSource = "${CommonAppMainDir}/kotlin/kotli/app/datasource/config/AppConfigSource.kt"
const val AnalyticsSource = "*/*AnalyticsSource.kt"
const val CacheSource = "*/*CacheSource.kt"
const val SqlDelightSource = "*/*SqlDelightSource.kt"
const val ConfigSource = "*/*ConfigSource.kt"
const val PagingSource = "*/*Paging*.kt"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ object CommonProvider : BaseFeatureProvider() {
override fun getType(): FeatureType = FeatureTypes.Unspecified
override fun getId(): String = "common"
override fun createProcessors(): List<FeatureProcessor> = listOf(
CommonKtorProcessor
CommonKtorProcessor,
CommonStatelyProcessor
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kotli.template.multiplatform.compose.common

import kotli.engine.BaseFeatureProcessor
import kotli.engine.TemplateState
import kotli.engine.template.VersionCatalogRules
import kotli.engine.template.rule.RemoveMarkedLine

object CommonStatelyProcessor : BaseFeatureProcessor() {

override fun getId(): String = "common.stately"
override fun isInternal(): Boolean = true

override fun doRemove(state: TemplateState) {
state.onApplyRules(
VersionCatalogRules(
RemoveMarkedLine("stately")
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package kotli.template.android.compose.dataflow.cache
package kotli.template.multiplatform.compose.dataflow.cache

import kotli.engine.FeatureProcessor
import kotli.template.android.compose.dataflow.BaseDataFlowProvider
import kotli.template.android.compose.dataflow.cache.basic.BasicCacheProcessor
import kotli.template.multiplatform.compose.dataflow.BaseDataFlowProvider
import kotli.template.multiplatform.compose.dataflow.cache.basic.BasicCacheProcessor

class CacheProvider : BaseDataFlowProvider() {
object CacheProvider : BaseDataFlowProvider() {

override fun getId(): String = "dataflow.cache"
override fun createProcessors(): List<FeatureProcessor> = listOf(
BasicCacheProcessor()
BasicCacheProcessor
)

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
package kotli.template.android.compose.dataflow.cache.basic
package kotli.template.multiplatform.compose.dataflow.cache.basic

import kotli.engine.BaseFeatureProcessor
import kotli.engine.FeatureProcessor
import kotli.engine.TemplateState
import kotli.engine.template.VersionCatalogRules
import kotli.engine.template.rule.CleanupMarkedLine
import kotli.engine.template.rule.RemoveFile
import kotli.engine.template.rule.RemoveMarkedLine
import kotli.template.multiplatform.compose.Rules
import kotli.template.multiplatform.compose.common.CommonStatelyProcessor
import kotli.template.multiplatform.compose.showcases.datasource.cache.CacheShowcasesProcessor
import kotlin.time.Duration.Companion.hours

class BasicCacheProcessor : BaseFeatureProcessor() {
object BasicCacheProcessor : BaseFeatureProcessor() {

const val ID = "dataflow.cache.basic"

override fun getId(): String = ID
override fun getIntegrationEstimate(state: TemplateState): Long = 8.hours.inWholeMilliseconds
override fun dependencies(): List<Class<out FeatureProcessor>> = listOf(
CacheShowcasesProcessor::class.java,
CommonStatelyProcessor::class.java
)

override fun doRemove(state: TemplateState) {
state.onApplyRules("core/data/src/main/kotlin/core/data/datasource/cache", RemoveFile())
state.onApplyRules("*CacheSource*", RemoveFile())
override fun doApply(state: TemplateState) {
state.onApplyRules(
Rules.BuildGradleSharedData,
CleanupMarkedLine("{dataflow.cache.basic}")
)
}

companion object {
const val ID = "dataflow.cache.basic"
override fun doRemove(state: TemplateState) {
state.onApplyRules(
Rules.CacheSource,
RemoveFile()
)
state.onApplyRules(
Rules.AppDIKt,
RemoveMarkedLine("CacheSource")
)
state.onApplyRules(
Rules.BuildGradleSharedData,
RemoveMarkedLine("{dataflow.cache.basic}")
)
state.onApplyRules(
VersionCatalogRules(
RemoveMarkedLine("stately-concurrent-collections")
)
)
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kotli.template.multiplatform.compose.dataflow.database.sqldelight

import kotli.engine.BaseFeatureProcessor
import kotli.engine.FeatureProcessor
import kotli.engine.TemplateState
import kotli.engine.template.VersionCatalogRules
import kotli.engine.template.rule.CleanupMarkedBlock
Expand All @@ -10,6 +11,7 @@ import kotli.engine.template.rule.RemoveMarkedBlock
import kotli.engine.template.rule.RemoveMarkedLine
import kotli.engine.template.rule.RenamePackage
import kotli.template.multiplatform.compose.Rules
import kotli.template.multiplatform.compose.common.CommonStatelyProcessor
import kotli.template.multiplatform.compose.dataflow.paging.cashapp.CashAppPagingProcessor
import kotlin.time.Duration.Companion.hours

Expand All @@ -23,6 +25,10 @@ object SqlDelightProcessor : BaseFeatureProcessor() {

override fun getIntegrationEstimate(state: TemplateState): Long = 4.hours.inWholeMilliseconds

override fun dependencies(): List<Class<out FeatureProcessor>> = listOf(
CommonStatelyProcessor::class.java
)

override fun doApply(state: TemplateState) {
state.onApplyRules(
Rules.BuildGradleApp,
Expand Down Expand Up @@ -55,8 +61,10 @@ object SqlDelightProcessor : BaseFeatureProcessor() {
)
state.onApplyRules(
VersionCatalogRules(
RemoveMarkedLine("touchlab-stately"),
RemoveMarkedLine("sqldelight")
RemoveMarkedLine("sqldelight"),
RemoveMarkedLine("stately-common"),
RemoveMarkedLine("stately-isolate"),
RemoveMarkedLine("stately-iso-collections"),
)
)
state.onApplyRules(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import kotli.engine.BaseFeatureProvider
import kotli.engine.FeatureProcessor
import kotli.engine.FeatureType
import kotli.engine.model.FeatureTypes
import kotli.template.multiplatform.compose.showcases.datasource.cache.CacheShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.datasource.http.HttpShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.datasource.keyvalue.KeyValueShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.datasource.paging.PagingShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.navigation.NavigationShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.feature.loader.data.DataLoaderShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.feature.passcode.PasscodeShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.feature.theme.ThemeShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.feature.theme.change.ChangeThemeShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.feature.theme.toggle.ToggleThemeShowcasesProcessor
import kotli.template.multiplatform.compose.showcases.navigation.NavigationShowcasesProcessor

object ShowcasesProvider : BaseFeatureProvider() {

Expand All @@ -33,7 +34,8 @@ object ShowcasesProvider : BaseFeatureProvider() {
PagingShowcasesProcessor,
HttpShowcasesProcessor,
KeyValueShowcasesProcessor,
DataLoaderShowcasesProcessor
DataLoaderShowcasesProcessor,
CacheShowcasesProcessor
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package kotli.template.multiplatform.compose.showcases.datasource.cache

import kotli.engine.BaseFeatureProcessor
import kotli.engine.TemplateState
import kotli.engine.template.rule.RemoveFile
import kotli.engine.template.rule.RemoveMarkedLine
import kotli.template.multiplatform.compose.Rules

object CacheShowcasesProcessor : BaseFeatureProcessor() {

const val ID = "showcases.datasource.cache"

override fun getId(): String = ID
override fun isInternal(): Boolean = true

override fun doRemove(state: TemplateState) {
state.onApplyRules(
Rules.ShowcasesKt,
RemoveMarkedLine("Cache")
)
state.onApplyRules(
Rules.ShowcasesCacheDir,
RemoveFile()
)
state.onApplyRules(
Rules.AppKt,
RemoveMarkedLine("BasicCacheViewModel")
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Thread-safe API for storing and retrieving any in-memory data. Can be utilized as an L1 Cache when managing HTTP requests, offering an efficient means to present data without delays, but with the ability to update based on expiration and other conditions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Basic In-Memory Cache API
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## Overview

The API can be accessed through:
- `shared.data.datasource.cache.CacheSource` - facade interface at the core module level.
- `app.datasource.cache.AppCacheSource` - decorator class at the app level.

The difference is that the class serves as a **decorator** and can provide extra methods without impacting facade implementations.

Facade **CacheSource** provides the following methods:

- `getState(key: CacheKey<T>, valueProvider: suspend () -> T?): CacheState<T>` - Retrieves the state of a cache entry associated with the specified key.
- `get(key: CacheKey<T>, valueProvider: suspend () -> T?): T?` - Retrieves the value associated with the specified key from the cache.
- `invalidate(type: Class<K>)` - Invalidates all cache entries associated with the specified key type.
- `invalidate(key: K)` - Invalidates the cache entry associated with the specified key.
- `remove(type: Class<K>)` - Removes all cache entries associated with the specified key type.
- `remove(key: K)` - Removes the cache entry associated with the specified key.
- `put(key: CacheKey<T>, value: T)` - Associates the specified value with the specified key in the cache.
- `clear()` - Clears all entries from the cache.

## Example

Both the **facade** and **decorator** are pre-configured via dependency injection (DI) as singletons in `app.di.datasource.ProvidesCacheSource`.

To start using, just inject it to your DI managed class.

```kotlin
class BasicCacheViewModel(
private val cacheSource: CacheSource = get()
) : BaseViewModel() {

val cacheStore = StoreObject<String>()

override fun doBind() {
launchAsync {
val cacheKey = SimpleCacheKey()
val cacheState = cacheSource.getState(cacheKey, ::getDateAsFormattedString)
cacheState.changes().collectLatest(cacheStore::set)
}
}

private fun getDateAsFormattedString(): String {
val time = Clock.System.now()
return time.format(DateTimeComponents.Format {
byUnicodePattern("yyyy-MM-dd HH:mm:ss")
})
}

private data class SimpleCacheKey(
override val ttl: Long = CacheKey.TTL_10_SECONDS
) : CacheKey<String>

}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cache
2 changes: 1 addition & 1 deletion template/shared/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ kotlin {
api(libs.kotlinx.coroutines.core)
api(libs.kotlinx.serialization.json)
implementation(libs.multiplatform.settings.no.arg)
implementation(libs.touchlab.stately.concurrent.collections)
implementation(libs.touchlab.stately.concurrent.collections) // {dataflow.cache.basic}
}
commonTest.dependencies {
implementation(libs.kotlin.test)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ interface CacheKey<T> {
* When a key is immortal, the cache entry will be initialized regardless of the component lifecycle.
*
* This can be useful when you need to proceed with some request without interruptions.
* For example, an OAuth refresh token actualization response needs to be completed and stored locally
* Another example:
*
* an OAuth refresh token actualization response needs to be completed and stored locally
* as an atomic action, so any further calls under OAuth authorization can continue with the newly obtained token.
* If such an action is processed on the server but interrupted and not stored on the client,
* it is possible that the old token becomes outdated and any further request to update it will fail.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

package shared.data.datasource.keyvalue

import co.touchlab.stately.collections.ConcurrentMutableMap
import com.russhwolf.settings.Settings
import shared.data.serialization.SerializationStrategy

Expand Down

0 comments on commit 5cd0cde

Please sign in to comment.