msawady’s tech-note

フルスタックエンジニアの学んだことや考えていること

【Scala】Scala 開発で twitter.Future を扱うときの頻出5パターン

はじめに

7月から転職をして Scala をメインに書いています。 2ヶ月ほど開発をしてFuture(twitter.Future) の扱いに慣れたので復習がてらブログにします。

基本的には以下の5パターンの組み合わせでなんとかなってます。

  • API コールの結果の返り値を使って別のAPIコールをする
  • コレクションを引数にしてAPIコールをする
  • 検索して、見つかったものだけ返す
  • 検索して、条件に合うものだけを返す
  • 検索して、見つかったものだけを返す。見つからないときはログに出す。

API コールの結果の返り値を使って別のAPIコールをする

for 式を使うことで良しなに flatMap される。

def fetchSecondResult(): Future[SecondResult] = {
  for {
    firstResult <- callFirstApi()
    secondResult <- callSecondApi(firstResult)
  } yield secondResult
}

コレクションを引数にしてAPIコールをする

twitter.Future の collect, traverseSequentiallyを使う。

def fetchHoges(hogeIds: Seq[Long]): Future[Seq[Hoge]] = {

   // これだと返り値が Seq[Future[Hoge]] になるので嬉しくない
  hogeIds.map(dao.selectHoge(_))

  // 順次実行され、返り値が Future[Seq[Hoge]] になる。
  Future.traverseSequentially(hogeIds)(dao.selectHoge(_)) 

  // 並列実行され、返り値が Future[Seq[Hoge]] になる。
  Future.collect(hogeIds.map(dao.selectHoge(_)) 
}

コレクションを引数に検索をして、見つかったものだけを返す

上のようなケースで selectHogeのようなメソッドのシグネチャは Future[Option[Hoge]] となっているのが殆ど。 見つかったもの(Some)になったものだけを返して、Noneになったものを潰すためにFutureの中身を flatten する。

def fetchHoges(hogeIds: Seq[Long]): Future[Seq[Hoge]] = {

  // Seq[Option[Hoge]] を flatten することで Seq[Hoge] にできる
  Future.collect(hogeIds.map(dao.selectHoge(_)).map(_.flatten)

}

コレクションを引数に検索をして、さらに条件に合うものだけを返す

SQLの where 句にするには厳しい条件で、広めにクエリしてからドメインロジックでフィルタするケースに。

def fetchActiveHoges(hogeIds: Seq[Long]): Future[Seq[Hoge]] = {

  // collect を使うことで filter と map を同時に行うことができる
  Future.collect(
    hogeIds.map(hogeId => dao.selectHoge(hogeId))
  ).map(res => res.collect {
    case Some(hoge) if hoge.isActive => hoge
  })

}

コレクションを引数に検索をして、見つかったものだけを返す。見つからなかったときはログに出す。

見つかったものだけ返せば良いが、デバッグ用にログは吐いておきたいケースに。

def fetchHoges(hogeIds: Seq[Long]): Future[Seq[Hoge]] = {

  // Option#orElse を利用することで、Noneのときの処理を書くことができる。(Default値を入れるケースもありそう)
  Future.collect(
      hogeIds.map(hogeId => dao.selectHoge(hogeId).map(_.orElse {
        warn(s"Hoge for [HogeID=$hogeId] is not found")
        None
      }))
    ).map(_.flatten)
}

おわりに

実務ではもっと複雑なコードになりがちですが、基本的に以上のパターンの組み合わせである程度はなんとかなります。 むしろ、組み合わせが複雑にならないように設計することが重要だと感じていて、今後の課題です。