Archives for 12月2012

2.0以降のPlay JavaでパワーアップしたPlayのOptionをつかう

この記事は、Play or Scala Advent Calendar 2012の23日目の記事です。

Playには、昔からF.javaという怪しげなライブラリ群があり、関数型プログラミングっぽい記述の手伝いをして、フレームワークを支えてきました。 その中でも今回は、PlayにおけるOptionの話を書こうと思います。Play Javaな人向けの記事です。PlayのOptionについては、ScalaのOptionの簡易版のようなものと想像してもらうといいかもしれません。

PlayのOptionのつかいかた

PlayのOptionは、play.libs以下にある、F.javaをインポートすることで使えます。 実際には下記のようにコントローラーなどでimport文を書きます。

import play.libs.F;

F.javaにはOption以外にも、ScalaでおなじみのTuple、Either、Promise、そしてコールバックや関数渡し用のCallbackやFunctionといった型が定義されています。

ちなみにPlay 2.0.4におけるOptionの定義は、こんな感じになっています(インターフェース的に抜粋)。Scalaにある定義に似ていますね。

public static abstract class Option<T> implements Iterable<T> {
    public abstract boolean isDefined();
    public abstract T get();
    public static <T> None<T> None();
    public static <T> Some<T> Some(T value);
    public T getOrElse(T defaultValue);
    public <A> Option<A> map(Function<T,A> function);
}

Optionを継承したクラスとして、おなじくおなじみのSomeとNoneが存在します。 isDefinedというのは、値があるかどうかを示すブール値で、Someの場合はtrue、Noneの場合はfalseになります。 また、getメソッドを使うと、Someの場合は値の取得、Noneの場合はIllegalStateExceptionが発生します。 Play 1.2.xからの大きな進歩として、getOrElseメソッドの実装が挙げられます。これにより、Optionらしい使い方が可能になりました。 mapメソッドは、Function型を使って、フィルターのように処理を適用させた値を取り出すことが出来ます。

Option.applyの実装

ただし、ScalaのようにOption(オブジェクト)でくるむと自動でSomeやNoneを返すなんていう仕様にはなっていませんので、そこは自力で作ることになります。 例えばこんな具合のユーティリティクラスを作るとかどうでしょうか。

package libs;
import play.libs.F;
public class OptionUtil {
    static public <A> F.Option<A> apply(A value) {
        if(value != null) {
            return F.Option.Some(value);
        } else {
            return F.Option.None();
        }
    }
}

これは、いたってシンプルなもので、値があればSome、なければNoneを返すというだけのものです。 しかし、ScalaのOption.applyメソッドの実装をみてみると、だいたいそんな感じなのがわかります。

Option.scala

def apply[A](x: A): Option[A] = if (x == null) None else Some(x)

さて、これを使ってみましょう。 まず、インポート文を宣言します。

import libs.OptionUtil;

続いて、てきとうなアクション内にこんな感じで書いてみます。 まずは、文字列がある場合の処理。getOrElseメソッドも使ってみます。

String testString = "文字列です";
F.Option<String> testStringOption = OptionUtil.apply(testString);
System.out.println("testStringOption is :" + testStringOption);
System.out.println("testStringOption getOrElse is :" + testStringOption.getOrElse("文字列ではありません"));

すると、

testStringOption is :Some(文字列です)
testStringOption getOrElse is :文字列です

このように出ます。 続いて、nullの場合を見てみましょう。

String testNullString = null;
F.Option<String> testNullStringOption = OptionUtil.apply(testNullString);
System.out.println("testNullStringOption is :" + testNullStringOption);
System.out.println("testNullStringOption getOrElse is :" + testNullStringOption.getOrElse("文字列ではありません"));

この結果は、こんな風に出ます。

testNullStringOption is :None
testNullStringOption getOrElse is :文字列ではありません

おお、という感じで実行が出来ました。

ScalaのOptionをつかってみる?

さて、ここまで見てきて、そもそも何故Playには、Optionが別途実装されているのかと思われる方もいるでしょう。 実はこのOptionは、PlayがJavaベースだった頃から使われてきたもので、その頃に多く使われていたものでした。今、Scalaがベースになり、大部分のコアコードがScalaで書かれている現在、そこまで活用されているものではないのでしょう。

Play 2.0とScalaは切っても切りはなせないものです。Javaで書く事になろうともです。 それで、Javaで書いたとしてもScalaのお世話にはなるのですが、当然、ScalaのライブラリなどもPlayで何かアプリケーションを作る場合は素で使えます。 つまり、Java版からScalaのOptionを使う事も可能なわけです。

