REST

REST とは REpresentational State Transfer の略です。

2000 年にカリフォルニア大学 Irvine 校 (University of California, Irvine) の Roy Fielding による博士論文「Architectural Styles and the Design of Network-based Software Architectures」の中で最初に紹介されました。

この論文では、分散コンピューティングのプラットフォームとして Web を使用するソフトウェア・アーキテクチャーについて、次のいくつかの原則が提唱されています。

  • アドレス可能なリソース (Addressable resources)
  • 制約された統一インターフェイス (A uniform, constrained interface)
  • 表現指向 (Representation-oriented)
  • ステートレスな通信 (Communicate statelessly)
  • アプリケーション状態エンジンとしてのハイパーメディア (Hypermedia As The Engine Of Application State, HATEOAS)

これらの原則に従った Web システムは「RESTful なシステム」と呼ばれます。

また、REST は特定のプロトコルや技術要素に依存するものではありませんが、一般的には REST over HTTP を指します。

アドレス可能なリソース

リソースは、REST における重要な概念です。個々のリソースは、URI (Uniform Resource Identifier) を通してアドレス可能でなければなりません。

制約された統一インターフェイス

平たく言えば、(「リソース」に対して) HTTP で規定された GET, POST などのメソッドだけを使用してインタフェースを構築すべしということです。 REST において使用される主なメソッドは次の通りです。

GET

GET は読み取り専用の操作です。この操作は「べき等」かつ安全な操作となります。べき等とは、操作を何度繰り返しても常に同じ結果となることを意味します。安全とは、GET の呼び出しによりサーバの状態が全く変更されないことを意味します。

PUT

PUT は、リソースの作成・更新のために使用されます。この操作は、べき等、かつ非安全な操作となります。

DELETE

DELETE は、リソースの削除のために使用されます。この操作も、べき等、かつ非安全な操作となります。

POST

POST は、唯一非べき等、かつ非安全な操作となります。POST メソッドにおいて何をしなければならないという規定はなく、また、リクエストと一緒に情報を送信してもしなくてもよいですし、また、レスポンスから情報を受信してもしなくても構いません。

HEAD

HEAD は GET と非常に似ていますが、レスポンスボディは返さず、レスポンスコード・リクエストと関連付けられたヘッダのみを返します。

表現指向

リソースに対するインタフェースにおいてどのようなフォーマットを使用するかを、サーバ・クライアント間でネゴシエートして決められるようにすべきであるという原則です。1 つの URI によって参照されるリソースは複数のフォーマット (XML, JSON, YAML, etc...) を持つことができます。

ステートレスな通信

サーバのスケーラビリティを確保するために、セッションデータはクライアントで保持するべきであるという原則です。

セッション固有のデータは全てクライアントで管理され、サーバでは「リソース」のデータのみを管理するということになります。

アプリケーション状態エンジンとしてのハイパーメディア

この原則のことを、Hypermedia As The Engine Of Application State の略で HATEOAS と呼ぶのだそうです。

例えば Web ブラウザのアドレスバーに URL を直接入力してページ遷移するのではなく HTML のリンクをたどることによって別の Web ページに遷移することができるのと同じように、Web サービスからの応答に別のリソースへのリンクを埋め込んでおくことによって、単にそのリンクをたどるだけで別のサービスを呼び出せるようにするという原則です。

  • サービスに対するパラメータ
  • いつ、どのサービスを呼び出すことができるか

等といったことについての考慮をなるべくクライアントサイドでやらずに済ませられるようにしようということのようですが、これらはアプリケーションにおいて定義されるべきことですので、HATEOAS についてフレームワーク (JAX-RS) において提供される機能はありません。

JAX-RS

JAX-RS とは、Java において RESTful なサービスを構築するために使用可能な API です。プレーン Java オブジェクトに対して JAX-RS で提供されるアノテーションを適用する事によって使用します。

Java EE 6 にバージョン 1.1 が取り込まれており、Java EE 7 にバージョン 2 が取り込まれています。バージョン 2 において、クライアント API・フィルタ・CDI 連携などの機能が追加されています。

JAX-RS による RESTful サービスの構築

JAX-RS について学ぶために、これを使用して簡単な RESTful サービスを構築してみます。

まず、RESTful サービスで取り扱うデータとして、次の Items, Item クラスを使用するものとします。 Item クラスは Items クラスに集約され、Items クラスでは Item の追加・取得・更新・削除 (CRUD) が可能です。

public class Items {
    private Map<String, Item> items = new HashMap<>();

    public void addItem(Item item) {
        String id = String.valueOf(items.size());
        item.setId(id);
        items.put(id, item);
    }
    public void setItem(Item item) {
        items.put(item.getId(), item);
    }
    public Item getItem(String id) {
        return items.get(id);
    }
    public Collection<Item> getAllItems() {
        return items.values();
    }
    public int size() {
        return items.size();
    }
    public void deleteItem(String id) {
        items.remove(id);
    }
    public void deleteAllItem() {
        items.clear();
    }
}
public class Item {
    private String id;
    private String name;

