Home

Awesome

Aedile

main <img src="https://img.shields.io/maven-central/v/com.sksamuel.aedile/aedile-core.svg?label=latest%20release"/> <img src="https://img.shields.io/nexus/s/https/s01.oss.sonatype.org/com.sksamuel.aedile/aedile-core.svg?label=latest%20snapshot&style=plastic"/>

Aedile is a simple Kotlin wrapper for Caffeine which prefers coroutines rather than Java futures.

See changelog

Features

Usage

Add Aedile to your build:

implementation 'com.sksamuel.aedile:aedile-core:<version>'

Next, in your code, create a cache configuration using the standard Caffeine builder. Then, instead of using the buildAsync methods that Caffeine provides, use the asCache or asLoadingCache methods that Aedile provides.

val cache = Caffeine.newBuilder().asCache<String, String>()

With this cache we can request values if present, or supply a suspendable function to compute them.

val value1 = cache.getIfPresent("foo") // value or null

val value2 = cache.get("foo") {
   delay(100) // look ma, we support suspendable functions!
   "value"
}

The asLoadingCache method supports a generic compute function which is used if no specific compute function is provided.

val cache = Caffeine.newBuilder().asLoadingCache<String, String>() {
   delay(1) // look ma, we support suspendable functions!
   "value"
}

cache.get("foo") // uses default compute, will return "value"
cache.get("bar") { "other" } // uses specific compute function to return "other"

Configuration

When creating the cache, Aedile wraps the standard Caffeine configuration options, adding extension functions to make it easier to use Kotlin types - such as kotlin.time.Duration rather than Java's Duration.

For example:

val cache = Caffeine
   .newBuilder()
   .expireAfterWrite(1.hours) // supports kotlin.time.Duration
   .maximumSize(100) // standard Caffeine option
   .asCache<String, String>()

Evictions

Caffeine provides different approaches to eviction:

You can specify a suspendable function to listen to evictions using the withEvictionListener method.

val cache = Caffeine
   .newBuilder()
   .expireAfterWrite(1.hours) // supports kotlin.time.Duration
   .maximumSize(100) // standard Caffeine option
   .asCache<String, String>()
   .withEvictionListener { key, value, cause ->
      when (cause) {
         RemovalCause.SIZE -> println("Removed due to size constraints")
         else -> delay(100) // suspendable for no real reason, but just to show you can!!
      }
   }.asCache<String, String>()

Removals

Similar to evictions, you can specify a suspendable function to listen to removals using the withRemovalListener method.

val cache = Caffeine
   .newBuilder()
   .asCache<String, String>()
   .withRemovalListener { key, value, cause ->
      ...
   }.asCache<String, String>()

Coroutine Context

Aedile will use the context from the calling function for executing the compute functions. You can specify your own context by just switching the context like with any suspendable call.

val cache = Caffeine.newBuilder().asCache<String, String>()
val value = cache.get("foo") {
   withContext(Dispatchers.IO) {
      // blocking database call
   }
}
}

Metrics

Micrometer provides integration which wraps the Caffeine cache classes. To use this, call .underlying() to get access to the wrapped Caffeine instance.

For example:

CaffeineCacheMetrics(cache.underlying().synchronous(), "my-cache-name", tags).bindTo(registry)