Playframework (Scala) でRedisにさくっとアクセスする。non-blockingで。
はじめに
ScalaでRedisにアクセスする例です。ScalaでのRedisクライアントライブラリはいくつか選択肢はあるようですが、ここではJava製のLettuceを使います。
サンプルコードは以下にあります github.com
一見
先にPlayframeworkで実際にRedisにアクセスしているコードをお見せします。 Redisに値をSETしてからGETするだけのControllerのActionです。
@Singleton class RedisController @Inject()( cc: ControllerComponents, redisConnection: StatefulRedisConnection[String, String] // RedisのコネクションをDI )(implicit ec: ExecutionContext) extends AbstractController(cc) { def redis = Action.async { import scala.jdk.FutureConverters._ // JavaのCompletableFutureをScalaのFutureにするコンバータ val asyncCommands = redisConnection.async() // Redisのコマンドの非同期API for { _ <- asyncCommands.set("key", "Hello, Redis!").asScala // Redisに値をセット message <- asyncCommands.get("key").asScala // Redisから値をゲット } yield Ok(message) } }
RedisController.scala - GitHub
アクセスすると "Hello, Redis!" と表示されます。
DI設定等は後述
Lettuceとは
公式ページ: https://lettuce.io/
特徴:
- Java製 Redisクライアントライブラリ
- ノンブロッキングIO (netty NIOベース)
- 3種類のAPIを提供している
- 同期API (blocking IO)
- 非同期API (javaのCompletableFutureベース)
- Reactive API (Reactor coreベース)
- Redis ClusterやSentinelにも対応している。
- Redis Pub/Subも使える
JavaのCompletableFutureはScalaのFutureに簡単に変換できます。 また、Reactive APIはReactive Streamsの実装なのでAkka Streamなどとも相互変換可能です。
DI設定
Playframework標準のGoogle Guiceを使っている場合の設定例です。
まず、LettuceのRedisClientやStatefulRedisConnection (Redisへのコネクション)のプロバイダーを定義します。
// RedisClientのプロバイダー @Singleton class RedisClientProvider @Inject()( config: Configuration, lifecycle: ApplicationLifecycle ) extends Provider[RedisClient] { private val redisClient = RedisClient.create(config.get[String]("lettuce.redis-uri")) // RedisへのURIはapplication.confで設定する // アプリケーションの終了時のクリーンアップ lifecycle.addStopHook { () => redisClient.shutdownAsync().asScala } override val get: RedisClient = redisClient } // StatefulRedisConnectionのプロバイダー @Singleton class StatefulRedisConnectionProvider { // 省略。RedisClientProviderと同様です }
RedisClientProvider, StatefulRedisConnectionProvider - GitHub
次に、Google GuiceのModule設定をします。PlayframeworkはデフォルトではルートパッケージにModule
という名前のクラスを配置すると読み込まれます。
class Module extends AbstractModule { override def configure() = { bind(classOf[RedisClient]) .toProvider(classOf[RedisClientProvider]) bind(new TypeLiteral[StatefulRedisConnection[String, String]] {}) .toProvider(classOf[StatefulRedisConnectionProvider]) } }
型パラメーター付きのクラスのDIを設定するときは、new TypeLiteral[< DIしたい型 >]
とする必要があります。