下準備としてパッケージオブジェクトあたりに以下のおまじないを書いておく1 この時点ですでに面倒だ!
import com.twitter.util.Future
import scalaz._
import Scalaz._
implicit def FutureFunctor: Functor[Future] = new Functor[Future] {
def map[A, B](f: Future[A])(map: A => B): Future[B] = f.map(map(_))
}
implicit def FutureMonad: Monad[Future] = new Monad[Future] {
def point[A](a: => A) = Future.value(a)
def bind[A, B](f: Future[A])(fmap: A => Future[B]) = f.flatMap(fmap(_))
}
type ReaderTFuture[A, B] = ReaderT[Future, A, B]
object ReaderTFuture extends KleisliInstances with KleisliFunctions {
def apply[A, B](f: A => Future[B]): ReaderTFuture[A, B] = kleisli(f)
}
Finch + MySQLでREST APIサーバを構築するで書いたUserがimplicit parameterを使ってClientを受け取っているので、これをReaderTに置き換えてみた。
import com.twitter.finagle.exp.mysql._
case class User(id: Long, email: String, screen_name: String)
object User {
def find(id: Long): ReaderTFuture[Client, Option[User]] = ReaderTFuture { client =>
client.prepare("SELECT * FROM users WHERE id = ?")(id) map { result =>
result.asInstanceOf[ResultSet].rows.map(convertToEntity).headOption
}
}
def create(email: String, screen_name: String): ReaderTFuture[Client, Long] = ReaderTFuture { client =>
client.prepare("INSERT INTO users (email, screen_name) VALUES(?, ?)")(email, screen_name) map { result =>
result.asInstanceOf[OK].insertId
}
}
def convertToEntity(row: Row): User = {
val LongValue(id) = row("id").get
val StringValue(email) = row("email").get
val StringValue(screen_name) = row("screen_name").get
User(id, email, screen_name)
}
}
こんな感じで使うことができる。
val client = Mysql.client
.withCredentials("user", "password")
.withDatabase("database")
.newRichClient("127.0.0.1:3306")
(for {
id <- User.create("user1@example.com", "user1")
user <- User.find(id)
} yield user match {
case Some(v) => Created(v)
case _ => NotFound(new Exception("xxxxx"))
}).run(client)
上記のように.run
でまとめて渡すことも出来るしval user = User.find(id)(client)
と直接渡すことも出来るので、使いやすいほうを選べば良さそう。
参考文献
- 独習 Scalaz — モナド変換子
- Scala における Repository の実装パターンを考える -模索篇- - sandbox
- Scala で IO コンテキストの共有を implicit 以外で解決する方法 (0) - sandbox
- Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 - Qiita
- 【Scala Days 2014】The Reader Monad for Dependency Injection を解説してみた | Scala Tech Blog
-
FutureをFunctorとMonadの型クラスのインスタンスにしておく必要がある。ReaderTではなくReader使う場合は不要。 ↩︎