Play FrameworkのRedis Pluginインストール


2014/9/17 追記

なんとまあドンピシャなタイミングでクラスメソッドさんのblogと内容がもろ被りしてしまいました。(※自分の方が投稿早いしパクリじゃないですよ。)

内容はほぼ同じですが、クラスメソッドさんの方が丁寧に書かれているため、そちらもお勧めです。また、コメントでscalikejdbc作者の@seratch_jaさんとScalazコミッターの@xuwei_kさんがアドバイスしている点は必見ですよ。

ちなみに2週間ほどscala 2.11.1 + Redis Plugin 2.3.0(scala 2.10用)の構成でアプリ開発していますが、特に問題は起きていません。でもやっぱ後々トラブル起きたら困りますし、早々にSedis, Jedis, scala-redisあたりに乗り換えておくのが正解でしょうか。

---追記ここまで---

今回はPlay FrameworkでRedis Pluginを使う方法について書いて行きます。

Redis PluginはPlayでRedisを使えるようにするためのプラグインです。RedisはKeyValueストアで、NoSQL DBの1種です。キャッシュサーバとしての用途もあります。

Redis Pluginとは

redisをPlayで使えるようにするためのものです。ライブラリの1種と考えても良いですが、PlayのCacheの実装を置き換えるプラグインという認識の方が良いと思います。

置き換える、とは例えばこういうことです。

package controllers

import play.api.cache.Cache
import play.api.Play.current
import play.api.mvc._

object Application extends Controller {

  def sample = Action {
    Cache.set("string-key", "ABCD", 3000)
    val value: String = Cache.getAs[String]("string-key").get
    Ok(value)
  }
}

Cacheを使ってRedisに値を格納したり取り出したりしています。play.api.cache.Cacheのデフォルト実装を置き換えているため、Redisを経由するようになります。

Redis Pluginは以下の場所で公開されています。

https://github.com/typesafehub/play-plugins/tree/master/redis

Redis Pluginのインストール

上記のURLのREADME.mdにインストール方法が書いてあります。

手順は以下です。

  1. build.sbtにdependencyを追記する

    libraryDependencies ++= Seq(
      "com.typesafe.play.plugins" %% "play-plugins-redis" % "2.3.0"
    )
    
  2. app/conf/play.pluginsファイルを作成し、以下を記述する

    550:com.typesafe.plugin.RedisPlugin
    
  3. conf/application.confに以下を追記する

    ehcacheplugin=disabled
    

公式に書いてあることはこれだけですが、場合によってはこれだけでは上手く行きません。

ビルド中にRedis Pluginが見つからないというエラーが出ることがあります。

[warn] 	::::::::::::::::::::::::::::::::::::::::::::::
[warn] 	::          UNRESOLVED DEPENDENCIES         ::
[warn] 	::::::::::::::::::::::::::::::::::::::::::::::
[warn] 	:: com.typesafe.play.plugins#play-plugins-redis_2.11;2.3.0: not found
[warn] 	::::::::::::::::::::::::::::::::::::::::::::::
sbt.ResolveException: unresolved dependency: com.typesafe.play.plugins#play-plugins-redis_2.11;2.3.0: not found
    at sbt.IvyActions$.sbt$IvyActions$$resolve(IvyActions.scala:217)
    at sbt.IvyActions$$anonfun$update$1.apply(IvyActions.scala:126)
    at sbt.IvyActions$$anonfun$update$1.apply(IvyActions.scala:125)
    at sbt.IvySbt$Module$$anonfun$withModule$1.apply(Ivy.scala:115)
    at sbt.IvySbt$Module$$anonfun$withModule$1.apply(Ivy.scala:115)
    at sbt.IvySbt$$anonfun$withIvy$1.apply(Ivy.scala:103)
    at sbt.IvySbt.sbt$IvySbt$$action$1(Ivy.scala:48)
    at sbt.IvySbt$$anon$3.call(Ivy.scala:57)
    at xsbt.boot.Locks$GlobalLock.withChannel$1(Locks.scala:93)
    at xsbt.boot.Locks$GlobalLock.xsbt$boot$Locks$GlobalLock$$withChannelRetries$1(Locks.scala:81)
    at xsbt.boot.Locks$GlobalLock$$anonfun$withFileLock$1.apply(Locks.scala:102)
    at xsbt.boot.Using$.withResource(Using.scala:11)
    at xsbt.boot.Using$.apply(Using.scala:10)
    at xsbt.boot.Locks$GlobalLock.ignoringDeadlockAvoided(Locks.scala:62)
    at xsbt.boot.Locks$GlobalLock.withLock(Locks.scala:52)
    at xsbt.boot.Locks$.apply0(Locks.scala:31)
    at xsbt.boot.Locks$.apply(Locks.scala:28)
    at sbt.IvySbt.withDefaultLogger(Ivy.scala:57)
    at sbt.IvySbt.withIvy(Ivy.scala:98)
    at sbt.IvySbt.withIvy(Ivy.scala:94)
    at sbt.IvySbt$Module.withModule(Ivy.scala:115)
    at sbt.IvyActions$.update(IvyActions.scala:125)
    at sbt.Classpaths$$anonfun$sbt$Classpaths$$work$1$1.apply(Defaults.scala:1223)
    at sbt.Classpaths$$anonfun$sbt$Classpaths$$work$1$1.apply(Defaults.scala:1221)
    at sbt.Classpaths$$anonfun$doWork$1$1$$anonfun$74.apply(Defaults.scala:1244)
    at sbt.Classpaths$$anonfun$doWork$1$1$$anonfun$74.apply(Defaults.scala:1242)
    at sbt.Tracked$$anonfun$lastOutput$1.apply(Tracked.scala:35)
    at sbt.Classpaths$$anonfun$doWork$1$1.apply(Defaults.scala:1246)
    at sbt.Classpaths$$anonfun$doWork$1$1.apply(Defaults.scala:1241)
    at sbt.Tracked$$anonfun$inputChanged$1.apply(Tracked.scala:45)
    at sbt.Classpaths$.cachedUpdate(Defaults.scala:1249)
    at sbt.Classpaths$$anonfun$updateTask$1.apply(Defaults.scala:1214)
    at sbt.Classpaths$$anonfun$updateTask$1.apply(Defaults.scala:1192)
    at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
    at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:42)
    at sbt.std.Transform$$anon$4.work(System.scala:64)
    at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237)
    at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237)
    at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18)
    at sbt.Execute.work(Execute.scala:244)
    at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237)
    at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237)
    at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:160)
    at sbt.CompletionService$$anon$2.call(CompletionService.scala:30)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
