JAX-RSはHTML Webアプリケーションを開発するのに充分なフレームワークであるか?

JavaEEでのWebアプリケーション開発フレームワークと言えばJSFですが、JSFはデスクトップGUIの開発スタイルに似せた、コンポーネントベース、イベントドリブンなフレームワークであるため、拒否反応を示す人も多いようです。
で、フロントコントローラー型のフレームワーク (StrutsRuby on Railsなど) が必要な人への選択肢をJavaEEは提供していないのか? ということになるのですが、JAX-RSがこの役割に向いているのではないかと言われています。

でも、基本的にRestful Webサービスを開発するためのJAX-RSがHTML Webアプリケーションの開発に本当に使えるのか、疑問に思っている人も多いと思います。
最近JAX-RSを使ってRestful Webサービスのみならず、HTMLを返却するWebアプリケーションの開発にも使ってみる機会があったので、そこで分かったことについてまとめてみたいと思います。
(JAX-RSの実装としてはJerseyを、アプリケーションサーバーはGlassFishを選択しています)

Restful Webサービス開発フレームワークとしてのJAX-RS

まず、JAX-RS本来のターゲットであるRestful Webサービス開発についてです。この点においては非常に優れたフレームワークであるとの感想を持ちました。
優れていると思った点は次の通りです。

ルーティングの設定が直観的で分かり易い

JAX-RSではルーティングの設定をアノテーションで記述します。

@Path("/contacts")
public class ContactService {
    @GET
    @Path("{id}")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Contact find(@PathParam("id") Long id) {
        return repository.find(id);
    }
}

この例のようにHTTPメソッド、パス、パスパラメータのマッピングをクラスやメソッドのすぐそばに指定でき、かなり柔軟な設定が可能です。
多くのフレームワークではルーティング設定は外部設定ファイルに記述するか、あるいはフレームワークが定めた規約に従った記述をすることになっていますが、個人的にはルーティング設定は実装のすぐそばに明示的に示してくれた方が分かり易いと思っています。

XMLJSONへのシリアライズが楽

上に示したコード例を観ると分かりますが、普通にJavaオブジェクトを返却する普通のJavaプログラムとして書いておけば、JAXBやJackson *1 などを使ってよしなにXMLJSONシリアライズしてくれるのでとっても楽ちんです。
普通のJavaプログラムとして作るのでとてもテストしやすいです。

例外マッピングが優れている

例外処理というのは常に頭を悩ませるものですが、JAX-RSはこの点もなかなか優れています。
JAX-RSには例外マッピングの仕組みがあり、ExceptionMapper を例外の種類に応じて実装しておくと、メソッドから例外が投げられた時にハンドリングをして、望みのレスポンスコードを返すことなどができるようになります。

/**
 * コンテンツが見つからないことを示す例外ContentNotFoundExceptionを定義したとして、
 * その例外が送出された場合は404を返す例外マッピングを行う例。
 */
@Provider
public class ContentNotFoundExceptionMapper implements ExceptionMapper<ContentNotFoundException> {
    @Override
    public Response toResponse(ContentNotFoundException exception) {
        return Response.status(Response.Status.NOT_FOUND).build();
    }
}

HTML Webアプリケーション開発フレームワークとしてのJAX-RS

それでは本題のHTML Webアプリケーション開発について述べてみたいと思います。
JAX-RSWebサービスを実装するためのもの、と思われがちですが、HTMLを返すことができますし、実装系はそれをサポートする仕組みを用意しています。
Jerseyでは、次の例のように、Viewable オブジェクトに遷移先のテンプレートとモデルオブジェクトを指定すると、JSPに処理を委譲できるようにする仕組みを提供しています *2

    @GET
    @Produces(MediaType.TEXT_HTML)
    public Viewable findAll() {
        List<Users> users = repository.findAll();
        return new Viewable("/user_list.jsp", users);
    }

さて、この仕組みを用いて、JAX-RS (Jersey) を使ってWebアプリケーションを構築してみたのですが、Webアプリケーションフレームワークとして必要な機能の実装具合がどうだったのか、作ってみて分かったこと示します。
リクエスト処理パート (リクエストパラメータを受け取り、本処理を行って遷移先画面を決定するまで) の実装とテンプレート処理パートの実装 (つまりJSP実装) に分けてまとめたいと思います。

リクエスト処理パートの実装について