こんな風に、scala.Optionとscala.Option$をまずは、インポートします。

import scala.Option$;
import scala.Option;

最後に$とついているのは、ScalaのObjectを利用するためにこういった表記になりますが詳しくは割愛します。 すると、こういう風に書く事で、ScalaのOptionとして定義ができます。

Option<String> testStringScalaOption = Option$.MODULE$.apply(testString);
System.out.println("testStringScalaOption is :" + testStringScalaOption);

nullの場合も、

Option<String> testNullStringScalaOption = Option$.MODULE$.apply(testNullString);
System.out.println("testNullStringScalaOption is :" + testNullStringScalaOption);

となります。Scalaで書くより煩雑ですが、まあ書けます。 が、問題はここからで、一見、testStringScalaOption.getOrElse(“hoge”)などと書けば値を取得出来そうに見えますが、できません。

ScalaのOptionのgetOrElseは、

final def getOrElse[B >: A](default: => B): B =
    if (isEmpty) default else this.get

こういった実装になっており、これをJavaから定義しようとすると、えと関数をごにょごにょしないとだめぽくねいですかね?ってなって面倒そうに思えるのでやめる感じになります。

ということで、素直にPlayが用意しているOptionを使うというのがよい気もします。

ビュー側での使い方

では、話をビュー側に移します。

Scalaテンプレートでは、Scala版であろうが、Java版であろうがテンプレートエンジン自体は変わらないため、下記のようにJava版であろうと、Scalaライクに記述ができます。

@Option(testString).map { s => @s }

デフォルト値を決めるなら、OptionでおなじみのgetOrElseを使ってこんな風に書けます。

@Option(testString).getOrElse("文字列ではありません")

Play内蔵のOptionを使った場合も同様に、このようにして、nullの場合(つまり、Noneの場合)のデフォルト値を設定しつつ値を取得することが可能です。

@testStringOption.getOrElse("文字列ではありません")

ということで、楽しいplay.libs.F.Optionライフを送りましょう!

Read More

WebSocketアプリケーションを作るのにPlay frameworkをつかおう

この記事は、HTML5 Advent Calendar 2012向けに書いています。

HTML5とその周辺技術などを含んでいるアドベントカレンダーということで、WebSocketのバックエンド側の話について書いてみます。 WebSocketはフロントエンド側はおそらくはJavaScript(対応していないブラウザはFlashが橋渡しをするライブラリを使用するかもしれませんが)で書く事になるでしょう。

WebSocketのバックエンド側は、というとNode.jsで書くというのが認識としては広く知られているところではないでしょうか。 しかし、実際WebSocketのサーバーとして機能するものは大量に存在しており、Wikipediaをみただけでも相当数存在することがわかります。

今回は、その中でもオススメなサーバー側フレームワークとしてPlay frameworkを、WebSocketアプリケーション構築のメリット中心に紹介したいと思います。

Play frameworkのサイト

Play frameworkって何ですか?

Play framework(以下Play)は、「一言で言うと、型安全なRails」みたいな形容で表現されることがあります。 Playのバージョンが1の頃は、Javaベースで書かれており、確かにそのような表現がふさわしかったと言えますが、バージョン2以降はScalaで書き直され、それに留まらないようなフレームワークになっていきつつあります。1.xからずっと使ってきたので、2の変化には戸惑いました。 現状、ScalaとJavaで開発が可能な、Webアプリケーションフレームワークとして知られています。

日本では有志による翻訳も行われており、

Play framework 日本語訳サイト

があります。

Playの特徴としては、

  • 全てをコンパイルし、型安全
  • 非同期も同期も得意
  • RESTful的な方向性

が掲げられていますが、

個人的には、

  • Rails以降のMVC型Webアプリケーションフレームワークの作法に則っており、
  • なおかつ、WebSocketとかの非同期系の処理も得意で、
  • 非常に高速に動作し(ただし、コンパイル自体はすこし時間がかかる)、
  • ビルドシステムや本番で使えるWebサーバーがデフォルトで内蔵されていて、
  • 全てがプラガブルな(Play本体自体もビルドシステムのプラグイン)、
  • JavaとScala(とXtend)両方で開発(混ぜられる!!)出来、
  • 静的型付けな言語の魅力を活かしたIDEによる強力な開発サポートを受ける事ができる

という点で気に入っているWebアプリケーションフレームワークです。

Playはよく動作速度に関して、Node.jsと比較されたりします。 両方とも、ノンブロッキングI/O(っていうモデルがあるんです)を採用している点や、WebSocketを扱える点など共通点も多いため、仕方がないのですが、今回は比較はしません。

