Showing posts with label playframework. Show all posts
Showing posts with label playframework. Show all posts

Wednesday, June 24, 2015

Scala筆記: 回顧Option以及認識新朋友Either

最近又開始比較仔細的研究playframework,
在追Action的時候不小心認識了Either,(這樣寫起來怎麼有點花心感XD)
就趁機整理一下啦!

Either顧名思義就是不是這個就是那個(繞口令來著)
這樣是不是會讓你想起不是有(Some)就是沒有(None)的Option呢?
以下是相關的class以及他們的繼承關係.
sealed abstract class Option[+A] extends Product with Serializable
final case class Some[+A](x: A) extends Option[A]
case object None extends Option[Nothing]
abstract final class Nothing extends Any

之前在處理error handling的時候有用過Option,
成功回傳Some,失敗就回傳None,
但其實會有些問題, 因為我們有時候會需要針對不同的失敗原因個別處理,
像是我們要從資料庫讀取某筆資料, 可能會因為網路問題, 或是資料庫過於忙碌等等因素造成失敗
或者是我們成功得到資料庫的回應但是該筆資料並不存在
把這些林林總總的狀況概括用None來表示其實會太簡略

這個情況下我們就可以使用Either,
以下是相關的class以及他們的繼承關係.
sealed abstract class Either[+A, +B] extends AnyRef
final case class Left[+A, +B](a: A) extends Either[A, B] with Product with Serializable
final case class Right[+A, +B](b: B) extends Either[A, B] with Product with Serializable
他的值可以是類型A或類型B,
通常回傳Left是用來表示失敗,用Right表示成功

因為Action的部分有點複雜, 這邊就從playframework的OAuth library貼個簡單的範例.
  def retrieveRequestToken(callbackURL: String): Either[OAuthException, RequestToken] = {
    val consumer = new DefaultOAuthConsumer(info.key.key, info.key.secret)
    try {
      provider.retrieveRequestToken(consumer, callbackURL)
      Right(RequestToken(consumer.getToken(), consumer.getTokenSecret()))
    } catch {
      case e: OAuthException => Left(e)
    }
  }
再加上Left跟Right都是case class,
所以我們抓到結果之後可以簡單地用match處理回傳值
val reqTok = retrieveRequestToken("localhost:9000/callback")
reqTok match {
  case Right(tok) => // success, continue 
  case Left(ex) => // fail to get request token, dothing
}
大概是這樣!

另外再補充一些細節,
1. sealed
被加上sealed關鍵字的類別, 只能被定義在同一個原始碼檔案的類別繼承.
2. Option[+A]
是covariance的概念, 指的是D如果是A的子類別, 則Option[D]也是Option[A]的子類別


ref:
- Either
- Left
- Option
- 探索Playframework: 如何正確使用thread pool

Wednesday, February 25, 2015

探索Playframework: 如何正確使用thread pool

在scala的世界, 我們常會用Future來處理某些非同步的工作,
用Future, 必需要透過implict的方式提供execution context, 來執行這些工作.

白話的說就是要指定你的code要在哪個thread pool跑.

使用Playframework, 在比較單純的情況下直接用預設的thread pool就可以了.
然後再透過application.conf設定適當的參數.

import play.api.libs.concurrent.Execution.Implicits._

def someAsyncAction = Action.async {
  import play.api.Play.current
  WS.url("http://www.playframework.com").get().map { response =>
    // This code block is executed in the imported default execution context
    // which happens to be the same thread pool in which the outer block of
    // code in this action will be executed.
    Results.Ok("The response code was " + response.status)
  }
}

play {
  akka {
    akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
    loglevel = WARNING
    actor {
      default-dispatcher = {
        fork-join-executor {
          parallelism-factor = 1.0
          parallelism-max = 24
        }
      }
    }
  }
}

import play.api.libs.concurrent.Execution.Implicits._
先來找找我們用的execution context在哪裡,
defaultContext是ActorSystem的dispatcher
而create ActorSystem用的設定是"play"這個關鍵字下面的所有屬性.
接下來就是看ActorSystem如何設定dispatcher囉~
defaultGlobalDispatcher是用DefaultDispatcherId(即akka.actor.default-dispatcher)抓到設定值

大概是這樣囉!


針對不同類型的工作, 有時候我們會不希望所有事情都塞在預設的thread pool做,
要使用其他的thread pool, 只需要先在application.conf加上一組新的設定.
再用lookup讓Akka能找到它就好.
可以直接在產生Future的時候指定execution context或是直接import讓implicit發生作用
my-context {
  fork-join-executor {
    parallelism-factor = 20.0
    parallelism-max = 200
  }
}

object Contexts {
  implicit val myExecutionContext: ExecutionContext = Akka.system.dispatchers.lookup("my-context")
}

Future {
  // Some blocking or expensive code here
}(Contexts.myExecutionContext)

or

import Contexts.myExecutionContext
Future {
  // Some blocking or expensive code here
}

當然實際上要如何設定thread pool還是要參考測試結果啦!

ref:
- playframework source code (2.3.x)
- akka source code (2.3)
- understanding play thread pools (2.3.5)