    public Item() {
    }
    public Item(String name) {
        this(null, name);
    }
    public Item(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String toString() {
        return String.format("%s (%s)", name, id);
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

JAX-RS が提供するアノテーション

RESTful サービスのクラスを構築する前に、JAX-RS が提供するアノテーションの一部を紹介します。

HTTP メソッドのバインド

リソースに対する HTTP メソッドと Java のメソッドをバインディングするために次のアノテーションを使用できます。

  • @javax.ws.rs.GET
  • @javax.ws.rs.PUT
  • @javax.ws.rs.POST
  • @javax.ws.rs.DELETE
  • @javax.ws.rs.HEAD

当然これらのアノテーションはメソッドに対して指定することになります。なお、1 つのメソッドに対しては 1 つの HTTP メソッドアノテーションしか適用できません。

@Consumes, @Produces

HTTP メソッドアノテーションとともに @Consume, @Produces アノテーションを指定することにより、リクエスト・レスポンスで用いる HTTP Content-Type を JAX-RS に示すことができます。例えば次の例では application/xml の GET レスポンスを返すメソッドと、application/xml の POST リクエストを受け付けて application/xml のレスポンスを返すメソッドの定義例です。

@GET
@Produces("application/xml")
public StreamingOutput getItem(String id) {
    :
    :
}

@POST
@Consumes("application/xml")
@Produces("application/xml")
public Response postItem(Item item) {
    :
    :
}

リソース

@javax.ws.rs.Path というアノテーションによってリソースを定義できます。このアノテーションは、クラス、またはメソッドに指定可能です。

クラスを @Path("/items") のようにアノテートすることによって「ルートリソース」を定義できます。

「サブリソース」を返すリソースのメソッドを @Path アノテートすることによってサブリソースのファクトリメソッドを定義できます。このようなメソッドは「サブリソースロケータ」と呼ばれます。

また、HTTP メソッドアノテーションとともに @Path アノテーションを使用することによって特定のサブリソースに対するメソッドを定義できます。

テンプレートパラメータ

@Path("{id}") のように指定することによって、名前付きのワイルドカードパターン (「/」 (スラッシュ) を含まない文字列) を指定できます。このアノテーションが @Path("/items") と指定されたクラスのサブリソースロケータに指定されていた場合、/items/foo のような URI を処理します。

また、正規表現を使用することもできます。上記の例で @Path("{id : \\d+}") というアノテーションを指定した場合、/items/42 のような URI だけを処理します。

リソースクラスの例

これまでのことを踏まえて、Items, Item を処理するためのリソースクラスを構築してみます。

@Path("/Items")
public class ItemsResource {

    static Items items = new Items();

    @Context
    private UriInfo context;

    /**
     * Creates a new instance of ItemsResource
     */
    public ItemsResource() {
    }

    /**
     * Retrieves representation of an instance of bar.ItemsResource
     * @return an instance of data.Items
     */
    @GET
    @Produces("application/xml")
    public Items getXml() {
        System.out.printf("get all items\n");
        return items;
    }

    /**
     * POST method for creating an instance of ItemResource
     * @param content representation for the new resource
     * @return an HTTP response with content of the created resource
     */
    @POST
    @Consumes("application/xml")
    @Produces("application/xml")
    public Response postXml(Item content) {
        items.addItem(content);
        System.out.printf("add item: %s\n", content);
        return Response.created(context.getAbsolutePath()).build();
    }

    /**
     * DELETE method for creating an instance of ItemResource
     * @return an HTTP response with content of the created resource
     */
    @DELETE
    @Produces("application/xml")
    public Response deleteXml() {
        items.deleteAllItem();
        System.out.printf("delete all items\n");
        return Response.created(context.getAbsolutePath()).build();
    }

    /**
     * Sub-resource locator method for {id}
     */
    @Path("{id}")
    public ItemResource getItemResource(@PathParam("id") String id) {
        return ItemResource.getInstance(id);
    }
}
public class ItemResource {

    private String id;

    /**
     * Creates a new instance of ItemResource
     */
    private ItemResource(String id) {
        this.id = id;
    }

    /**
     * Get instance of the ItemResource
     */
    public static ItemResource getInstance(String id) {
        // The user may use some kind of persistence mechanism
        // to store and restore instances of ItemResource class.
        return new ItemResource(id);
    }

    /**
     * Retrieves representation of an instance of bar.ItemResource
     * @return an instance of data.Item
     */
    @GET
    @Produces("application/xml")
    public Item getXml() {
        Item item = ItemsResource.items.getItem(id);
        System.out.printf("get item: %s\n", item);
        return item;
    }

    /**
     * PUT method for updating or creating an instance of ItemResource
     * @param content representation for the resource
     * @return an HTTP response with content of the updated or created resource.
     */
    @PUT
    @Consumes("application/xml")
    public void putXml(Item content) {
        content.setId(id);
        System.out.printf("put item: %s\n", content);
        ItemsResource.items.setItem(content);
    }

    /**
     * DELETE method for resource ItemResource
     */
    @DELETE
    public void delete() {
        System.out.printf("delete item: %s\n", id);
        ItemsResource.items.deleteItem(id);
    }
}