理由は僕がPlayの魅力のほうを語りたいためと、Node.jsについて語れるほどの知識がないためです。

ということで、WebSocketアプリケーションをPlayで書くメリットについて。

Playおすすめの理由その1

Playは、Webサーバであると同時に、MVCフレームワークである

Playには内蔵サーバーがあり、Playのアプリケーション自体がサーバーとして起動します。これは、テスト用のビルトインサーバーというわけではなく、実際に本番運用も可能な高速なサーバーです。

加えて、Playは、Rails以降のMVC型Webアプリケーションフレームワークと同じように、モデル、ビュー、コントローラーの役割が分かれているタイプのフレームワークで、フォルダ構造もそうなっています。これ系のWebアプリケーションフレームワークをやってきた方なら直感的にどこになにを書くか理解することができるでしょう。

他のフレームワーク同様、テストに関するサポートも手厚く、ユニットテストやファンクショナルテスト、そしてSelenium WebDriverを利用したWebテストが搭載されています。

また、Java版であれば、Ebean ORMによるデータベースアクセス機能、Scala版であればAnorm他様々なデータベースへのアクセス機能が用意されています。

つまり、Playは、従来型のWebアプリケーションを構築する土台として、機能します。

加えて、Playは、WebのサービスにおけるAPIアプリケーションとしても使えるほどの優れたパフォーマンスを発揮します。 例えば、Kloutという世界的に有名なサービスがありますが、これのAPIにPlayが使われていたりします。

Scaling the Klout API with Scala, Akka, and Play « The Official Klout Blog | Klout

そして、重要なのがここからなのですが、これら既にある程度できあがっている作りやすい土台の上に、WebSocketアプリケーションを構築することが出来るのです。

Playおすすめの理由その2

WebSocketとWebアプリケーションの定義がシームレスに行える

ここからは少し実装寄りの話になります。内容としては、Play Javaを例に挙げます。理由はいろいろな事情からPlay Javaが気に入っているからです。 通常Play Javaでは、基本的にはコントローラーのメソッドを1つのURLに割り当てる事が出来ます。 たとえば、こんな風に書きます。

public static Result index() {
    // もろもろの処理を書く
    return ok(index.render());
}

で、これをRoutesの定義ファイルみたいなところに

GET     /index        controllers.Application.index()

こんな風に書くと、「/index」にてアクセスしたときにindex()メソッドが呼ばれるという仕組みになります。 Playではこんな風にルーティングを定義します。 ここまでは、まあだいたいそんな感じだよねってとこかと思います。

続いてWebSocketの場合はどう書くでしょうか。 WebSocketは、フロント側からは独自のプロトコル「ws://」「wss://」でアクセスされます。 コントローラーに書くコードは、下記コードのようになります。

public static WebSocket<JsonNode> websocket() {
  return new WebSocket<JsonNode>() {
    // もろもろの処理を書く
  }
}

さぞや違う書き方かと思えば、ほとんど通常のWebページとおなじだということがわかります。 Result型ではなく、WebSocket<A>型を返すという以外はほぼ同じです。

もちろん「もろもろの処理を書く」の中には、WebSocketで使われる各種メソッドを定義する必要がありますが、定義すればいいものは、開始時に実行されるonReady()、何らかのメッセージを受信した際に実行されるonMessage()、接続を閉じた時に行われるonClose()の3つを定義さえすれば使えます。

これらイベント処理には、Akkaと呼ばれるフレームワークが使われています。

Akkaのサイト

上記コードは、Routesの定義として、

GET     /websocket    controllers.Application.websocket()

こんな風に定義をした上で、ビュー側から

@routes.Application.websocket().webSocketURL(request)

こう呼び出すと、

ws://localhost:9000/websocket

こういうURLとして出力されます。また、wssプロトコルとしてURLを出力したい場合は、

@routes.Application.websocket().webSocketURL(request, true)

と記述すると

wss://localhost:9000/websocket

のようにURLが出力されます。

つまりは、通常のWebアプリケーションを書く感覚でWebSocketアプリが作れちゃうわけです。 加えてフレームワークで使うモデルには当然ダイレクトにアクセスできますし、WebSocketの監視画面なんかもWebSocketとWebアプリケーションを分けて作る場合に比べるとはるかに楽に実装できるであろうことが推測されます。

実はPlay 1.xの頃にもWebSocketは扱えたのですが、WebSocketControllerという専用のコントローラーを使う必要がありました。 その部分もPlay 2.0以降格段に扱いやすくなっています。

是非冬休みにPlay frameworkを試してみてください!

Read More