Skip to content

Commit

Permalink
Add a typed API for more typesafe caching
Browse files Browse the repository at this point in the history
Use of the typed API means you can't accidentally put something of the
wrong type into your cache.

e.g.

```
import scalacache._
implicit val sc: ScalaCache = ...

val intCache = typed[Int]

intCache.put("one")(1) // OK
intCache.put("two")(2.0) // Nope! Compiler error

val one = intCache.get("1") // returns a Future[Option[Int]]
```

Use of the typed API is optional. Users can still use the existing,
un-typesafe API if they prefer to play fast and loose with their types.
  • Loading branch information
cb372 committed Aug 19, 2015
1 parent a5d29c5 commit ca58351
Showing 1 changed file with 102 additions and 56 deletions.
158 changes: 102 additions & 56 deletions core/src/main/scala/scalacache/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,99 @@ import scala.util.{ Failure, Success, Try }

package object scalacache extends StrictLogging {

class TypedApi[V](implicit val scalaCache: ScalaCache) {

def get(keyParts: Any*)(implicit flags: Flags): Future[Option[V]] = getWithKey(toKey(keyParts))

def getSync(keyParts: Any*)(implicit flags: Flags): Option[V] = getSyncWithKey(toKey(keyParts))

def put(keyParts: Any*)(value: V, ttl: Option[Duration] = None)(implicit flags: Flags): Future[Unit] =
putWithKey(toKey(keyParts), value, ttl)

def remove(keyParts: Any*): Future[Unit] =
scalacache.remove(toKey(keyParts))

def removeAll(): Future[Unit] =
scalacache.removeAll()

def caching(keyParts: Any*)(f: => V)(implicit flags: Flags): V = {
val key = toKey(keyParts)
val fromCache = Try(getSyncWithKey(key)) match {
case Success(result) => result
case Failure(e) =>
logger.warn(s"Failed to read from cache. Key = $key", e)
None
}
fromCache getOrElse {
val result = f
Try(putWithKey(key, result, None)) recover {
case e => logger.warn(s"Failed to write to cache. Key = $key", e)
}
result
}
}

def cachingWithTTL(keyParts: Any*)(ttl: Duration)(f: => V)(implicit flags: Flags): V = {
val key = toKey(keyParts)
val fromCache = Try(getSyncWithKey(key)) match {
case Success(result) => result
case Failure(e) =>
logger.warn(s"Failed to read from cache. Key = $key", e)
None
}
fromCache getOrElse {
val result = f
Try(putWithKey(key, result, Some(ttl))) recover {
case e => logger.warn(s"Failed to write to cache. Key = $key", e)
}
result
}
}

private def getWithKey(key: String)(implicit flags: Flags): Future[Option[V]] = {
if (flags.readsEnabled) {
scalaCache.cache.get(key)
} else {
logger.debug(s"Skipping cache GET because cache reads are disabled. Key: $key")
Future.successful(None)
}
}

private def getSyncWithKey(key: String)(implicit flags: Flags): Option[V] =
Await.result(getWithKey(key), Duration.Inf)

private def putWithKey(key: String, value: V, ttl: Option[Duration] = None)(implicit flags: Flags): Future[Unit] = {
if (flags.writesEnabled) {
scalaCache.cache.put(key, value, ttl)
} else {
logger.debug(s"Skipping cache PUT because cache writes are disabled. Key: $key")
Future.successful(())
}
}
}

/**
* Create an instance of the 'typed' API that limits use of the cache to a single type.
*
* This means you can't accidentally put something of the wrong type into your cache,
* and it reduces keystrokes because you don't need to specify types when calling `get`, `put`, etc.
*
* e.g. {{{
* import scalacache._
* implicit val sc: ScalaCache = ...
*
* val intCache = typed[Int]
*
* intCache.put("one")(1) // OK
* intCache.put("two")(2.0) // Nope! Compiler error
*
* val one = intCache.get("1") // returns a Future[Option[Int]]
* }}}
*
* @tparam V the type of values that the cache will accept
*/
def typed[V](implicit scalaCache: ScalaCache) = new TypedApi[V]()(scalaCache)

/**
* Get the value corresponding to the given key from the cache.
*
Expand All @@ -15,7 +108,8 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the corresponding value
* @return the value, if there is one
*/
def get[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Future[Option[V]] = getWithKey(toKey(keyParts))
def get[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Future[Option[V]] =
typed[V].get(keyParts: _*)

/**
* Convenience method to get a value from the cache synchronously. Warning: may block indefinitely!
Expand All @@ -24,7 +118,8 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the corresponding value
* @return the value, if there is one
*/
def getSync[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Option[V] = getSyncWithKey(toKey(keyParts))
def getSync[V](keyParts: Any*)(implicit scalaCache: ScalaCache, flags: Flags): Option[V] =
typed[V].getSync(keyParts: _*)

/**
* Insert the given key-value pair into the cache, with an optional Time To Live.
Expand All @@ -37,7 +132,7 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the corresponding value
*/
def put[V](keyParts: Any*)(value: V, ttl: Option[Duration] = None)(implicit scalaCache: ScalaCache, flags: Flags): Future[Unit] =
putWithKey(toKey(keyParts), value, ttl)
typed[V].put(keyParts: _*)(value, ttl)

/**
* Remove the given key and its associated value from the cache, if it exists.
Expand Down Expand Up @@ -68,22 +163,8 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def caching[V](keyParts: Any*)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V = {
val key = toKey(keyParts)
val fromCache = Try(getSyncWithKey(key)) match {
case Success(result) => result
case Failure(e) =>
logger.warn(s"Failed to read from cache. Key = $key", e)
None
}
fromCache getOrElse {
val result = f
Try(putWithKey(key, result, None)) recover {
case e => logger.warn(s"Failed to write to cache. Key = $key", e)
}
result
}
}
def caching[V](keyParts: Any*)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V =
typed[V].caching(keyParts: _*)(f)

/**
* Wrap the given block with a caching decorator.
Expand All @@ -98,45 +179,10 @@ package object scalacache extends StrictLogging {
* @tparam V the type of the block's result
* @return the result, either retrived from the cache or returned by the block
*/
def cachingWithTTL[V](keyParts: Any*)(ttl: Duration)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V = {
val key = toKey(keyParts)
val fromCache = Try(getSyncWithKey(key)) match {
case Success(result) => result
case Failure(e) =>
logger.warn(s"Failed to read from cache. Key = $key", e)
None
}
fromCache getOrElse {
val result = f
Try(putWithKey(key, result, Some(ttl))) recover {
case e => logger.warn(s"Failed to write to cache. Key = $key", e)
}
result
}
}
def cachingWithTTL[V](keyParts: Any*)(ttl: Duration)(f: => V)(implicit scalaCache: ScalaCache, flags: Flags): V =
typed[V].cachingWithTTL(keyParts: _*)(ttl)(f)

private def toKey(parts: Seq[Any])(implicit scalaCache: ScalaCache): String =
scalaCache.keyBuilder.toCacheKey(parts)(scalaCache.cacheConfig)

private def getWithKey[V](key: String)(implicit scalaCache: ScalaCache, flags: Flags): Future[Option[V]] = {
if (flags.readsEnabled) {
scalaCache.cache.get(key)
} else {
logger.debug(s"Skipping cache GET because cache reads are disabled. Key: $key")
Future.successful(None)
}
}

private def getSyncWithKey[V](key: String)(implicit scalaCache: ScalaCache, flags: Flags): Option[V] =
Await.result(getWithKey(key), Duration.Inf)

def putWithKey[V](key: String, value: V, ttl: Option[Duration] = None)(implicit scalaCache: ScalaCache, flags: Flags): Future[Unit] = {
if (flags.writesEnabled) {
scalaCache.cache.put(key, value, ttl)
} else {
logger.debug(s"Skipping cache PUT because cache writes are disabled. Key: $key")
Future.successful(())
}
}

}

0 comments on commit ca58351

Please sign in to comment.