Scenic3 によるRESTサポート

Scenic3はSlim3のController層を薄くラップした拡張ライブラリです。Slim3では1アクション(URL)に対して1つのControllerを作成するシンプルなデザインですが、どうしてもControllerの数が多くなってしまいます。関連するアクションは1つのクラスにまとめ、アクションメソッドを複数定義する設計を好む人にとってはやや窮屈な仕様です。Scenic3ではそんな人達のために、複数のControllerクラスを1つのPageクラスに定義する手段を提供しています。
一方、REST風のAPIを設計する場合は同一URLに対して異なるHTTPメソッドを割り当てる事になります。例えば、あるモデルに対する取得と削除は次のような同じURLとなります。

http://yourdomain/item/{id}

idにはモデルを識別する一意の値が指定されます。同一のモデルに対する操作は同じURLとなり、HTTPメソッドがGET/POST/DELETE/PUTのどれであるかにより振る舞いが変わるようになります。
このようなREST風のAPISlim3で実装するには、Controllerのrunメソッド内で、isGet, isPut, isPost, isDeleteメソッドを利用して条件分岐を行えば実現できます。Scenic3を使った場合でもisGet, isPut, isPost, isDeleteが利用できるので同様です。
Scenic3の目的は「見通しの良いコード」を実現することです。0.4.0で実装されるRESTサポートを利用する事で、アクションメソッド毎に、呼び出されるHTTPメソッドを宣言的に指定できるようになります。

RESTサポートを使ったPageクラス

以下のサンプルはScenic3のRESTサポートを利用したPageクラスです。アクションメソッドに対してActionPathを指定する所までは従来のScenic3と同じですが、@GET, @POST, @PUT, @DELETEアノテーションが追加されています。

@Page("/item")
public class RESTPage extends ScenicPage {
    @GET
    @ActionPath("{id}")
    public Navigation get(@Var("id") Long id) throws IOException {
        super.requestScope("id", id);
        return response("@GET: id=" + id);
    }

    @POST
    @ActionPath("{id}")
    public Navigation post(@Var("id") Long id) throws IOException {
        super.requestScope("id", id);
        return response("@POST: id=" + id);
    }

    @PUT
    @ActionPath("{id}")
    public Navigation put(@Var("id") Long id) throws IOException {
        super.requestScope("id", id);
        return response("@PUT: id=" + id);
    }

    @DELETE
    @ActionPath("{id}")
    public Navigation delete(@Var("id") Long id) throws IOException {
        super.requestScope("id", id);
        return response("@DELETE: id=" + id);
    }
}

RESTサポートを使わない場合は、次のようになります。

@Page("/item")
public class RESTPage extends ScenicPage {

    @ActionPath("{id}")
    public Navigation get(@Var("id") Long id) throws IOException {
        super.requestScope("id", id);
        if (isGet()) {
          return response("@GET: id=" + id);
        } else if (isPost()) {
          return response("@POST: id=" + id);
        } else if (isPut()) {
          return response("@PUT: id=" + id);
        } else if (isDelete()) {
          return response("@DELETE: id=" + id);
        } else {
          throw new IllegalStateException();
        }
    }
}

設計の好みはありますが、アノテーションを使いif文を減らす方がコードの見通しが良いと感じませんか?

使い方

使い方はScenic3の使い方とほとんど同じです。異なるのは@GET, @PUT, @POST, @DELETEの各アノテーションを付与する事です。単純化の為、複数のHTTPメソッドを1つのアクションメソッドに定義することはできません。つまり、特定のHTTPメソッドか全てのメソッドのどちらかです。

HTTP Method Override

HTTP PUTやDELETEを偽装する場合は、FrontControllerにパラメータとして「scenic3.httpMethodOveride」を指定してください。

    <filter>
        <filter-name>FrontController</filter-name>
        <filter-class>org.slim3.controller.ScenicFrontController</filter-class>
        <init-param>
          <param-name>scenic3.httpMethodOveride</param-name>
          <param-value>true</param-value>
        </init-param>
    </filter>

scenic3.httpMethodOverideをtrueに設定する事で、HTTP Header に X-HTTP-Override-Methodが含まれる場合、設定されたHTTPメソッドとして扱うようになります。また、POSTでかつパラメータに「_method」が含まれている場合、設定されたHTTPメソッドとして扱うようになります。デフォルトでは無効になっていますので、必要に応じて有効にしてください。

尚、Scenic3 0.4.0 のリリースは今週末を予定しています。ドキュメントはこちらからどうぞ。