リクエスト処理パートでフレームワークの求められる機能は次のようなところでしょうか。

  1. 認証認可
  2. ルーティング
  3. クライアントの送信パラメータの集約
  4. バリデーション
  5. 遷移先の決定
  6. 例外処理

まず、1についてはJavaEEに用意されている仕組みをそのまま使います。Servletの場合と同じです。2については既に述べた通りですね。なかなか優れています。

3については、@PathParam、@QueryParam、@FormParam アノテーションを使って、メソッドパラメータにマッピング可能なようになっています。メソッドパラメータを String 以外の型にしていた場合、型変換も行ってくれます *3
キーの名称が不定な場合など、Map 型のコレクションで受け取りたい場合は、次のように MultivaluedMap オブジェクトに集約させることが可能です。

    @GET
    @Produces(MediaType.TEXT_HTML)
    public Viewable getListByCondition(@Context HttpServletRequest request, @Context UriInfo uriInfo) {
        // QueryParamからMultivaluedMapを取得するには、UriInfoを@Contextアノテーションでインジェクトさせて取得する
        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();

    }

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_HTML)
    public Response create(@Context HttpServletRequest request,
            @Context UriInfo uriInfo,
            MultivaluedMap<String, String> params) {
        // FormParamからMultivaluedMapを取得するには、MultivaluedMapの引数を宣言すればよい

    }

4についてはJavaEEに用意されている Bean Validation を使えば良いでしょう。
5、6についても既に述べたように仕組みがありますね。

総じて、リクエスト処理パートについてはWebアプリケーションフレームワークが備えるべき基本的な機能は充分に揃っているとの感想を持ちました。

テンプレート処理パートの実装について

Jerseyの場合、ViewableオブジェクトにくるんだモデルをJSPのEL側では "it" という変数名で参照できるようになっています。
それ以外については特に支援機能はありません。つまり、そこから先はピュアなJSPプログラミングになります。ELやJSTLを駆使して実装することになります。
もっともJavaEE6でELにメソッドも使えるようになったため、JSPでも他のテンプレートエンジンと特に遜色のないレベルで開発が可能になっていると思っています。

ただ、普通のJSPプログラミングであるため、エラー時にメッセージやハイライトをしたり、条件に応じた表示変更と言った処理もゴリゴリ実装する必要があります。これが地味にめんどかったりします。少し大きな規模の開発案件になった場合はある程度プレゼンテーションレイヤで必要となるライブラリを整備してあげる必要があると思います。

まとめると

総じてServletプログラミングよりは少し楽になる程度と考えておいた方がいいです。
リクエスト処理パートまではルーティングやパラメータ取得、例外ハンドリングといった部分でServletより大分楽になります。あとはFlashスコープのようなものがあると言うことなしなんですけどね。これに対して、テンプレート実装の部分についてはほぼ支援機能はありません。

ただ、フレームワーク部分が薄いので、その分ハマりにくいという安心感があると思います。JSFみたいに分厚い機構だとフレームワークの動きを熟知していないとハマったときに大変ですからね。ブラックボックス部分が少ない方が良いと思う人にはお勧めです。

テンプレート処理部分については支援機能がありませんが、今後のクライアントアプリケーション開発のトレンドを考えると、実はそれでも良いんじゃないかと思っています。
そもそもサーバーサイドでUI実装を行うのは、クライアント、ブラウザが非力だった時代の産物だと思っています。特にユーザー操作に対するインタラクティブな処理 (入力エラーのハイライトとかユーザー操作に伴う表示非表示の制御とか) はクライアントが責務を持つべきところで、サーバーがやる仕事じゃないと思っています。デスクトップGUIスマートフォンアプリを作るときはそういう実装になりますよね?
なので、今後はWebサーバーはデータやHTMLの基本構造だけを返すようにして、クライアント側のユーザーインタラクションに関わる処理はJavaScript (JavaScriptに変換する系の言語も含む) で実装する方向に行くべきだと自分は考えています。実際そういう流れに向かっていると思っています。*4

とまあ、こんなところでした。少しでも参考になればと。

*1:Jacksonは変換ルールが分かり易いので、JSONへのシリアライザとしてお勧めです。

*2:Spring MVCに少し似ていますね。

*3:型変換に失敗した場合はクライアントに 400 (Bad Request) を返すので、そこは注意が必要です。

*4:そういう意味ではRails4がTurbolinksみたいな仕組みを用意したのにはちょっと違和感を感じています。お前ら何そこで頑張っているんだと。