ScalaCache

Cache LibraryScalaMemoizationAsync CacheRedisMemcached

Cache Library

ScalaCache

Overview

ScalaCache is a facade for the most popular cache implementations, providing a simple, idiomatic Scala API for adding caching to any Scala app with minimal effort.

Details

ScalaCache provides a unified interface to various caching libraries, allowing you to add caching functionality to Scala applications with minimal effort. It supports a wide range of cache backends including Redis, Memcached, Guava Cache, Caffeine, and EhCache. The memoization feature automatically caches method results, using cached results when the same method is called with the same arguments. It also supports Future-based async APIs for performing cache operations in separate threads. The type-safe, idiomatic Scala API design enhances developer experience.

Pros and Cons

Pros

  • Unified API: Operate different cache libraries through a unified interface
  • Rich Backends: Supports Redis, Memcached, Guava, Caffeine, EhCache
  • Memoization: Easy implementation through automatic method result caching
  • Async Support: Future-based asynchronous cache operations
  • Scala Idiomatic: Fits functional programming style
  • TTL Support: Can set expiration times for cache entries

Cons

  • Scala Only: Cannot be used in Java
  • Learning Curve: Requires Scala knowledge
  • Backend Dependent: Subject to limitations of chosen cache library
  • Overhead: Minor performance impact from facade layer

Key Links

Usage Examples

Basic Usage

import scalacache._
import scalacache.caffeine._
import scalacache.memoization._

// Initialize cache
implicit val cache: Cache[String] = CaffeineCache[String]

// Basic cache operations
val result: Option[String] = sync.get("key")
sync.put("key")("value")
sync.remove("key")

// Store with TTL
import scala.concurrent.duration._
sync.put("key", ttl = Some(10.minutes))("value")

Memoization Feature

import scalacache.memoization._

class UserService {
  implicit val cache: Cache[User] = CaffeineCache[User]
  
  // Automatically cache method results
  def getUser(id: Int): User = memoize(None) {
    // Heavy processing like database access
    fetchUserFromDatabase(id)
  }
  
  // Memoization with TTL
  def getUserWithTTL(id: Int): User = memoize(Some(1.hour)) {
    fetchUserFromDatabase(id)
  }
}

Async Cache Operations

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

// Future-based async operations
val futureResult: Future[Option[String]] = async.get("key")

async.put("key")("value").foreach { _ =>
  println("Cache save completed")
}

// Async memoization
def fetchDataAsync(id: Int): Future[Data] = memoizeF(Some(30.minutes)) {
  // Async data fetching
  httpClient.fetchData(id)
}

Using Redis Backend

import scalacache._
import scalacache.redis._
import redis.clients.jedis._

// Redis connection setup
val jedisPool = new JedisPool("localhost", 6379)
implicit val cache: Cache[String] = RedisCache[String](jedisPool)

// Redis-specific operations
val serializer = scalacache.serialization.binary._

class ProductService {
  def getProduct(id: String): Product = memoize(Some(1.day)) {
    productRepository.findById(id)
  }
}

Using Memcached Backend

import scalacache._
import scalacache.memcached._
import net.spy.memcached._

// Memcached connection setup
val memcachedClient = new MemcachedClient(
  AddrUtil.getAddresses("localhost:11211")
)
implicit val cache: Cache[String] = MemcachedCache[String](memcachedClient)

// Cache operation example
def expensiveOperation(param: String): String = memoize(Some(2.hours)) {
  // Heavy processing
  performComplexCalculation(param)
}

Cache Configuration and Customization

import scalacache.caffeine._
import com.github.benmanes.caffeine.cache.Caffeine
import java.util.concurrent.TimeUnit

// Detailed Caffeine configuration
val underlyingCache = Caffeine.newBuilder()
  .maximumSize(10000)
  .expireAfterWrite(1, TimeUnit.HOURS)
  .recordStats()
  .build[String, Entry[String]]()

implicit val cache: Cache[String] = CaffeineCache[String](underlyingCache)

// Get statistics
val stats = underlyingCache.stats()
println(s"Hit rate: ${stats.hitRate()}")

Error Handling

import scala.util.{Try, Success, Failure}

class CacheService {
  implicit val cache: Cache[String] = CaffeineCache[String]
  
  def safeGet(key: String): Try[Option[String]] = {
    Try(sync.get(key)) match {
      case Success(value) => Success(value)
      case Failure(ex) =>
        logger.error(s"Cache access error: $key", ex)
        Failure(ex)
    }
  }
  
  def safeMemoize[T](key: String, ttl: Option[Duration] = None)
                   (f: => T): T = {
    try {
      memoize(ttl)(f)
    } catch {
      case ex: Exception =>
        logger.warn(s"Memoization error, executing directly: $key", ex)
        f
    }
  }
}