2.0からScalaに対応したWebアプリ開発の人気軽量フレームワーク「Play」について解説し、Webアプリの作り方を紹介する入門連載。今回は、EHCacheを使うキャッシュ機構、非同期HTTP通信、Google Closure Compilerを使うJavaScriptの最適化、RequireJSを使うJavaScript依存関係の解決方法について解説します。
前回の記事「Play2におけるJSONおよびCoffeeScriptの使い方」では、Play2でJSONやCoffeeScriptを使用する方法を紹介しました。
今回も、Play2でよく使用されるであろう、以下のさまざまな機能を紹介します。
連載第5回の「Play 2.1にアップグレードしてコントローラを使いこなす」では、Play2で使用する「セッション」「フラッシュスコープ」という、データを一定期間保持するための機能について説明しました。
そこでも述べていますが、Play2のキャッシュ機構には、以下のような制限があります。
Java EE(J2EE)でいう「HTTPセッション」のような機能は、PlayのCache APIを使用することで実現できるといいました。ここで、そのCache APIについて説明しましょう。
なお、Play2におけるCache APIのデフォルト実装は「EHCache」です。それ以外の実装もプラグインで利用可能です。
キャッシュ機構を使用するには、play.api.Applicationオブジェクトが必要です。そのため、play.api.Play.currentをimportしておく必要があります。キャッシュへの保存/取り出しは、下記のように簡単に行えます。
import play.api.Play.current import play.api.cache.Cache class Point(x:Int,y:Int) ・ ・ ・ Cache.set("point1", Point(3,6),<有効期限の秒数>) val opt: Option[Point] = Cache.getAs[Point]("point1")
cacheもセッションと同じく、単純なkey/valueのストアですが、文字列以外にもオブジェクトなどのさまざまなデータを保存できます。
また、set関数の第3引数に数値を指定することで、Cacheの有効期限を設定できます(※有効期限の秒数はオプション)。
さらに、getOrElse関数でCacheから値を取り出すこともできます。この関数を使用すると、値が見つからない場合には第2引数の値を計算し、それをキャッシュに格納した上で返します。
val p:Point = Cache.getOrElse("point1",<有効期限の秒数>) { //キャッシュが見つからない場合はこの値を保存して返す new Point(0,0) }
なおキャッシュについては、公式サイトの解説も参考にしてください。
Playアプリにおいて、Action呼び出しはできる限り速く完了してレスポンスをクライアントに返さなければいけません。しかし、処理によっては非常に重い計算を行ったり、レスポンスが予測できない外部Webサービスに依存したりすることもあるでしょう。
そういった、レスポンスがまだ生成できない場合は、Futureオブジェクトを使用します。レスポンスとして「Future[Result]」を返すことで、最終的にResult型の値を保証できます。
通常のResultの代わりにFuture[Result]を返せば、処理をブロックせずに即座に結果を返し、別の処理を実行することが可能です。アプリはFutureを返す処理が後で完了したとき、内包された結果(Result)をクライアントへ送信します。
Future[Result]を生成するには、元となるFutureが先に必要です。そのFutureを取得したタイミングで、Future[Result]を取得し、クライアントに結果を返します。
//非同期計算処理を実行 val futureSrcValue: Future[Int] = calcAsync() //futureSrcValueが実際の値を取得できるようになったら、レスポンスを生成 val futureResult: Future[Result] = futureSrcValue.map { num => Ok("Number : " + num) }
上記の例ではcalcAsync関数でFutureオブジェクトを取得します。その実際の値が取得可能になったら、「Ok」をクライアントに返しています。
後述するWS APIや、Akka APIを使った非同期処理、アクターと通信する場合などは、全てFutureを取得して非同期処理を行います。
なお、コードブロックを非同期で実行してFutureを取得する簡単な方法は下記のようになります。
val futureInt: Future[Int] = scala.concurrent.Future { intensiveComputation() }
非同期処理をアクションで実行するに当たって、もう1つ必要なことがあります。いままでアクションの戻り値には、SimpleResultを使用してきましたが、非同期結果を返すためには、SimpleResultをラップしたAsyncResultを使います。
def sample = Action { val future = scala.concurrent.Future { caclSomething() } //AsyncブロックはFuture[Result]からAsyncResultを作成するメソッド Async { future.map(i => Ok("result: " + i)) } }
上記のようにAsyncブロックを使用すると、最終的にActionはAsyncResultを受け取るようになります。
wsライブラリを使用して外部サービスを呼び出して、Futureの動作を確認してみましょう。
Futureを使用した非同期処理のサンプルを作成してみます。まずは、conf/routesファイルに新しいコントローラを登録します。
GET /asyncSample controllers.AsyncController.index()
次に、actionクラスを作成します。actionでは、play.api.libs.ws.WSオブジェクトを使用して、外部サイト(JSONレスポンスを返すサイト)に接続しています。
WSによるHTTP通信は、Future[play.api.libs.ws.Response]を返すため、非同期処理となります。
package controllers import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import play.api.libs.ws import play.api.libs.ws.WS import play.api.mvc.Action import play.api.mvc.Controller object AsyncController extends Controller { def index = Action { Async { //外部サービスからレスポンスデータを取得 val jsonFeed: Future[ws.Response] = WS.url("http://isaacs.couchone.com/registry/_all_docs").get() //外部サービスの処理をしている間、他の処理を行う calc jsonFeed map { response => Ok(response.body) } } } private def calc() { for (i <- 1 to 5) { println("calc:" + i) Thread.sleep(1000) } } }
外部サーバへアクセスしている隙に、calc関数を呼び出して別処理(1秒ごとにコンソール表示)を行っています。外部アクセスが非同期で行われている間に処理しているので、結果を待つことなくcalc関数を実行して、結果取得後、クライアントにレスポンスを返すことができます。
Webアプリでも処理の並列化が当たり前になってきている昨今、サーバの処理をブロックしない非同期処理の実行は重要な手法になっています。公式にある、以下のドキュメントも参考にしてみてください。
Copyright © ITmedia, Inc. All Rights Reserved.