Skip to content

Commit

Permalink
In-Memory Cache API (#11)
Browse files Browse the repository at this point in the history
Added In-Memory cache source
  • Loading branch information
kotlitecture committed Jun 24, 2024
1 parent 65824f5 commit bc8f109
Show file tree
Hide file tree
Showing 35 changed files with 895 additions and 28 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
@@ -0,0 +1,14 @@
package kotli.template.multiplatform.compose.dataflow.cache

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

object CacheProvider : BaseDataFlowProvider() {

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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

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 doApply(state: TemplateState) {
state.onApplyRules(
Rules.BuildGradleSharedData,
CleanupMarkedLine("{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
4 changes: 0 additions & 4 deletions template/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,6 @@ android {
sourceCompatibility(libs.versions.android.jvmTarget.get())
targetCompatibility(libs.versions.android.jvmTarget.get())
}
dependencies {
debugImplementation(libs.compose.ui.tooling)
debugImplementation(libs.compose.ui.tooling.preview)
}
}
// {platform.android.config}
// {platform.jvm.config}
Expand Down
22 changes: 12 additions & 10 deletions template/app/src/commonMain/kotlin/kotli/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,31 @@ import androidx.compose.runtime.remember
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import kotli.app.di.get
import kotli.app.feature.navigation.NavigationBarViewModel
import kotli.app.feature.navigation.samples.a.NavigationAViewModel
import kotli.app.feature.navigation.samples.b.NavigationBViewModel
import kotli.app.feature.navigation.samples.c.NavigationCViewModel
import kotli.app.feature.theme.change.ChangeThemeViewModel
import kotli.app.feature.theme.toggle.ToggleThemeViewModel
import kotli.app.showcases.ShowcasesViewModel
import kotli.app.showcases.datasource.cache.basic.BasicCacheViewModel
import kotli.app.showcases.datasource.http.basic.BasicHttpViewModel
import kotli.app.showcases.datasource.keyvalue.`object`.ObjectKeyValueViewModel
import kotli.app.showcases.datasource.keyvalue.primitive.PrimitiveKeyValueViewModel
import kotli.app.showcases.datasource.paging.basic.BasicPagingViewModel
import kotli.app.showcases.datasource.sqldelight.crud.SqlDelightCrudViewModel
import kotli.app.showcases.datasource.sqldelight.paging.SqlDelightPagingViewModel
import kotli.app.showcases.feature.loader.data.DataLoaderShowcaseViewModel
import kotli.app.showcases.navigation.args.from.ArgsNavigationFromViewModel
import kotli.app.showcases.navigation.args.to.ArgsNavigationToViewModel
import kotli.app.showcases.navigation.no_args.from.NoArgsNavigationFromViewModel
import kotli.app.showcases.navigation.no_args.to.NoArgsNavigationToViewModel
import kotli.app.showcases.feature.loader.data.DataLoaderShowcaseViewModel
import kotli.app.ui.loader.LoaderViewModel
import kotli.app.ui.screen.template.TemplateViewModel
import kotli.app.ui.screen.template_no_args.TemplateNoArgsViewModel
import kotli.app.ui.theme.AppThemePersistenceViewModel
import kotli.app.ui.theme.AppThemeProvider
import kotli.app.ui.theme.AppThemeViewModel
import kotli.app.ui.loader.LoaderViewModel
import kotli.app.feature.navigation.NavigationBarViewModel
import kotli.app.feature.navigation.samples.a.NavigationAViewModel
import kotli.app.feature.navigation.samples.b.NavigationBViewModel
import kotli.app.feature.navigation.samples.c.NavigationCViewModel
import kotli.app.feature.theme.change.ChangeThemeViewModel
import kotli.app.feature.theme.toggle.ToggleThemeViewModel
import kotli.app.showcases.datasource.sqldelight.crud.SqlDelightCrudViewModel
import kotli.app.showcases.datasource.sqldelight.paging.SqlDelightPagingViewModel
import shared.presentation.ViewModelProvider

/**
Expand Down Expand Up @@ -66,4 +67,5 @@ private val AppViewModelFactory = viewModelFactory {
initializer { ObjectKeyValueViewModel(get(), get()) }
initializer { SqlDelightCrudViewModel(get(), get()) }
initializer { SqlDelightPagingViewModel(get(), get(), get(), get()) }
initializer { BasicCacheViewModel(get(), get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package kotli.app.datasource.cache

import shared.data.datasource.cache.InMemoryCacheSource

/**
* Decorator class for working with L1 Cache.
*
* Can provide extra methods without impacting facade implementations.
*/
class AppCacheSource : InMemoryCacheSource()
2 changes: 2 additions & 0 deletions template/app/src/commonMain/kotlin/kotli/app/di/DI.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kotli.app.di

import kotli.app.di.datasource.ProvidesAnalyticsSource
import kotli.app.di.datasource.ProvidesCacheSource
import kotli.app.di.datasource.ProvidesConfigSource
import kotli.app.di.datasource.ProvidesHttpSource
import kotli.app.di.datasource.ProvidesKeyValueSource
Expand All @@ -17,6 +18,7 @@ val koinDI = startKoin {
modules(
ProvidesAnalyticsSource,
ProvidesConfigSource,
ProvidesCacheSource,
ProvidesHttpSource,
ProvidesKeyValueSource,
ProvidesPagingSource,
Expand Down
Loading

0 comments on commit bc8f109

Please sign in to comment.