Skip to content

Commit

Permalink
Move instantiation to top-level functions. Use Lettuce's coroutines i…
Browse files Browse the repository at this point in the history
…mplementation. Extract default GetNameStrategy to DefaultGetNameStrategy. Update README.md with more details and new instantiation.
  • Loading branch information
dave08 committed Feb 18, 2024
1 parent 3b0042c commit 2d38124
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 65 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
> This library is still experimental and actively being developed, it may change until it is released. For now, it's just released as snapshots on Jitpack.io.
> [!NOTE]
> For now, this library depends on KotlinX Serialization to serialize computation results to Json for storage in the cache.
> For now, this library depends on KotlinX Serialization to serialize computation results to Json for storage in the cache. So all classes being stored in the caches should be annotated with `@Serializable`, and the plugin should be enabled in the module using the cache.
# Kacheable

Expand All @@ -24,7 +24,7 @@ class Foo() {
val conn = client.connect()

// Use the redis store with Kacheable
val cache = KacheableImpl(RedisKacheableStore(conn))
val cache = Kacheable(RedisKacheableStore(conn))

suspend fun getUser(userId: Int) = cache("user-cache", userId) {
// some db query to retrieve user... this will be cached the first
Expand All @@ -42,7 +42,7 @@ Any cache without a configuration uses defaults, but can be configured per cache
val configs = listOf(CacheConfig("foo", ExpiryType.after_write, 30.minutes)).associateBy { it.name }

// Then initialize the cache with them:
val cache = KacheableImpl(RedisKacheableStore(conn), configs)
val cache = Kacheable(RedisKacheableStore(conn), configs)
```

The options are:
Expand Down Expand Up @@ -74,3 +74,23 @@ suspend fun dontSaveBar(shouldSave: Boolean = false): Bar = cache("foo", saveRes
Bar(32, "something")
}
```

You can also provide your own implementation for naming the cache's key:

```kotlin
// This interface needs to be implemented:
fun interface GetNameStrategy {
fun getName(name: String, params: Array<out Any>): String
}

// Instantiate Kacheable with it.
val MyGetNameStrategy = GetNameStrategy { name, params ->
// This is the default implementation in `DefaultGetNameStrategy`
if (params.isEmpty())
name
else
"$name:${params.joinToString(",")}"
}

val cache = Kacheable(RedisKacheableStore(conn), getNameStrategy = MyGetNameStrategy)
```
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ package com.github.dave08.kacheable

fun interface GetNameStrategy {
fun getName(name: String, params: Array<out Any>): String
}

val DefaultGetNameStrategy: GetNameStrategy = GetNameStrategy { name, params ->
if (params.isEmpty())
name
else
"$name:${params.joinToString(",")}"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.dave08.kacheable

import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer

interface Kacheable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@ package com.github.dave08.kacheable
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json

class KacheableImpl(
val store: KacheableStore,
val configs: Map<String, CacheConfig> = emptyMap(),
val getNameStrategy: GetNameStrategy = GetNameStrategy { name, params ->
if (params.isEmpty())
name
else
"$name:${params.joinToString(",")}"
},
private val jsonParser: Json = Json
internal class KacheableImpl(
private val store: KacheableStore,
private val configs: Map<String, CacheConfig>,
private val getNameStrategy: GetNameStrategy,
private val jsonParser: Json,
) : Kacheable {
/**
* Don't use * in this list yet... I don't think del supports it properly.
Expand Down Expand Up @@ -65,4 +60,11 @@ class KacheableImpl(
jsonParser.decodeFromString(type, result)
}
}
}
}

fun Kacheable(
store: KacheableStore,
configs: Map<String, CacheConfig> = emptyMap(),
getNameStrategy: GetNameStrategy = DefaultGetNameStrategy,
jsonParser: Json = Json
): Kacheable = KacheableImpl(store, configs, getNameStrategy, jsonParser)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.github.dave08.kacheable

import kotlinx.serialization.KSerializer

object NoopKacheable : Kacheable {
internal object NoopKacheable : Kacheable {
override suspend fun <R> invalidate(vararg keys: Pair<String, List<Any>>, block: suspend () -> R): R =
block()

Expand All @@ -13,4 +13,6 @@ object NoopKacheable : Kacheable {
saveResultIf: (R) -> Boolean,
block: suspend () -> R
): R = block()
}
}

fun KacheableNoOp(): Kacheable = NoopKacheable
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
package com.github.dave08.kacheable.blocking

import com.github.dave08.kacheable.CacheConfig
import com.github.dave08.kacheable.DefaultGetNameStrategy
import com.github.dave08.kacheable.ExpiryType
import com.github.dave08.kacheable.GetNameStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json

class BlockingKacheableImpl(
val store: BlockingKacheableStore,
val configs: Map<String, CacheConfig> = emptyMap(),
val getNameStrategy: GetNameStrategy = GetNameStrategy { name, params ->
if (params.isEmpty())
name
else
"$name:${params.joinToString(",")}"
},
private val jsonParser: Json = Json
internal class BlockingKacheableImpl(
private val store: BlockingKacheableStore,
private val configs: Map<String, CacheConfig>,
private val getNameStrategy: GetNameStrategy,
private val jsonParser: Json
) : BlockingKacheable {
/**
* Don't use * in this list yet... I don't think del supports it properly.
Expand Down Expand Up @@ -68,4 +64,11 @@ class BlockingKacheableImpl(
jsonParser.decodeFromString(type, result)
}
}
}
}

fun BlockingKacheable(
store: BlockingKacheableStore,
configs: Map<String, CacheConfig> = emptyMap(),
getNameStrategy: GetNameStrategy = DefaultGetNameStrategy,
jsonParser: Json = Json
) : BlockingKacheable = BlockingKacheableImpl(store, configs, getNameStrategy, jsonParser)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.github.dave08.kacheable.blocking

import com.github.dave08.kacheable.Kacheable
import kotlinx.serialization.KSerializer

object NoopBlockingKacheable : BlockingKacheable {
internal object NoopBlockingKacheable : BlockingKacheable {
override fun <R> invalidate(vararg keys: Pair<String, List<Any>>, block: () -> R): R =
block()

Expand All @@ -13,4 +14,6 @@ object NoopBlockingKacheable : BlockingKacheable {
saveResultIf: (R) -> Boolean,
block: () -> R
): R = block()
}
}

fun BlockingKacheableNoOp(): BlockingKacheable = NoopBlockingKacheable
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package com.github.dave08.kacheable

import io.lettuce.core.ExperimentalLettuceCoroutinesApi
import io.lettuce.core.api.StatefulRedisConnection
import io.lettuce.core.api.coroutines
import kotlinx.coroutines.future.await
import kotlin.time.Duration

@OptIn(ExperimentalLettuceCoroutinesApi::class)
class RedisKacheableStore(private val conn: StatefulRedisConnection<String, String>) : KacheableStore {

override suspend fun delete(key: String) {
conn.async().del(key).await()
conn.coroutines().del(key)
}

override suspend fun set(key: String, value: String) {
conn.async().set(key, value).await()
conn.coroutines().set(key, value)
}

override suspend fun get(key: String): String? =
conn.async().get(key).await()
override suspend fun get(key: String): String? = conn.coroutines().get(key)

override suspend fun setExpire(key: String, expiry: Duration) {
conn.async().pexpire(key, expiry.inWholeMilliseconds).await()
conn.coroutines().pexpire(key, expiry.inWholeMilliseconds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package kacheable
import com.github.dave08.kacheable.CacheConfig
import com.github.dave08.kacheable.ExpiryType
import com.github.dave08.kacheable.blocking.BlockingKacheable
import com.github.dave08.kacheable.blocking.BlockingKacheableImpl
import com.github.dave08.kacheable.blocking.RedisBlockingKacheableStore
import io.kotest.core.spec.style.FreeSpec
import io.kotest.extensions.testcontainers.perTest
Expand All @@ -28,7 +27,7 @@ class BlockingCacheableTest : FreeSpec() {
extensions(container.perTest())

"Saves the result of a function with no parameters" {
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn)))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn)))
val results = mutableListOf<Bar>()

results += (1..5).map { testClass.bar() }
Expand All @@ -43,7 +42,7 @@ class BlockingCacheableTest : FreeSpec() {
}

"Saves the result of a function with multiple parameters" {
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn)))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn)))

testClass.baz(32, "something")

Expand All @@ -52,15 +51,15 @@ class BlockingCacheableTest : FreeSpec() {

"Sets expiry from last write" {
val config = listOf(CacheConfig("BlockingFoo", ExpiryType.after_write, 30.minutes)).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.bar()

expectThat(conn.sync().ttl("BlockingFoo")).isEqualTo((30.minutes).inWholeSeconds)
}

"Saves cache with default configs when not specified" {
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), emptyMap()))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), emptyMap()))

testClass.bar()

Expand All @@ -69,7 +68,7 @@ class BlockingCacheableTest : FreeSpec() {

"Sets expiry from last access" {
val config = listOf(CacheConfig("BlockingFoo", ExpiryType.after_access, 30.minutes)).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.bar()
delay(100)
Expand All @@ -81,7 +80,7 @@ class BlockingCacheableTest : FreeSpec() {
"When function result is null" - {
"and nullPlaceholder setting is not set, the cache entry isn't saved" {
val config = listOf(CacheConfig("BlockingFoo", nullPlaceholder = null)).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.nullBar()

Expand All @@ -90,7 +89,7 @@ class BlockingCacheableTest : FreeSpec() {
"and nullPlaceholder setting is set, the cache value is the placeholder" {
val placeholder = "--placeholder--"
val config = listOf(CacheConfig("BlockingFoo", nullPlaceholder = placeholder)).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.nullBar()

Expand All @@ -99,7 +98,7 @@ class BlockingCacheableTest : FreeSpec() {
"and nullPlaceholder setting is set, null is returned when retrieving the value" {
val placeholder = "--placeholder--"
val config = listOf(CacheConfig("BlockingFoo", nullPlaceholder = placeholder)).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.nullBar()
val result = testClass.nullBar()
Expand All @@ -111,7 +110,7 @@ class BlockingCacheableTest : FreeSpec() {
"Invalidates a cache entry" - {
"without parameters" {
val config = listOf(CacheConfig("BlockingFoo")).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.bar()
testClass.invBar()
Expand All @@ -121,7 +120,7 @@ class BlockingCacheableTest : FreeSpec() {

"with same parameters" {
val config = listOf(CacheConfig("BlockingFoo")).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.baz(32, "something")
testClass.invBaz(32, "something")
Expand All @@ -132,7 +131,7 @@ class BlockingCacheableTest : FreeSpec() {

"Save when condition is fullfilled" {
val config = listOf(CacheConfig("BlockingFoo")).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.dontSaveBar()
val result = testClass.dontSaveBar()
Expand All @@ -156,7 +155,7 @@ class BlockingCacheableTest : FreeSpec() {
"Cache results that are not serializable" - {
"int" {
val config = listOf(CacheConfig("BlockingFoo")).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.primitiveInt()

Expand All @@ -167,7 +166,7 @@ class BlockingCacheableTest : FreeSpec() {

"null int" {
val config = listOf(CacheConfig("BlockingFoo", nullPlaceholder = "null")).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.primitiveNullInt()

Expand All @@ -178,7 +177,7 @@ class BlockingCacheableTest : FreeSpec() {

"boolean" {
val config = listOf(CacheConfig("BlockingFoo")).associateBy { it.name }
val testClass = BlockingFoo(BlockingKacheableImpl(RedisBlockingKacheableStore(conn), config))
val testClass = BlockingFoo(BlockingKacheable(RedisBlockingKacheableStore(conn), config))

testClass.primitiveBoolean()

Expand Down
Loading

0 comments on commit 2d38124

Please sign in to comment.