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にインストール方法が書いてあります。
手順は以下です。
-
build.sbtにdependencyを追記する
libraryDependencies ++= Seq( "com.typesafe.play.plugins" %% "play-plugins-redis" % "2.3.0" )
-
app/conf/play.pluginsファイルを作成し、以下を記述する
550:com.typesafe.plugin.RedisPlugin
-
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、機能は少ないですが、単純でなかなか良いと思います。