Cache.getOrElse on Futures in Playframework
Asked Answered
K

2

5

What is the correct way to cache the result of future in playframework. E.g.:

val userGravatar: Future[JsValue] = RemoteGravatarService.get(user.id, user.email)

object RemoveGravatarService {
  def get(userId: String, email: String): Future[JsValue] = {
    Cache.getOrElse("gravatar-for-$userId", 1.hour) {
      WS.url("gravatar.com/email=$email").get().asJson
    }
  }
}

We don't want to ask (this fictional) "Gravatar" every time, because it doesn't change that often. But we need som userGravatar info quite often locally.

Here we cache the future itself, but actually we only want to cache the result of the future. Is there a convenient/correct way to do this in play?

Kaila answered 5/1, 2016 at 13:0 Comment(2)
Any type, but let's say JsValue (since it's serializeable). Updated example with type annotations. The important thing here is that I want the cache to respond with Future.successful(previousJsonResult) if the value is ready in cache.Kaila
You could use spray cacheLime
B
7

There isn't a method in Play's API that will handle Futures. You can wrap Play's cache API to handle the orElse case where it returns a Future. Generically:

object FutureCache {

    def getOrElse[A](key: String, expiration: Int)(orElse: => Future[A])
      (implicit app: Application, ct: ClassTag[A], ec: ExecutionContext): Future[A] = {
        Cache.getAs[A](key).map(Future.successful).getOrElse {
            val value = orElse
            value onSuccess { case result => Cache.set(key, result, expiration) }
            value
        }
    }

}

Usage:

FutureCache.getOrElse[JsValue]("gravatar-for-$userId", 3600) {
  WS.url("gravatar.com/email=$email").get().map(_.json)
}

You could also create an overload for getOrElse that uses Duration.

Basketry answered 5/1, 2016 at 16:26 Comment(0)
N
0

You could use a Promise[JsValue], something like that:

val userGravatar: Future[JsValue] = RemoteGravatarService.get(user.id, user.email)

object RemoveGravatarService {
  def get(userId: String, email: String): Future[JsValue] = {
    Cache.getOrElse("gravatar-for-$userId", 1.hour) {
      Promise[JsValue].completeWith(WS.url("gravatar.com/email=$email").get().map(_.json)
    }.future
  }
}
Navigable answered 8/11, 2016 at 11:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.