Play2のstackable-controllerを使ってみる


## 概要

（play2-authの前準備としての記事になります）

stackable-controllerとは、Play2で認証系を担うplay2-authを裏で支えているライブラリです。

（ちなみに、僕は他でどう使えるのかはよくわかってないです。。logging処理を挟みたいならfilterでいけそうですし、DBのトランザクション管理はService層でDIで解決出来そうなので。）

## 環境

- Play2.5
- stackable-controller 0.6.0

## Elementを用意する

まずはStackの対象とするElementを作っていきます。
ここでは、1/2の確率でlong値を生成して返すという処理を書いてみます。

```scala:MyStackLongElement.scala
import akka.actor.ActorSystem
import jp.t2v.lab.play2.stackc.{RequestAttributeKey, RequestWithAttributes, StackableController}
import play.api.mvc.{Controller, Result}

import scala.concurrent.Future
import scala.util.Random

trait MyStackLongElement extends StackableController {
  self: Controller =>

  val actorSystem: ActorSystem
  val random: Random

  private case object LongKey extends RequestAttributeKey[Long]

  override def proceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] => Future[Result]): Future[Result] = {
    implicit val ctx = actorSystem.dispatcher
    val long = random.nextLong()
    val isFail = random.nextBoolean()
    if(isFail) return Future{ BadRequest }

    super.proceed(req.set(LongKey, long))(f)
  }

  def getStackedLong()(implicit req: RequestWithAttributes[_]): Long = req.get(LongKey).get
}
```

このElementは、proceedの中で`req.set(LongKey, long)`をしています。
なんらかの処理結果をここでsetしているわけですね。
（ちなみに、条件が合わない場合はそのままBadRequestなどのResultを返すこともできます。）

値をsetしたことによって、このElementを実装したcontrollerの中では、getStackedLongを通じて`req.get(LongKey).get`を行い値を取得することが出来るようになります。

## Controllerを実装する

```scala:StackController.scala
import javax.inject.{Inject, Singleton}

import akka.actor.ActorSystem
import play.api.mvc.Controller

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random

@Singleton
class StackController @Inject() (
  override val actorSystem: ActorSystem,
  override val random:Random = new Random
) extends Controller with MyStackLongElement {

  implicit val myExecutionContext: ExecutionContext = actorSystem.dispatcher

  def message = AsyncStack { implicit req =>
    Future{
      val long = getStackedLong()
      Ok(long.toString)
    }
  }
}
```

先ほど用意したMyStackLongElementをextendsして、任意の場所でgetStackedLongを呼ぶだけです。
（なお、ここではRandomはテストしやすいようにDIする形にしました。）

stackable-controllerの役割はこれでほぼ全部と認識しています。
（`cleanupOnSucceeded` あたりはfilterに吸収されそう。。。。）

## テストを書く

stackable-controllerを用いていても、普通のControllerと同じようにテストを行えます。

```scala:StackControllerTest.scala
import java.util.concurrent.TimeUnit

import akka.actor.ActorSystem
import akka.util.Timeout
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FunSpec, MustMatchers}
import play.api.mvc.Result
import play.api.test.{FakeRequest, Helpers}

import scala.concurrent.Future
import scala.util.Random

class StackControllerTest extends FunSpec with MustMatchers with MockitoSugar  {

  describe("StackControllerTest") {

    implicit val timeout = Timeout(5000, TimeUnit.MILLISECONDS)

    it("StackController success"){
      val randomMock = mock[Random]
      when(randomMock.nextLong()) thenReturn 100L
      when(randomMock.nextBoolean()) thenReturn false
      val actorSystem = ActorSystem.apply()
      val controller = new StackController(actorSystem, randomMock)

      val result: Future[Result] = controller.message().apply(FakeRequest())
      Helpers.contentAsString(result) mustBe "100"
      Helpers.status(result) mustBe 200
    }

    it("StackController fail"){
      val randomMock = mock[Random]
      when(randomMock.nextLong()) thenReturn 100L
      when(randomMock.nextBoolean()) thenReturn true
      val actorSystem = ActorSystem.apply()
      val controller = new StackController(actorSystem, randomMock)

      val result: Future[Result] = controller.message().apply(FakeRequest())
      Helpers.status(result) mustBe 400
      Helpers.contentAsString(result) mustBe ""
    }
  }
}
```

MyStackLongElementは、randomで1/2で失敗するように作っています。
なのでテスト時にはMockを使って成功・失敗をコントロールして動作確認してみました。

なるべくグローバルに依存しないように作れば、このようにテストも容易になります。

## まとめ

これだけだとあまり嬉しさがわからないと思うので、play2-authの記事を待つべし。