[error] (*:update) sbt.ResolveException: unresolved dependency: com.typesafe.play.plugins#play-plugins-redis_2.11;2.3.0: not found

これは使用しているScalaのバージョンが2.11の場合に出ます。Redis Pluginはこのリポジトリ(http://repo.typesafe.com/typesafe/releases/com/typesafe/play/plugins/)で公開されていますが、どうやら2.11用のバイナリはビルドされていないようです・・・。

結局どうするのがベストかは分かりませんが、自分で2.11用にビルドしたものを用意するのは面倒なので、2.10用のものを使うことにします。

"com.typesafe.play.plugins" % "play-plugins-redis_2.10" % "2.3.0"

build.sbtを上記のように変更してバージョン直接指定にすると2.10用のものを取るようになります。

この状態でビルド今度はsedisが無いよ、と言われます。

[warn] 	::::::::::::::::::::::::::::::::::::::::::::::
[warn] 	::          UNRESOLVED DEPENDENCIES         ::
[warn] 	::::::::::::::::::::::::::::::::::::::::::::::
[warn] 	:: org.sedis#sedis_2.10.0;1.1.1: not found
[warn] 	::::::::::::::::::::::::::::::::::::::::::::::
sbt.ResolveException: unresolved dependency: org.sedis#sedis_2.10.0;1.1.1: not found

sedisはScala用のRedisライブラリですが、Redis Pluginは内部でsedisを使っているからこうなるんですね。ではbuild.sbtに以下を追記し、sedisが置いてあるリポジトリから引っ張って来れるようにします。

resolvers += "Sedis Repo" at "http://pk11-scratch.googlecode.com/svn/trunk"

この状態でコンパイルするとcross-versionエラーが出る場合がありますが、その場合はbuild.sbtに以下を追記するとエラーを抑止できます。

conflictWarning := ConflictWarning.disable

こういうことしたくないのであれば2.11用Redis Pluginをコンパイルして用意しておくほうが良いですね。早く公式で配布して欲しいです。

使い方

準備が長かったですが、ここまでで完了です。Redisをローカルマシンにインストールして起動しておいた状態で、Playアプリを起動すればOKです。

コードは冒頭にもありますが、Cacheのインタフェースと変わりませんので、こんな感じで使います。

package controllers

import play.api.cache.Cache
import play.api.Play.current
import play.api.mvc._

object Application extends Controller {

  def sample = Action {
    Cache.set("string-key", "ABCD", 3000)
    val value: String = Cache.getAs[String]("string-key").get
    Ok(value)
  }
}

import play.api.Play.currentは一見不要な様に見えますが、無いとエラーになります。(Application contextを表現するためのもので、Redis Pluginはconfigファイルとかにアクセスする必要があるので、そのためにimportしておく必要があるのかな?)

ローカル以外のRedisに接続する方法や細かい設定は公式にあるので、それをご覧下さい。基本的にconf/application.confにredis.host=xxxxというように書くだけです。

Cache実装を差し替えるRedis Plugin、機能は少ないですが、単純でなかなか良いと思います。