2016/11/21

Play cache API


Scala Play 建議使用 EHCache 作為 cache solution,這在 java 領域是一個常見的套件,目前先看看官方的 cache 方式,畢竟已經整合到 play framework 中,未來再看看有沒有辦法改用 redis。


設定 cache


build/sbt 中增加 cache 這個 libraryDependencies


libraryDependencies ++= Seq(
  cache,
  ...
)

app/controllers/CacheApplication.scala


  • cache.set 新增 cache item
  • cache.get 取得 cache item
  • cache.remove 移除 cache item
  • cache.getOrElse 取得 cache item,如果找不到就新增

package controllers

import java.util.concurrent.TimeoutException
import javax.inject.Inject

import akka.actor.ActorSystem
import akka.pattern.after
import models.{Project, ProjectRepo, TaskRepo}
import play.api.Logger
import play.api.cache.CacheApi
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.mvc.{Action, Controller}

import scala.concurrent.Future
import scala.concurrent.duration._

class CacheApplication @Inject()(cache: CacheApi)
                           extends Controller {
  def newCache(name: String) = Action {
    val result = s"Add new project ${name} to cache..\n"

    val project: Project = Project(999, name)
    cache.set("project."+name, project)
    Ok(result)
  }

  def newCache2(name: String) = Action {
    val result = s"Add new project ${name} to cache with 5 minuts..\n"

    val project: Project = Project(888, name)
    cache.set("project."+name, project, 5.minutes)

    // 移除 cache item
    //cache.remove("project."+name)
    Ok(result)
  }

  def getCache(name:String) = Action {
    val project: Option[Project] = cache.get[Project]("project."+name)

    val result = project match {
      // 以 Some 測試 name 有沒有存在
      case Some(pj) => s"get project from cache: ${pj.name}"
      case None => s"can't get project from cache.."
    }

    Ok(result)
  }

  def getCache2(name:String) = Action {
    val project: Project = cache.getOrElse[Project]("project."+name) {
      // 如果 cache 中找不到 project.name 就產生一個新的,存到 cache 中
      Project(777, name)
    }

    val result = s"getOrElse project from cache: ${project.name}"
    Ok(result)
  }

}

conf/routes


GET           /cache/:name               controllers.CacheApplication.newCache(name:String)
GET           /cache2/:name               controllers.CacheApplication.newCache2(name:String)
GET           /getcache/:name            controllers.CacheApplication.getCache(name:String)
GET           /getcache2/:name           controllers.CacheApplication.getCache2(name:String)

測試


$ curl 'http://localhost:9000/cache/cproject'
Add new project cproject to cache..

$ curl 'http://localhost:9000/getcache/cproject'
get project from cache: cproject

$ curl 'http://localhost:9000/cache2/cproject'
Add new project cproject to cache with 5 minuts..

$ curl 'http://localhost:9000/getcache2/cproject'
getOrElse project from cache: cproject

預設的 cache store


預設緩存叫 play, 並可以利用 ehcache.xml 來設定新的 cache store。


調整 application.conf


play.cache {
  # If you want to bind several caches, you can bind the individually
  bindCaches = ["db-cache", "user-cache", "session-cache"]
}

在 controller/Application.scala 中使用新的 cache store


import play.api.cache._
import play.api.mvc._
import javax.inject.Inject

class Application @Inject()(
    @NamedCache("session-cache") sessionCache: CacheApi
) extends Controller {

}

Caching HTTP responses


可以將 HTTP response 放進 cache


首先利用 cached: Cached 來 cache actions


import play.api.cache.Cached
import javax.inject.Inject

class Application @Inject() (cached: Cached) extends Controller {

}

例如這個方式,就是將 home 放入 cache


  def cacheAction = cached("homePage") {
    Action {
      Ok("Hello World")
    }
  }

也可以搭配 Authenticated,為每一個 user 暫存不同的 result


def userProfile = Authenticated {
  user =>
    cached(req => "profile." + user) {
      Action {
        Ok(views.html.profile(User.find(user)))
      }
    }
}

可以選擇只要 cache 200 OK 的 response


def get(index: Int) = cached.status(_ => "/resource/"+ index, 200) {
  Action {
    if (index > 0) {
      Ok(Json.obj("id" -> index))
    } else {
      NotFound
    }
  }
}

或是將 404 Not Found 暫存幾分鐘


def get(index: Int) = {
  val caching = cached
    .status(_ => "/resource/"+ index, 200)
    .includeStatus(404, 600)

  caching {
    Action {
      if (index % 2 == 1) {
        Ok(Json.obj("id" -> index))
      } else {
        NotFound
      }
    }
  }
}

Custom Cache API


如果要自己實作新的 Cache API


要先在 application.conf disable EhCacheModule


play.modules.disabled += "play.api.cache.EhCacheModule"

然後用新的 cache API implementation,並 reuse NamedCache 綁定該 implementation。


References


Scala Cache


Play 緩存 API